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.