Topic: can anyone get this many to many pdf to work ?

http://jrhicks.net/96
I followed it all and read through the comments to just end up screwing up my file ...
It does not want to update the join table at all..

Any other example sof join tables in rails that you know of ? http://railsforum.com/viewtopic.php?id=265 was cool
but part 2 has never come around yet

Re: can anyone get this many to many pdf to work ?

The API does a decent job of describing some join models. I wish it went into more detail in some areas, but it's worth a read.

Is there any area specifically you are having trouble with?

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

Hmm,, well I gues it's just that it is not sending the create or update to the join table so it will not show the checkbox value in the list or the database but I can add to the database directly just fine to show it's value..

This is just a simple thing to try and get my head around the system of join tables..

Otherwise the API doc Project#portfolio I Idont get the # there.. I thought that was a comment
[code =rails]
class ExpensesController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify :method => :post, :only => [ :destroy, :create, :update ],
         :redirect_to => { :action => :list }

  def list
    @expense_pages, @expenses = paginate :expenses, :per_page => 10
  end

  def show
    @expense = Expense.find(params[:id])
    #@tags = Tag.find(:all)
    @tag = Tag.find(params[:id])
   

  end

  def new
    @expense = Expense.new
    @tags = Tag.find(:all)
  end

  def create
    @expense = Expense.new(params[:expense])
    @tag = Tag.new(params[:tag])
   
    #@expense.tags = Tag.find(@params[:tag_ids])
    if @expense.save
      flash[:notice] = 'Expense was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

  def edit
    @expense = Expense.find(params[:id])
    @tags = Tag.find(:all)
  end

  def update
    @expense = Expense.find(params[:id])
    @tag = Tag.find(params[:id])

    #@expense.tags = Tag.find(@params[:tag_ids])

#@expense.tags = Tag.find(@params[:tag_ids]) if @params[:tag_ids]
#@expense.tag_ids = params[:tag_ids]
    if @expense.update_attributes(params[:expense])
      flash[:notice] = 'Expense was successfully updated.'
      redirect_to :action => 'show', :id => @expense
    else
      render :action => 'edit'
    end
  end

  def destroy
    Expense.find(params[:id]).destroy
    redirect_to :action => 'list'
  end
end
[/code]

Re: can anyone get this many to many pdf to work ?

tripdragon wrote:

Otherwise the API doc Project#portfolio I Idont get the # there.. I thought that was a comment

That can be confusing it times. It just means that portfolio is a method in the Project class - nothing to do with comments.

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

OMG.. i'm more confused now then ever before after reading that api, about this join stuff sad
i'll see if I can think it out, but if you know of anyway to sum up the methods for join and habtm.. some how it would be nice... Like less alien speak...

Last edited by tripdragon (2006-10-04 12:22:59)

Re: can anyone get this many to many pdf to work ?

more big_smile
Is this like 1.1.8 legal anymore ?
_form view
[code =rails]
<%= error_messages_for 'expense' %>

<!--[form:expense]-->
<p><label for="expense_amount">Amount</label><br/>
<%= text_field 'expense', 'amount'  %>
<br />
<%= text_field 'expense', 'amount' %>
<br />
<% for tag in @tags %>
<input type="checkbox"
id="<%= tag.id %>"
name="tag.id[]"
value="<%= tag.id %>"
<%if @expense.tags.include? tag%>checked="checked"<%end%>
><%= tag.name %>
<% end %></p>
<!--[eoform:expense]-->

[/code]

Re: can anyone get this many to many pdf to work ?

I'm assuming you have a HABTM relationship between Tag and Expense and you want to add checkboxes to edit what tags are associated with expense? Rails adds a method to Expense called "tag_ids=" for setting which tags the expense is related to. If we give the checkbox a special name (of 'expense[tag_ids][]') Rails will automatically take all the checked boxes and place their values in an array which will be accessible at params[:expense][:tag_ids]. The beauty of this is it will automatically call the tag_ids= method if we do:

@expense.update_attributes(params[:expense])

Here's the code that should go in the view:

<% for tag in @tags %>
  <%= check_box_tag 'expense[tag_ids][]', tag.id, @expense.tags.include?(tag) %>
  <%=h tag.name %><br />
<% end %>

There, that will generate a list of checkboxes with the proper name and value. It will automatically check the box if the tag is related to the expense. Next, in the update action, we need to just update the expense with the params[:expense]. Everything else will be handled for us:

def update
  @expense = Expense.find(params[:id])
  if @expense.update_attributes(params[:expense])
    #...
  else
    #..
  end
end

There is one more gotcha. If no checkboxes are checked, the params[:expense][:tag_ids] are never set so it doesn't remove all the associated tags from the expense. To solve this we need to set it to an empty array manually if it is nil. Like this:

def update
  params[:expense][:tag_ids] ||= []
  @expense = Expense.find(params[:id])
  #...
end

Does that work?

Last edited by ryanb (2006-10-04 13:21:50)

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

AWESOME! this makes far more sense to me than the pdf

Now that I am journeying into join tables. joining two tables one with 100 fields, and the other with a  small 12, but only displays two fields from that 100 fields tables is this smart ? Or shoudl I break something like that up smaller for a speed and server relief? Like a join table that just looks for a few fields?

Re: can anyone get this many to many pdf to work ?

When you say 100 fields, are you referring to 100 columns or 100 rows in a table? I don't even want to think about 100 columns; is this just an extreme example or do you seriously have a table with 100 columns?

Selecting fewer columns from the database will be faster than selecting all of them, but I wouldn't worry about that now. First work on making a nice, simple design, then optimize later if you need to. Something like that should be as easy to add later as it is now.

Last edited by ryanb (2006-10-04 14:10:18)

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

100 columns, fields is old term I have stuck in my head from filemaker which is where I am dragging this database from..

Yeah, during the development of this I have feared the 100 columns issue, in development mode at 1,400 test rows it has been somewhat zippy but not application zippy...

Last edited by tripdragon (2006-10-04 14:18:27)

Re: can anyone get this many to many pdf to work ?

Keep in mind things should be faster when you switch to the production environment, so the development environment isn't really the best place to test the speed of things.

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

ryanb wrote:

<% for tag in @tags %>
  <%= check_box_tag 'expense[tag_ids][]', tag.id, @expense.tags.include?(tag) %>
  <%=h tag.name %><br />
<% end %>

But this writes out each checkbox with the same ID.  Not only is it not valid XHTML, but it prevents me from using <label for="">.  This is precisely the problem I am having with the <input type="checkbox" id="1"> format that the other method mentioned spits out.  When I have two of those on a page, the labels collide and you get unexpected behavior.

Is there a better solution?

Re: can anyone get this many to many pdf to work ?

You can manually specify the id:

<% @tags.each_with_index do |tag, index| %>
  <%= check_box_tag 'expense[tag_ids][]', tag.id, @expense.tags.include?(tag), :id => "tag#{index}" %>
<% end %>

The name is what gets submitted so that is what must stay the same.

Last edited by ryanb (2006-11-10 18:15:36)

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

This is great Ryan and is works in my case as well. I do want to try and understand all the magic that is going on behind here so I am going to ask some questions. Could you please dissect this for me:

check_box_tag 'expense[tag_ids][]', tag.id, @expense.tags.include?(tag)

1. I think I understand why you are doing expense[tag_ids][], but I don't understand why it works. What is Rails doing within that statement and what is it looking for that makes this special naming work?

2. @expense.tags.include?(tag) - What is going on here?

Much thanks.

Re: can anyone get this many to many pdf to work ?

Ceres wrote:

check_box_tag 'expense[tag_ids][]', tag.id, @expense.tags.include?(tag)

1. I think I understand why you are doing expense[tag_ids][], but I don't understand why it works. What is Rails doing within that statement and what is it looking for that makes this special naming work?

Rails treats square brackets in form field names a special way. If there's text between the square brackets Rails will put the value inside a hash. If there's no text inside the square brackets Rails will gather all the values with that name and put them into an Array.

For example, we end up with several checkboxes with the name "expense[tag_ids][]". If you take a look at the development log when you submit the form you will see how Rails splits this up. Something like this:

Parameters: {"action" => "create", "controller" => "expenses", { "expense" => { "name" => "foo", "tag_ids" => ["1", "3", "8"] }}}

You can see this is an array of numbers (checked tag ids) inside a hash which is inside another hash. This all gets thrown into the "params" hash which you use in the create/update actions in the controller.

@expense = Expense.new(params[:expense])

If you look back at the parameters you can see the parmas[:expense] is actually a hash. So this is what it's doing:

@expense = Expense.new("name" => "foo", "tag_ids" => ["1", "3", "8"])

When you supply a hash to the "new" method like this, it will set the attributes of Expense. This ends up calling each method:

expense.name = "foo"
expense.tag_ids = ["1", "3", "8"]

This tag_ids method is something that is added when you set the has_and_belongs_to_many association. It basically sets which tags the expense belongs to.

Ceres wrote:

2. @expense.tags.include?(tag) - What is going on here?

It may help to break it up. The @expense.tags method returns an array of tag models that the expense is related to. The "include?" method is defined in Array and returns true/false depending on if the given parameter is inside the array. In other words, this returns true if the expense has that tag, and false if it doesn't. The result is a checked or unchecked checkbox.

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

That was a great explanation. Thank you for that Ryan.

Re: can anyone get this many to many pdf to work ?

This thread is helpful but in my code I would like to be able to choose which checklist to append listings to.  When I use the code: @checklist.update_attributes(params[:checklist]) it saves the listings but overwrites the ones that already exist for that checklist.  I thought Maybe I could solve it by pushing the existing values onto the :checklist hash but I guess my code is wrong.  Any ideas?

 def test_checklist
    @mycheck = params[:checklistsend]
    @name = @mycheck['name']
      @check_id = params[:check_id]
       
    @checklist = Checklist.find(@check_id)   
    @checkname = @checklist.name
   
    @clp = params[:checklist]
    @cls = @checklist.listings.id  #really only want the listing ids here but I don't think this is working
    @clp.push(@cls) #this is not working
   
    if @checklist.update_attributes(@clp)
        flash[:notice] = "Listings were successfully saved to '#{@checkname}'."
        #redirect_to :action => 'show_checklists', :id => @checklist.id
    else
        flash[:notice] = 'There was a problem saving your Checklist. Please try again later.'                     end
end

Re: can anyone get this many to many pdf to work ?

I recommend putting this code in the model instead of doing it in the controller. As you said, my earlier post almost gets you there, but you want it to append to the listings instead of replacing them entirely.

The trick here is actually to give the checkboxes hash a new name:

<% for listing in @listings %>
  <%= check_box_tag 'checklist[append_listing_ids][]', listing.id %>
  <%=h listing.name %><br />
<% end %>

Right now this will cause an error because there's no "append_listing_ids" setter method in the Checklist model. That's no problem because you can make one:

# in Checklist model
def append_listing_ids=(listing_ids)
  listing_ids.each do |listing_id|
    self.listings << Listing.find(listing_id)
  end
end

Hope that helps.

Railscasts - Free Ruby on Rails Screencasts

Re: can anyone get this many to many pdf to work ?

Thanks Ryan,

That works nicely.  Two more questions:

1.  Is there a similar method to delete the selected listings rather than append them?

2.  Is there a way to exclude some fields in params hash when updating?  i.e. 

 @checklist = Checklist.new(params[:checklist, :except => :name])  #this code doesn't work

Thanks!

Re: can anyone get this many to many pdf to work ?

Acutally instead of exluding, all I really need to do is just update one field from the :checklists hash.  I tried:

@checklist.update_attributes(params[:checklist][:append_listing_ids])

but it gives me: undefined method `stringify_keys!'