Search code examples
ruby-on-railsrubyhas-many-throughnomethoderror

has_many :through Association, NoMethodError (undefined method `each' for "":String) and save association


I can't get the has_many :through Association to work. And do I need to set something extra in the create method from the post_controller, to save also the post_category? (Below the code, the errors and my tries)

I have a has_many :through Association with post, post_category and as join table categorization as follows:

The Post Model:

class Post < ApplicationRecord
  has_many :categorizations
  has_many :post_categories, :through => :categorizations
end

The Post::Category Model (Used namespacing for better project structure):

class Post::Category < ApplicationRecord
  has_many :categorizations
  has_many :posts, :through => :categorizations
end

The Categorization Model (Used class_name, cause of the namespacing of Post::Category):

class Categorization < ApplicationRecord
  belongs_to :post
  belongs_to :post_category, :class_name => 'Post::Category'
end

And then I have in the view a select where I create a post with a post_category:

<div class="form-group">
  <%= f.label :post_categories, :class => 'control-label', :value => 'Category: ' %>
  <%= f.select :post_categories, options_from_collection_for_select(all_post_categories, :id, :name), {}, {class: 'selectpicker', :'data-live-search' => 'true', required: 'false' } %>
</div>

In the posts_controller, I permitted the :post_categories symbol:

def post_params
  params.require(:post).permit(:post_categories)
end

Here is my migration file for the Association:

class CreateCategorizations < ActiveRecord::Migration[5.0]
  def change
    create_table :categorizations do |t|
      t.integer :post_id
      t.integer :post_category_id

      t.timestamps
    end
    add_index :categorizations, :post_id
    add_index :categorizations, :post_category_id
    # multiple-key index enforces uniqueness on (post_id, post_category_id)
    # pairs, so that a category can't have the same post twice
    add_index :categorizations, [:post_id, :post_category_id], unique: true
  end
end

When I try to submit the form, I get the following error in the logs:

NoMethodError (undefined method `each' for "3":String):
app/controllers/posts_controller.rb:32:in `create'

So I searched for some answers ok -> You can't assign string to association.

I tried another way to make the association going to work.

So I tried using the post_category_id, like recommended here

I set in the select of the view

<%= f.select :post_category_id, options_from_collection_for_select(all_post_categories, :id, :name), {}, {class: 'selectpicker', :'data-live-search' => 'true', required: 'false' } %>

then in the posts_controller permit the category_id:

def post_params
  params.require(:post).permit(:post_category_id)
end

and in the Post model:

class Post < ApplicationRecord
  has_many :categorizations
  has_many :post_category_id, :through => :categorizations
end

This is the new error:

Could not find the source association(s) "post_category_id" or :post_category_id in model Categorization. Try 'has_many :post_category_id, :through => :categorizations, :source => <name>'. Is it one of post or post_category?

Ok makes sense, cause there is no post_category_id association.

Update:

The post_params:

<ActionController::Parameters {"content"=>"content", "post_categories"=>"2"} permitted: true>

Solution

  • I figured out a right solution for me with this answer and this question.

    First solution to just save one Post::Category with a Post

    Regarding to this answer, I had to change my permit parameter in the posts_controller to this:

    def post_params
      params.require(:post).permit(:post_category_ids)
    end
    

    Then I need also to change the select in the view where I create a post with a post_category to this:

    <div class="form-group">
      <%= f.label :post_category_ids, :class => 'control-label', :value => 'Category: ' %>
      <%= f.select :post_category_ids, options_from_collection_for_select(all_post_categories, :id, :name), {}, {class: 'selectpicker', :'data-live-search' => 'true', required: 'false' } %>
    </div>
    

    That's it. The rest of the code is still the same as I showed before my second try in my question.

    Second solution to save multiple Post::Category with a Post

    Regarding to this question, I need to change my permit parameter in the posts_controller to this:

    def post_params
      params.require(:post).permit(:post_category_ids => [])
    end
    

    And to select more than one Post::Category at the same time, I need to add multiple: 'true' to the select in the view:

    <div class="form-group">
      <%= f.label :post_category_ids, :class => 'control-label', :value => 'Category: ' %>
      <%= f.select :post_category_ids, options_from_collection_for_select(all_post_categories, :id, :name), {}, {class: 'selectpicker', :'data-live-search' => 'true', required: 'false', multiple: 'true' } %>
    </div>