ruby-on-railsrubyacts-as-taggable-on

Rails ActiveRecord::AssociationTypeMismatch - ActsAsTaggableOn::Tag(#755220) expected, got "" which is an instance of String(#7280):


I'm using the acts-as-taggable-on gem to add tags on my business + service models in my Rails 6 app in order to enable users to find whatever service/business they're seeking more easily. A service will have food-specific tags available if the business is a restaurant, and more general ones if it's another type of business. Whenever I try to create either a new business or a new service, I'm getting the same error:

ActiveRecord::AssociationTypeMismatch - ActsAsTaggableOn::Tag(#755220) expected, got "" which is an instance of String(#7280):
  app/controllers/services_controller.rb:12:in `create'

Here is the relevant part of my Service model code:

class Service < ApplicationRecord
  belongs_to :business

  acts_as_taggable_on :tags
  acts_as_taggable_on :food_taggings, :service_taggings

Here's the relevant part of my new service form:

<%= simple_form_for [@business, @service] do |f|%>
<%= f.input :food_taggings, collection: Service.foodlist, input_html: {multiple: true, id: "food_tagging_new", class: "select2"}, label: "Please add some descriptive tags to the dish that you're offering so that local users could find it more easily" %>
<%= f.input :service_taggings, collection: Business.offerings, input_html: {multiple: true, id: "service_tagging_new", class: "select2"}, label: "Please add some descriptive tags to the service that you're offering so that local users could find it more easily" %>
<%= f.button :submit, 'Submit', class: 'btn btn-primary'%>
</div>
<% end %>      

Here's the relevant part of my services controller code:

def create
    @service = Service.new(service_params)
    if @service.save
      flash[:notice] = "This service was successfully added!"
      redirect_to @service
    else
      render "new"
    end
  end

private
  
  def service_params
    params.require(:service).permit( :tag_list, tag_list: [], food_taggings: [], service_taggings: [] )
  end

And here are the params that go along with the create new service request:

{"authenticity_token"=>"kld9sOSfro/nrINxQdKpXCZnxt6Cjb6TIw+jcjW5XmpUhvfm767dPXStOGB2vEBbckZvb87uKXlZo2KGjAo8vA==", "service"=>{"name"=>"", "description"=>"", "price_cents"=>"", "food"=>"0", "food_taggings"=>[""], "service_taggings"=>[""]}, "commit"=>"Submit", "controller"=>"services", "action"=>"create", "business_id"=>"5"}

How would I go about fixing this issue so that both of the models can get created successfully? I was already able to create some seeds for both without including any tags successfully, and I'm not sure exactly what Rails is expecting now?


Solution

  • To recap what acts_as_taggable_on is doing, say you declare a single taggable attribute:

    class Service < ApplicationRecord
      acts_as_taggable_on :tags
    end
    

    The gem does two things:

    1. It dynamically creates an association (i.e., has_many/belongs_to) between your class and ActsAsTaggableOn::Tag, which is how the gem models tags you define. When you access tags on a instance of Service, you get an array of these Tag objects like you would with any has_many association.
    2. It also creates a friendly convenience wrapper called tag_list (note: singluarized), which is the main way the gem expects you to interact with tags. Calling this will do the work of querying the associated Tag objects and return you a nice array of strings. Or you can assign it an array of strings, which get parsed into Tag objects.

    In your form and controller, you are using the raw association references (food_taggings and service_taggings). Thus when your form POSTs, Rails properly raises an error because it is expecting those parameters to be arrays of Tag objects not arrays of strings.

    If you change your form to use the convenience wrapper names for the form fields, the gem will properly parse the array of strings in your params and create the associated objects:

    <%= simple_form_for [@business, @service] do |f|%>
      <%= f.input :food_tagging_list, ... %>
      <%= f.input :service_tagging_list, ... %>
      ...
    <% end %>  
    

    Don't forget to alter your permitted parameters on the controller as well:

    def service_params
        params.require(:service).permit(tag_list: [], 
                                        food_tagging_list: [], 
                                        service_tagging_list: [])
    end