19607

[code]

    - User
      - id
      - name
      - group_id
    
    - Group
      - id
      - name
    
[/code]

このようなモデルがあります。  
ユーザーのフォームに「グループ名」という`テキストフィールド`を用意して、  
その中にユーザーが入力したグループ名が存在すれば、そのグループとひも付けて、  
存在しない場合はグループを新規作成してから紐付けるというフォームを作りたいのですが、  
どのように作るのがrailsらしいのでしょうか。

ご教示お願いします。


CC BY-SA 3.0
2015-12-03T08:59:31.797
19593
2015-12-03T23:04:52.390
2015-12-03T23:04:52.390
85
9149
question
ruby-on-rails ruby
Railsで入力したグループ名が未存在なら新しいグループを、存在すれば既存のグループをユーザーに関連づけたい


Userクラスに`group_name`という属性を追加し、before_saveでGroupを検索、なければ作成、というようにするのが良いかなと思います。

[code]

    class User < ActiveRecord::Base
      belongs_to :group
      attr_accessor :group_name
      validates :name, presence: true
    
      before_save :find_or_create_group
      def find_or_create_group
        if group_name.present?
          self.group = Group.find_or_create_by!(name: group_name)
        else
          self.group = nil
        end
      end
    end
    
[/code]

フォームでは`User#group_name`を編集できるようにします。

[code]

    <%= form_for(@user) do |f| %>
      <% if @user.errors.any? %>
        <div id="error_explanation">
          <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
    
          <ul>
          <% @user.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
          </ul>
        </div>
      <% end %>
    
      <div class="field">
        <%= f.label :name %><br>
        <%= f.text_field :name %>
      </div>
      <div class="field">
        <%= f.label :group_name %><br>
        <%= f.text_field :group_name %>
      </div>
      <div class="actions">
        <%= f.submit %>
      </div>
    <% end %>
    
[/code]

コントローラでは`user_params`に`group_name`を追加します。  
また編集画面を開いたときに、`group_name`が入力されているようにします。

[code]

    class UsersController < ApplicationController
      # ...
    
      def edit
        @user.group_name = @user.group.try(:name)
      end
    
      # ...
    
      def user_params
        params.require(:user).permit(:name, :group_name)
      end
    end
    
[/code]

テスト（Minitest）を書くとこんな感じです。

[code]

    require "test_helper"
    
    class UsersTest < Capybara::Rails::TestCase
      setup do
        Group.create!(name: 'Potato')
      end
      test "Manage users" do
        visit root_path
        click_link 'New User'
        assert page.has_selector?('h1', text: 'New User')
    
        # 新しいグループを作成する
        fill_in 'Group name', with: 'Tomato'
        # バリデーションエラーが起きても入力値が残る
        click_on 'Create User'
        assert page.has_content?("Name can't be blank")
        assert page.has_field?('Group name', with: 'Tomato')
        # 登録実行
        fill_in 'Name', with: 'Alice'
        click_on 'Create User'
        assert page.has_content?('User was successfully created.')
        alice = User.find_by_name('Alice')
        assert_equal 'Tomato', alice.group.name
    
        # 既存のグループを割り当てる
        click_on 'Edit'
        assert page.has_selector?('h1', text: 'Editing User')
        assert page.has_field?('Group name', with: 'Tomato')
        fill_in 'Group name', with: 'Potato'
        click_on 'Update User'
        assert page.has_content?('User was successfully updated.')
        assert_equal 'Potato', alice.reload.group.name
    
        # グループの割り当てを解除する
        click_on 'Edit'
        assert page.has_selector?('h1', text: 'Editing User')
        assert page.has_field?('Group name', with: 'Potato')
        fill_in 'Group name', with: ''
        click_on 'Update User'
        assert page.has_content?('User was successfully updated.')
        assert_nil alice.reload.group
      end
    end
    
[/code]

上記のコードはGithubにアップしているので、実際に動かして確認することもできます。

<https://github.com/JunichiIto/find-or-create-form-sandbox>

参考にしてみてください。


CC BY-SA 3.0
2015-12-03T23:02:45.883
19607
2015-12-03T23:02:45.883
85
19593
answer