0

I have three models - User, Client, Topic.

User

has_many :clients
has_many :topics, :through => :clients, :uniq => true

Client

has_and_belongs_to_many :topics

Topic

has_and_belongs_to_many :clients

What I am trying to do is on the edit view of my client, change the topics that this client has.

This is the Update Action of my Clients Controller:

  def update
        if params[:topic_ids]
             @client = current_user.clients.find(params[:id])
             @client.topic_ids = params[:client][:topic_ids]
             @client.save
        else
             @client = current_user.clients.find(params[:id])
        end


    respond_to do |format|
      if @client.update_attributes(params[:client])
        format.html { redirect_to @client, notice: 'Client was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @client.errors, status: :unprocessable_entity }
      end
    end
  end

This is what the log looks like:

Started PUT "/clients/6" for 127.0.0.1 at 2012-10-07 18:56:14 -0500
Processing by ClientsController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"J172mxxCX0OdxcGm4GSPv8=", "client"=>{"name"=>"Testeeee Johnson", "email"=>"testeee@johnson.com", "phone"=>"4320981234", "firm_id"=>"1", "personal_priority"=>"1", "last_contact"=>"2012-06-08", "vote"=>"1", "vote_for_user"=>"0", "next_vote"=>"2012-10-10", "vote_ii"=>"0", "vote_ii_for_us"=>"0"}, "topic_ids"=>["2"], "commit"=>"Update Client", "id"=>"6"}
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
  Client Load (0.1ms)  SELECT "clients".* FROM "clients" WHERE "clients"."user_id" = 1 AND "clients"."id" = ? LIMIT 1  [["id", "6"]]
  Topic Load (0.2ms)  SELECT "topics".* FROM "topics" INNER JOIN "clients_topics" ON "topics"."id" = "clients_topics"."topic_id" WHERE "clients_topics"."client_id" = 6
   (0.1ms)  begin transaction
   (0.0ms)  commit transaction
   (0.0ms)  begin transaction
   (0.0ms)  commit transaction
   (0.0ms)  begin transaction
   (0.0ms)  commit transaction
Redirected to http://localhost:3000/clients/6
Completed 302 Found in 8ms (ActiveRecord: 0.8ms)

Needless to say, it doesn't update the record for client.topics.

How do I update the topics attribute of my client record?

Edit 1

This is how the _form partial looks:

<%= form_for(@client) do |f| %>
  <% if @client.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@client.errors.count, "error") %> prohibited this client from being saved:</h2>

      <ul>
      <% @client.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

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

  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :firm %><br />
    <%= f.select :firm_id, Firm.all.collect { |firm| [firm.name, firm.id] }, {:include_blank => 'None'} %>
  </div>
  <div class="field">
  <h4>Topics</h4>  
    <% Topic.all.each do |topic| %>
      <% checked = @client.topics.include?(topic) %>
        <%= f.label(:name, topic.name) %> <%= check_box_tag "topic_ids[]", topic.id, checked %>
    <% end %>
  </div>
.
. - reduced for brevity
.


  <br /><br />
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
marcamillion
  • 32,933
  • 55
  • 189
  • 380

2 Answers2

0

For whatever reason, the form helper doesn't work with check_box.

So, this is the code that works:

<%= check_box_tag "client[topic_ids][]", topic.id, checked %>

According to other answers for similar questions, the helper f.check_box is model bound and the value supplied to the checkbox is implicit from the model on the form. The issue is, I can't figure out how to get the implicit value of the form_helper to produce the correct tag - i.e. client[topic_ids][], so I have had to resort to check_box_tag.

Community
  • 1
  • 1
marcamillion
  • 32,933
  • 55
  • 189
  • 380
-1

You mentioned in a comment below you would also like to add a weighting to each topic. This is no longer supported in the has_and_belongs_to_many association, so instead you should use the has_many :through association:

class User < ActiveRecord::Base
  has_many :clients
end

class Clients < ActiveRecord::Base
  belongs_to :user
  has_many :client_topics
  has_many :topics, :through => :clients_topics
end

# Create this table like any other, with a "weight" field
class ClientsTopics < ActiveRecord::Base
  belongs_to :client
  belongs_to :topic
end

class Topics < ActiveRecord::Base
  has_many :clients_topics
  has_many :clients, :through => :clients_topics
end

Now your update will need to delete all existing clients_topics, then loop through the passed topic_ids and weights and add them to the client like this:

def update
  if params[:topic_ids]
     @client = current_user.clients.find(params[:id])

     @client.clients_topics.delete_all

     params[:client][:topic_ids].each_with_index do |topic_id, index|
       weight = params[:client][:weights][index]
       @client.clients_topics.create!(:topic_id => topic_id, :weight => weight)
     end

  else
     @client = current_user.clients.find(params[:id])
  end

  (etc...)
end
Max Dunn
  • 1,244
  • 9
  • 13
  • Is this setup via a polymorphic association? – marcamillion Oct 08 '12 at 00:52
  • I don't get why I would have to use an each block to delete the topics, that seems a bit hacky. Doesn't Rails have something built in to manage these types of relationships? – marcamillion Oct 08 '12 at 00:53
  • You can use delete_all to delete the topics associated with the client, but Rails will leave the record and just set the client_id to nil, which is usually not desirable. – Max Dunn Oct 08 '12 at 00:56
  • No, this isn't a polymorphic association. It is basically the same as the has_and_belongs_to_many association, but the clients_topics table is explicitly created and referenced, rather than having it opaquely used. The other advantage of this is that you can add other fields to the clients_topics table, for instance, a weighting for how important each topic is to that client. – Max Dunn Oct 08 '12 at 00:59
  • Hrmm...interesting Max, because that's exactly what I need to do. Add a weighting of importance. Kinda freaky that you said that! – marcamillion Oct 08 '12 at 01:20
  • Max, how do I generate the join table `ClientsTopics`? Like a regular model? `rails g model ClientsTopics`? I already have a `clients_topics` table - which was made for the HABTM. Am I better off dropping that table and re-doing everything from scratch? – marcamillion Oct 08 '12 at 03:48
  • Michael, yes, you generate the ClientsTopics join table as a regular table and add a regular model for it. The reason for the name is that this is the standard naming convention for HABTM tables, so unless there is a more logical name, it is good to follow this convention. Since the table already exists, there is no reason to drop it, but you can change it to add other fields like, "weight". – Max Dunn Oct 08 '12 at 06:07
  • Max, this looks a bit non-standard & drastic to me - so I am hesitant to try this. I suspect the problem is much more basic than this general overhaul would indicate. I do appreciate your effort and time though. – marcamillion Oct 08 '12 at 06:27