複数のフォームがあり空のフォームはサブミットされないようにする

したいこと

二つのフォームがあり片方は何も書かれていない空の状態であれば、そのフォームをサブミットせずに記入されている片方のフォームだけ送信する方法。
この記事のように二つのフォームが同じモデルへ送信するのであれば、これは意味がないが、それぞれが別のモデルへ保存される時に便利。
今回はこの一つのフォームで複数のモデルへ保存するフォームに実装することを想定して作例する。 ただし、そこまで深入りしないが、今回はお互いのフォームはアソシエーションで紐づいていて、親子関係にあるため、親モデルのフォームが空であっても、親モデルのレコードは作成されるようにしている。

ポイント

  • サブミットするときの関数を設定する。
  • クリックすると、jqueryでフォームが空であるか調査し、空であればそのフォームにdisabled属性を加え、フォームを送信

View.html.erb

<%= form_with(model: review, local: true, id: "review_form") do |form| %>
  <div class="field">
    <%= form.label :content %>
    <%= form.text_area :content, id: :review_content %>
  </div>
  <%= form.fields_for :phrases do |phrase_field| %>
    <div class="field">
      <%= phrase_field.text_area :content, id: :phrase_content %>
    </div>
  <% end %>
  <div class="actions">
    <button name="button" type="button" id="submit_btn">投稿</button>
  </div>
<% end %>

jquery

$(function(){
$('#review_form').on('submit', function(){
  if($("#phrase_content").val() == "") {
    $('#phrase_content').attr('disabled','true');
  };
});
});

参考

【jQuery】submit前に処理を行う方法 - Qiita

textareaの縦幅を入力行数によって自動変更させる方法

autosizeというライブラリを使います

autosize | RubyGems.org | your community gem host

デモはこちら

方法

  1. Gemfileに記入
    gem 'autosize', '~> 2.4'
    bundleinstallを実行
  2. app/assets/javascripts/application.jsに以下を追記 //= require autosize

本来ならこれでできるはずだけど、エラーが出てassets配下を探していたので、gem environmentを実行して、GEM PATHを頼りにgemがインストールされているところへ行き、app/assets/javascripts/へautosize.jsファイルをコピーした

jqueryコード

autosize($('textarea'));
たったこれだけで、縦幅が自動で変更されます。 javascriptのコードやいろんなオプションはこちらを参考に

一つのフォームで複数のモデルに保存する

したいこと

今回は映画のレビューを投稿するreview投稿フォーム画面に、 映画の好きなフレーズを投稿できるphrase投稿フォームも表示し、 一度送信ボタンへ押すだけでreviewはもちろん、そのreviewに紐づいたphraseも保存する。 なので、reviewモデルとそのreviewモデルに紐づいたphraseモデルが存在している。

重要なメソッド

  • field_forメソッド
  • accepts_nested_attributes_for

バージョン

rails 5.1.4

 カラム

reviewsテーブルにはcontentカラム
phrasesテーブルにもcontentカラムのみとする

アソシエーション定義

model/review.rb

  has_many :phrases, dependent: :destroy
  accepts_nested_attributes_for :phrases

model/phrase.rb

belongs_to :review, optional: true

Controller

def new
  review = Review.new
  2.times { @review.phrases.build }
end

def create
  @review = Review.new(review_params)
  @review.save
end

private
def review_params
  params.require(:review).permit(:content,  phrases_attributes: [:content])
end

View

<%= form_with(model: review, local: true) do |form| %>
  <div class="field">
    <%= form.label :content %>
    <%= form.text_area :content, id: :review_content %>
  </div>
  <%= form.fields_for :phrases do |phrase_field| %>
    <div class="field">
      <%= phrase_field.text_area :content, id: :phrase_content %>
    </div>
  <% end %>
  <%= form.submit %>
<% end %>

学んだこと

  • field_forメソッドで異なるモデルへ保存するためのフォームを作成できる。controller#newでtimesメソッドを使ってインタンスを作成しておけば、viewではeach文を使って保存しなくても、(field_forだけではなく)form.fields_forでコントローラーで作成したインスタンス分のフォームを表示してくれる。
  • accepts_nested_attributes_forをモデルに追加することでStrongParameterに記載しているネストされたパラメーター(phrases_attributes: [:content])を読み取り、それぞれのモデルのレコードを更新できる。
  • rails5からはbelongs_to :review, optional: trueにしないと外部キー(〇〇_id)がもしnilの場合、保存ができなくなっているため、optional: trueをつけておくとそれを無効化しておく。ただ、今回はあってもなくても、どちらでも大丈夫。

参考

ポートフォリオ

これまでにRuby on Railsで開発したアプリケーション

1. 学習進捗管理表アプリ(11/28~12/3)

業務で生徒さんの学習の進捗を把握しなければいけなかったので、進捗状況を入力するとプログレスバーで見やすく表示できるアプリを作りました。

f:id:shoheimoment:20171223141317p:plain

f:id:shoheimoment:20171223142648p:plain

主な機能
  • 生徒の登録
  • 進捗状況の入力(日付、学習中のページの選択、コメント)
  • 生徒とその進捗を一覧表示
工夫した点

このアプリは通っているスクールの課題などではなく、業務に生かせそうだなと思い、自分の勉強がてら作成しました。その時点で学んでいたものを使って作成しました。
特に、生徒の進捗一覧で最も進んでいる生徒を上から順に表示するのに苦労しました。これにより、ActiveRecordメソッドやクエリメソッドの練習、クラスやメソッドの呼び出し方の練習になりました。

ソースコード

GitHub - shoooohei/progress

2. Instagramのクローンアプリ(12/4~12/18)

これはスクールのカリキュラムの一貫です。それまで習ってきたことを総動員してInstagramに似たアプリを作りました。カリキュラムでは0から作ったアプリとしては3つ目になります。

f:id:shoheimoment:20171223212105p:plain

ソースコード

GitHub - shoooohei/instagram

アプリケーションURL
https://shohei-portfolio-instagram.herokuapp.com/pictures

主な機能
  • ログイン機能
  • 写真投稿とその編集と削除機能
  • コメント投稿とその編集と削除機能
  • お気に入り機能
工夫した点

現在のwebアプリケーションはajax通信でページ遷移が少なくっています。これは現在のアプリには必須スキルだと思ったので、スクールのカリキュラムでは習っていないajax開発を購入した書籍やネットで紹介されているコードを参考にして行いました。
また、Herokuにデプロイしているのですが、Herokuは無料会員ではアプリでアップロードした画像の保持できず、一定時間が経過すると消えてしまします。そこで、AWSのS3と連携してアップロードした画像も保持できるようにしました。
上記2点ともカリキュラムの合格要件ではありませんでしたが、取り組むことでコントローラーとビューやりとりの仕組みを深く知ることができました。

自分の学習記録

現在ご覧になっているこのブログなのですが、こちらから↓記事一覧をみることができます。

shoohei.hatenablog.com

スクールのテキストには書かれていない内容や、自分で勉強して学んだことをアウトプットしています。

また、学習にあてた時間を計測しております↓↓

プログラミング学習時間記録 - Google スプレッドシート

購入した書籍

Ruby on Rails 5アプリケーションプログラミング

Ruby on Rails 5アプリケーションプログラミング

たのしいRuby 第5版

たのしいRuby 第5版

座右の銘

Life can be much broader once you discover one simple fact: Everything around you that you call life was made up by people that were no smarter than you and you can change it, you can influence it, you can build your own things that other people can use.
たった一つの単純な事実にさえ気づきさえすれば人生はもっと広がる。それはあなたを取り巻いている、あなたの人生なるものは、全然あなたより賢くもない連中が作り上げたものであって、あなたはそれを変えることもできるし、それに影響を及ぼすこともできるし、他の人たちも使うであろう何かを自分のために築くことだってできるのだ。    ー映画 「Social Network」Steve Jobsより

人生のモットー

「人生=仕事」「昨日の自分より一歩でも前へ」
自分の成長が一番のいきがいだと思っています。なので、日々一つでもいいから新しい知識や経験を重ねていくように努力しております。 これからも毎日プログラミングの勉強がんばります!

deviseで作ったuserモデルにカラムを追加し、sign up時に登録されるようにする

gemを読む

GitHub - plataformatec/devise: Flexible authentication solution for Rails with Warden.

この部分

# In case you want to permit additional parameters (the lazy way™), you can do so using a simple before filter in your   
`ApplicationController:`

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
  end
end

まず追加したいカラムを通常どうりmigrationファイルで作成

def change
    add_column :users, :name, :string
end

パラメーターで送れるようにビューのフォームに追加したい項目を追記

<div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
</div>

次に上記のコードをApplicationControllerに追記する。変更するのは追加したいカラム名のみ

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
end

HerokuとS3を連携

S3の初期設定

この記事のバケットの作成のところ(Imagemagikの導入の手前)までの通りすればできました。

【Rails】S3へ『CarrierWave+fog』を使って画像アップロードする方法 | vdeep

Gemfile

こちらを追加し、bundle install

gem 'fog-aws', group: :production
gem 'carrierwave', '~> 1.0'

○_uploader/rbに以下を追記

if Rails.env.production?
    storage :fog
  else
    storage :file
  end

carrier_wave.rb

$ touch config/initializers/carrier_wave.rbでファイル作成
作成したファイルに以下を追記

require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'


CarrierWave.configure do |config|
  if Rails.env.production?
    config.storage = :fog
    config.fog_provider = 'fog/aws'
    config.fog_credentials = {
      # Amazon S3用の設定
      :provider              => 'AWS',
      :region                => ENV['S3_REGION'],
      :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
      :aws_secret_access_key => ENV['S3_SECRET_KEY']
    }

    config.fog_directory     =  ENV['S3_BUCKET']
  else
    config.storage :file
    config.enable_processing = false if Rails.env.test?
  end
end

ここの下の画像の人の例を参考にしました。 f:id:shoheimoment:20171215112549p:plain

herokuの環境変数の設定

S3の初期設定で取得できているcredentials を前項のENVの[ ]内の変数を下記コマンドで設定していく。

heroku config:set S3_REGION=
heroku config:set S3_ACCESS_KEY=
heroku config:set S3_SECRET_KEY=
heroku config:set S3_BUCKET=

※credentialsがわからなくなったら、アクセスキーIDとREGIONは次から確認できる。 ルートアカウント認証情報を使用してサインイン→ヘッダーのユーザ名のdropdownからセキュリティ認証情報を選択→左のユーザー→ユーザーを選択→認証情報タブ secret keyはどう確認するかわからないからユーザーを作った時にcredentials.csvをダウンロードしておこう!

デプロイ

これでデプロイしてみるとできている。できなければ、DATABASEのリセットとかを試してみるといいかも。

file_filedの注意点

今回、以下のformで画像をupすることにして、空で送ってしまったときはバリデーションにひっかかるように設定しようと思った。

<%= form_with(model: @user_icon, local: true) do |form| %>
  <%= form.file_field :image %>
  <%= form.submit "更新する" %>
<% end %>

しかし、これで送信すると、以下のようにparameterにrequire(:user_icon)のkeyがそもそもないため、コントローラーのstrong_paramsでエラーになってしまう。
user_icons_controller.rb

def create
    @user_icon = UserIcon.new(user_icon_params)
    if @user_icon.save
      redirect_to user_path(@user_icon.user_id), notice: 'アイコンを設定しました'
    else
      render 'users/show'
    end
  end
#省略
private
  def user_icon_params
    params.require(:user_icon).permit(:user_id, :image)
  end

エラー詳細

[2] pry(#<UserIconsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"62WAR0vsxA+HqJabeXbmOOOCF7TfE+x9WLp0yXFOTes2+dFb8AHh0hq36kOsR7eH4zmbk8GvSjdqdAgvKwjnjA==", "commit"=>"更新する", "controller"=>"user_icons", "action"=>"create"} permitted: false>

とりあえずは、下記のようにform.file_fieldタグの他に、今回アソシエーションで紐づいて必要だったuser_idをhiddenタグをparameterに仕込むことで解決できた。

<%= form_with(model: @user_icon, local: true) do |form| %>
  <%= form.hidden_field :user_id, value: "#{current_user.id}" %>
  <%= form.file_field :image %>
  <%= form.submit "更新する" %>
<% end %>

ただ、今までnameやemailを全て空欄で送信してもバリデーションにひっかけることができたのに、なぜ今回はkey(この例では:user_icon)が設定されないのか不思議である。