简体   繁体   中英

Rails polymorphic has_many through association - form broken

In my Rails 5.1 app I am trying to create a tagging system from scratch.

I want Tags to be a polymorphic has_many :through association so that I can tag multiple models.

Currently I'm able to create a Tag (and the associated Tagging ) in the console by doing: Note.last.tags.create(name: "example") which generates the correct SQL:

Note Load (0.2ms)  SELECT  "notes".* FROM "notes" ORDER BY "notes"."id" DESC LIMIT $1  [["LIMIT", 1]]
(0.2ms)  BEGIN
SQL (0.4ms)  INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["name", "example"], ["created_at", "2017-10-21 14:41:43.961516"], ["updated_at", "2017-10-21 14:41:43.961516"]]
Note Load (0.3ms)  SELECT  "notes".* FROM "notes" WHERE "notes"."id" = $1 LIMIT $2  [["id", 4], ["LIMIT", 1]]
SQL (0.4ms)  INSERT INTO "taggings" ("created_at", "updated_at", "tag_id", "taggable_id", "taggable_type") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["created_at", "2017-10-21 14:41:43.978286"], ["updated_at", "2017-10-21 14:41:43.978286"], ["tag_id", 9], ["taggable_id", 4], ["taggable_type", "Note"]]

But when trying to create a Tag and its associations through my form it doesn't work. I can create the Tag but no Tagging .

controllers/notes/tags_controller.rb

class Notes::TagsController < TagsController
  before_action :set_taggable

  private

  def set_taggable
    @taggable = Note.find(params[:note_id])
  end
end

controllers/tags_controller.rb

class TagsController < ApplicationController
  before_action :authenticate_user!

  def create
    @tag = @taggable.tags.new(tag_params)
    @tag.user_id = current_user.id

    if @tag.save
      redirect_to @taggable, success: "New tag created."
    else
      render :new
    end
  end

  private

  def tag_params
    params.require(:tag).permit(:name)
  end

end

routes.rb

...
resources :notes, except: [:index] do
  resources :tags, module: :notes
end
...

.

class Note < ApplicationRecord
  belongs_to :notable, polymorphic: true
  has_many :taggings, as: :taggable
  has_many :tags, through: :taggings
end

class Tag < ApplicationRecord
  has_many :taggings
  has_many :taggables, through: :taggings
end

class Tagging < ApplicationRecord
  belongs_to :tag
  belongs_to :taggable, polymorphic: true
end

notes/show.html.erb

<p><%= @note.body %></p>
<%= render partial: 'tags/tags', locals: { taggable: @note } %>
<%= render partial: 'tags/form', locals: { taggable: @note }  %>

tags/form.html.erb

<%= simple_form_for [taggable, Tag.new] do |f| %>
  <%= f.input :name %>
  <%= f.submit %>
<% end %>

The error might be that the Tagging is not getting saved due to the :tag association being required by default.

Try:

class Tagging < ApplicationRecord
  belongs_to :tag, required: false
  belongs_to :taggable, polymorphic: true
end

Your approach is fundamentially flawed in that it will create duplicates of each tag instead of creating a join record. It also adds unessicary complication in that you have to create nested controllers for each taggable resource.

The fact that this does not fail a uniqueness validation for tags.name shows a shortcoming in your application - you should have a unique index in the DB and a validation in the model to avoid duplicates.

This would be a perfectly fine approach for something like comments where each created record should be unique but is not for this case where you're linking to an indirect association.

To assign existing tags to a record you can use a select or checkboxes to pass an array of ids:

<%= form_for(@note) do |f| %>
  # ...
  <%= f.collection_checkboxes(:tags_ids, Tag.all, :id, :name) %>
<% end %>

To create new tags you can use nested attributes or use ajax to send a POST request to /tags and update the view so that the tag ends up in the list of checkboxes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM