problem with habtm and save

Hi everybody, here is my problem.

I have a Post model, a Tag model, and they are related to each other by an has_and_belongs_to_many relationship.

Now I run ./script/console:

p=Post.new p.title="Hello" p.save

t=Tag.new t.name="tag1" p.tags = [t]

The result is that a Post record is created and saved, and then also a Tag record is created and saved, together with a record in the join table posts_tags. The question is: why are the tag and the record in the join table saved? Shouldn't they wait for an explicit save? To be more clear, why the last line ( p.tags=[t] ) triggers the creation in the database of a record in the tags table and another in the posts_tags table? Is there a way from stopping this until an explicit save occurs?

I hope someone can answer my question, I'm quite a rails newbie so I need help. Thanks.

Ci. wrote:

Hi everybody, here is my problem.

I have a Post model, a Tag model, and they are related to each other by an has_and_belongs_to_many relationship.

Now I run ./script/console:

p=Post.new p.title="Hello" p.save

t=Tag.new t.name="tag1" p.tags = [t]

The result is that a Post record is created and saved, and then also a Tag record is created and saved, together with a record in the join table posts_tags. The question is: why are the tag and the record in the join table saved? Shouldn't they wait for an explicit save? To be more clear, why the last line ( p.tags=[t] ) triggers the creation in the database of a record in the tags table and another in the posts_tags table? Is there a way from stopping this until an explicit save occurs?

I hope someone can answer my question, I'm quite a rails newbie so I need help. Thanks.

I searched the rails api http://api.rubyonrails.org/ and look into the module ActiveRecord::Associations::ClassMethods And I found the following words that may be helpful

    * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object (the owner of the collection) is not yet stored in the database.     * If saving any of the objects being added to a collection (via push or similar) fails, then push returns false.     * You can add an object to a collection without automatically saving it by using the collection.build method (documented below).     * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.

Please Contact me ,i'm a newbie too... and wanna a friend MSN:ikari.shinji.eva@gmail.com

Yes, I've read that page in the rails API, but.. yet, I haven't found a way to get around this problem.. I was quite enthusiastic about rails until now, but now I'm getting cold. If such an obvious thing is so difficult to do, maybe it's not the right framework to use.. I don't know..

Accoring to the API docs Ikari posted above, you can add a tag to the p model either by using the collection.build method:

> * You can add an object to a collection without automatically saving > it by using the collection.build method (documented below).

or by not saving your p object before adding the tag:

> * Adding an object to a collection (has_many or > has_and_belongs_to_many) automatically saves that object, except if the > parent object (the owner of the collection) is not yet stored in the > database.

The API docs contain the answers right there, the way around your problem is there.

These are not options in my application. I have posts and tags, and I have to associate existing tags to a post, on edit. So the tags already exist, and so does the post, because I'm editing an already saved post. What I'm trying to do is editing a post and the associated tag model in the same form, using a text field for the tag list, which then I convert to the correct tags to feed the model. But it doesn't work as expected.

In that case, if you have an existing post and an existing tag, and your habtm associations are set up correctly, if you have a collection_select in your form named post_tag_id and submit it along with rest of your params, then run

@post.update_attributes!(params[:post])

in your controller, Rails handles the associations all by itself and will update the join table.

The collection select will look something like:

<%= form.collection_select(:post_tag_id,    @tags,    :id,    :name, {}, {}) %>

Note that you'll need to create a @tags object in your edit action that contains all the tags you want as options.

It sounds to me like your talking about categories, ie one per post from a set group, more than tags, which generally mean that a user can come up with as many tags as they'd like with any new post. If it is tags you want, though, then you might want to check out plugins such as Acts as Taggable.

Ci. wrote:

Hi everybody, here is my problem.

I have a Post model, a Tag model, and they are related to each other by an has_and_belongs_to_many relationship.

Now I run ./script/console:

p=Post.new p.title="Hello" p.save

t=Tag.new t.name="tag1" p.tags = [t]

The result is that a Post record is created and saved, and then also a Tag record is created and saved, together with a record in the join table posts_tags. The question is: why are the tag and the record in the join table saved? Shouldn't they wait for an explicit save? To be more clear, why the last line ( p.tags=[t] ) triggers the creation in the database of a record in the tags table and another in the posts_tags table? Is there a way from stopping this until an explicit save occurs?

I hope someone can answer my question, I'm quite a rails newbie so I need help. Thanks.

Er... I've got something useful,I'll take it in an example

first,I create two table Posts & Tags,and association table Posts_Tags,and habtm assciation in models

then,script/console,one command followed by the data table

p = Post.new

=> #<Post:0x3477af4 @new_record=true, @attributes={"sth"=>nil}>

p.save

=> true

t = Tag.new

=> #<Tag:0x3460fc0 @new_record=true, @attributes={"sth"=>nil}>

t.save

=> true

p.tags.send("load_target")

=>

p.tags.target << t

=> [#<Tag:0x3460fc0 @new_record=false, @errors=#<ActiveRecord::Errors:0x345eb30 @errors={}, @base=#<Tag:0x3460fc0 ...>>, @attributes={"id"=>1, "sth"=>nil}, @new_record_before_save=true>]

and the data table ...

select * from posts_tags;

Empty set (0.01 sec)

and the model ...

Ikari Shinji wrote:

Er... I've got something useful,I'll take it in an example

first,I create two table Posts & Tags,and association table Posts_Tags,and habtm assciation in models

then,script/console

p = Post.new

=> #<Post:0x3477af4 @new_record=true, @attributes={"sth"=>nil}>

p.save

=> true

t = Tag.new

=> #<Tag:0x3460fc0 @new_record=true, @attributes={"sth"=>nil}>

t.save

=> true

p.tags.send("load_target")

=>

p.tags.target << t

=> [#<Tag:0x3460fc0 @new_record=false, @errors=#<ActiveRecord::Errors:0x345eb30 @errors={}, @base=#<Tag:0x3460fc0 ...>>, @attributes={"id"=>1, "sth"=>nil}, @new_record_before_save=true>]

and the data table ...

> select * from posts_tags; Empty set (0.01 sec)

and the model ...

y p.tags

--- - &id001 !ruby/object:Tag   attributes:     id: 1     sth:   errors: !ruby/object:ActiveRecord::Errors     base: *id001     errors: {}

  new_record: false   new_record_before_save: true => nil

so , i got it ,i separate model association from data table[bad ENGLISH i know ... ]

when you want to save the associations , following is the answer

p.tags.send("insert_record",t)

=> true

and the data table ... > select * from posts_tags; +----+---------+--------+ > id | post_id | tag_id | +----+---------+--------+ > 1 | 1 | 1 | +----+---------+--------+ 1 row in set (0.00 sec)

SO , I GOT IT, didn't I?

hehe , I read the source code and hope this will help you too. Please give me your MSN,I wanna a coder friend[bad english,i know...]

PS, transaction block is recommended from the source code,in this example...

p.transaction do # Er ... end

^^

"one command followed by the data table" has something wrong ... apologize... i fotgot reload the Post Model so you could catch errors on p.tags[0].errors attributes

p.reload

=> #<Post:0x3477af4 @new_record=false, @tags=nil, @errors=#<ActiveRecord::Errors:0x346bee8 @errors={}, @base=#<Post:0x3477af4 ...>>, @attributes={"id"=>"1", "sth"=>nil}, @new_record_before_save=true>

Thank you for the answer. Really, I don't know if this can be useful in my application.. there seem to be low level calls.. I don't know, anyway I'll play with it and see if something comes out. I'll give you my ICQ (I don't have MSN) if you email me, my email address is ciccio.a [AT] gmail [DOT] com.

@Ci -- this sounds a lot like acts_as_taggable. You might want to look into that plugin so you can go on to other interesting code.

Back on topic, the habtm call creates an association proxy for your Post class. If you ask for an assignment on that proxy, it assumes that you are giving it the new set of associations to be managed. That's why it's saved instantly. Similarly, you can append the collection. You just need to take advantage of what Rails is giving you.

From your description I assume that you are taking the contents of the text field, splitting on whitespaces, and then using each item in the collection as a tag. Is that correct?

So maybe a loop like this will work:

tag_texts = params[:tags].split /\s+/ requested_tags = tag_texts.collect do |tag_text|   Tag.find_by_text(tag_text) || Tag.create(:text=>tag_text) end @post.tags = requested_tags

tag_texts.collect is building up an array of Tag instances by finding the Tag, if it exists, or creating one. You can then pass that array to the tags proxy on your Post and it will take care of the rest. The advantage of this (why Rails is doing instant saves in fact!) is that you don't have to worry about whether or not the collection already contained the tag -- Rails manages it for you.

HTH, AndyV

Thank you for helping me. I finally find quite a clean way to solve my problem.