I''m a Java refugee, who is a bit of a Ruby and Rails noob, but I''m certainly enjoying myself and have had a bit of success in creating a few RoR bits and pieces.. however, I think I must be missing something. I just get the feeling that, although I''ve read through a couple of good books, there is something quite basic in RoR that I''m just not ''getting''. So, here is a (mildly contrived) example of the sort of thing I''ve been trying to do - maybe some experts out there could metaphorically slap me on the back of the head and say "Duh!" Firstly, I have a simple model: create_table :uris do |t| t.column :loc, :string, :null => false end create_table :links do |t| t.column :uri_id, :integer, :null => false t.column :text, :string, :null => false t.column :color, :string end I''ve then used generate scaffold_resource to create the usual default files, to which, I''ve made the following changes uri.rb: class Uri < ActiveRecord::Base has_many :links def to_s loc end end link.rb: class Link < ActiveRecord::Base belongs_to :uri end link_controller.rb: def update @link = Link.find(params[:id]) @uri = Uri.new(:loc=>params[:uri][:loc]) @link.uri = @uri ... def create @link = Link.new(params[:link]) @uri = Uri.new(:loc=>params[:uri][:loc]) @link.uri = @uri ... # (the rest is the same as the auto-generated code) views/links/new.rhtml: ... <% form_for(:link, :url => links_path) do |f| %> <p>Text: <%= f.text_field :text %></p> <% fields_for :uri do |u| %> <p>URL: <%= u.text_field :loc %></p> <% end %> <p>Color: <%= f.text_field :color %></p> <p><%= submit_tag "Create" %></p> <% end %> ... views/uris/new.rhtml: ... <% form_for(:uri, :url => uris_path) do |f| %> <p>URI Loc: <%= f.text_field :loc %></p> <p><%= submit_tag "Create" %></p> <% end %> ... This seems to be working OK, although I''m fairly sure I''ve not gone about it quite the right way (in terms of the best way of writing the views and controller to manipulate the Uri model and it''s relationship with Link). If anyone has any comments on that, I''m all ears! But anyway, at least it works...! Incidentally, my Uri view *didn''t* work when I originally named the Uri model Url... it failed at the line <% form_for(:url, :url => links_path) do |f| %>. I''m guess some sort of naming conflict.. any ideas what I should have done on that, apart from changing the name of my model? What I''m really struggling with, though, is this: I want to have default values for my attributes. That is, values that a new Link object would default to when it is instantiated. These would show up on the form when a user asks to create a new object. For a while, I tried using the before_create filter, but had no joy until I realised that this inserts the default values if the attributes are empty before saving the record to the database, which is too late for me... So, the most obvious place, I thought, was the initialize methods: link.rb: def initialize super @color="#999999" @text="default text" end This doesn''t work. I''ve no idea why, it looks like it should work. I''m setting the instance variables to a default value, yet when the "new" form is displayed, the fields are blank. :( I played around with a couple more permutations of this, but to no avail... The best that happens is that a) no default values are displayed, and b) I get "wrong number of arguments (1 for 0)" when the create method is called. Bah! My next effort was to add my own reader methods: link.rb: def color @color || "#999999" end def text @text || "default text" end No joy here either. Again, it looks to me like these should work... I don''t understand why they don''t. They system appears to be acting as though these variables aren''t used, and/or these methods aren''t called, but the way I understand everything I''ve read says that they are! I''m *sure* they are - I''ve seen "password" and "password=" methods overwritten in several different examples so that a hashed password can be created and saved to the DB. So, my next thought is that maybe whatever ActiveRecord sets @color to does not evaluate to false, so my code always returns @color, and never "#9999999". But: def color "#999999" end does not work either! Argh! In fact, now the following happens: I try to create a new link. The form is displayed with no defaults. I enter some values, and the object seems to be created properly. However, the value "#999999" is displayed when I use the "show" view, and the value I actually entered is displayed when I display the "edit" view. OK, so IIRC, my next attempt was using something along the lines of <p>Text: <%= f.text_field :text, :value=>"default text" %></p> Which looked promising until I wanted to use the same _partial files for both the edit and create methods... in which case, "default text" shows up even when editing an object that already has a value for that attribute. After much googling, I came across another idea. For some reason not clear to me, this should work: link.rb: def text self[:text] or "default text" end It doesn''t seem to work for me either. I''m not sure why it would, but the blog I read it on seemed to think it did work, and so did the comments on the blog, so I''ve no idea what I''m doing wrong. Ho hum... My next idea is to set these default values in the controller. This seems wrong to me - default values for a model ought to be defined in the model, shouldn''t they? But by now, I just want to get the thing working even if its not where I think it ought to be! so, my next attempt is: link_controller.rb def new @link = Link.new @link.text=''link_controller default text'' @link.color=''#ffffff'' end Yay! That worked. It''s not, IMO, the right place to put the code, but it works, so I''m putting up with it for now... But how do I get a default value for the Uri? I try adding this line to my "new" method: @link.uri = Uri.new(:loc=>''www.example.com/example.html'') It doesn''t work. At this point, I''m all out of ideas, and this is where I decided that my understanding just has a big whole in it that needs filling, so I''ve written this long post! Whatever it is that I''m not ''getting'', I think its causing me other problems too. For example, if have a list of languages, and want to have a select box to assign a language to a link, I''d assume that at some point the "language_id=" method would get called, so I write: def language_id=(lid) self.language=Language.find(lid) end or def language_id=(lid) @language=Language.find(lid) end or def language_id=(lid) language=lid end or any number of permutations of this sort of thing, none of which are wiping the scowl off my face! --- Thanks for taking the time to read down this far in the post, and any advice is much appreciated! -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Hi, After skim reading your post, is your question "How do I set default values for ActiveRecord?". Which I find a bit more concise. If so, a quick Google search indicates that you have merely used the wrong way to access the attributes, you should use their accessor as I don''t believe they are stored in "@" variables. See here: http://made-of-stone.blogspot.com/2007/01/default-values-in-your-models.html Ignore me if I''ve missed the point of your post. Regards, Andrew --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Andrew France wrote:> Hi, > > After skim reading your post, is your question "How do I set default > values for ActiveRecord?". Which I find a bit more concise. > > If so, a quick Google search indicates that you have merely used the > wrong way to access the attributes, you should use their accessor as I > don''t believe they are stored in "@" variables. > > See here: > http://made-of-stone.blogspot.com/2007/01/default-values-in-your-models.html > > Ignore me if I''ve missed the point of your post. > > Regards, > AndrewThanks, that''s helped me a bit.. There were a whole bunch of questions up there, but that was certainly one of them :) I still don''t understand why the things I tried don''t work. Why, for example, does it appear that either implementation of the method: def text @text || "default text" end or def text self[:text] or "default text" end don''t appear to be called by the form builders? If @text isn''t used by ActiveRecord, I''d have thought the first one would at least return the default text, and if ActiveRecord does use a hash, then I''d have thought the second one does work... From your post, I think I''ve made a bit of progress, so thank you for that :) I can understand that I should use accessor methods instead of @ variables. That makes sense and seems like good practice whatever models you are working with, since you can''t relying on the underlying implementation. However... I did this: link.rb: def initialize (params = nil) super self.text="default text from init" unless self.text self.color="#999999" unless self.color end and the "new" form now provides a default for color, but not for text. This just makes me more confused...! And, I still haven''t got this working with the Uri relationship! -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
> def initialize (params = nil) > super > self.text="default text from init" unless self.text > self.color="#999999" unless self.color > end > > and the "new" form now provides a default for color, but not for text. > This just makes me more confused...! And, I still haven''t got this > working with the Uri relationship!I''m not sure if this has any bearing on your particular problem, but "text" may no be a very "safe" column name to use. I have had issues in the past with MySQL when using keywords as column names. It''s best to use column names that are not database specific keywords. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
I''ve put some thought into this and have done some experimenting and thought I would bring up a few points of discussion: It seems to me there are multiple definitions of a "default value." 1. Database/Model level defaults: In relation to a model object it would be values that would define the "natural state" of an attribute. Take for example an attribute access_level on a User object. A natural default state may be a value representing a "regular" user (say 1 = "Regular user"). This type of default value can be provided right in your database migration like: class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.column :created_at, :datetime #<< Notice that Rails will automatically take care of the default value t.column :first_name, :string t.column :last_name, :string t.column :access_level, :integer, :default => 1 #<< 1 = Regular user end end def self.down drop_table :users end end It is also possible (although it seems unnecessary given the above) to set these types of defaults in the model class like: class User < ActiveRecord::Base def initialize(params = nil) super self.access_level ||= 1 end end In other cases the "default value" may actually be related more to a particular view. I recently ran into an example like this: I had a event scheduler that I wanted a default value for the current time to be set on the datatime selection control. In my opinion this is not a model level default value. To create an event, expecting people to subscribe to the event later, it is not the "natural state" of the Event object to have a start_time = Time.now. However, it is convenient to initialize the form''s control to default to the current date and time to make the user''s selection process easier. It just happens that the built-in time widgets do this (I think so anyway). But in any case the initial state of the model object''s start_time attribute to be nil and not Time.now. So assuming that this behavior is not built into the form''s control object Rails provides a convenient way to construct a new object with some attributes initialized that can be set from with the controller like: # GET /people/new def new @person = User.new(access_level => 1) end Maybe some purist MVC guys would say that is breaking the MVC design pattern, and maybe it is, but sometimes the convenience is so great that it''s worth bending the rules a bit. However, this is similar to many "factory constructor" design patterns that I often see in use. It would be more "pure" if the form''s control were extended to provide a default option. Something like: <p> <b>Access Level</b><br /> <%= f.text_field :access_level, :options => {:default => 1 }%> # DON''T do this it''s just made up to illustrate a point </p> --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
I recently ran across the problem of needing to initialize a model''s relation on creation and used the ''faux accessor'' solution because you really shouldn''t be overriding an AR initialize method. Josh outlines the technique here: http://blog.hasmanythrough.com/2007/1/22/using-faux-accessors-to- initialize-values -Michael http://javathehutt.blogspot.com On Feb 2, 2007, at 11:01 AM, Robert Walker wrote:> > I''ve put some thought into this and have done some experimenting and > thought I would bring up a few points of discussion: > > It seems to me there are multiple definitions of a "default value." > > 1. Database/Model level defaults: > In relation to a model object it would be values that would define the > "natural state" of an attribute. Take for example an attribute > access_level on a User object. A natural default state may be a value > representing a "regular" user (say 1 = "Regular user"). > > This type of default value can be provided right in your database > migration like: > > class CreateUsers < ActiveRecord::Migration > def self.up > create_table :users do |t| > t.column :created_at, :datetime #<< Notice that Rails will > automatically take care of the default value > t.column :first_name, :string > t.column :last_name, :string > t.column :access_level, :integer, :default => 1 #<< 1 = Regular > user > end > end > > def self.down > drop_table :users > end > end > > It is also possible (although it seems unnecessary given the above) to > set these types of defaults in the model class like: > > class User < ActiveRecord::Base > def initialize(params = nil) > super > self.access_level ||= 1 > end > end > > In other cases the "default value" may actually be related more to a > particular view. > > I recently ran into an example like this: I had a event scheduler > that I wanted a default value for the current time to be set on the > datatime selection control. In my opinion this is not a model level > default value. To create an event, expecting people to subscribe to > the event later, it is not the "natural state" of the Event object to > have a start_time = Time.now. However, it is convenient to initialize > the form''s control to default to the current date and time to make the > user''s selection process easier. > > It just happens that the built-in time widgets do this (I think so > anyway). But in any case the initial state of the model object''s > start_time attribute to be nil and not Time.now. > > So assuming that this behavior is not built into the form''s control > object Rails provides a convenient way to construct a new object with > some attributes initialized that can be set from with the controller > like: > > # GET /people/new > def new > @person = User.new(access_level => 1) > end > > Maybe some purist MVC guys would say that is breaking the MVC design > pattern, and maybe it is, but sometimes the convenience is so great > that it''s worth bending the rules a bit. However, this is similar to > many "factory constructor" design patterns that I often see in use. > > It would be more "pure" if the form''s control were extended to provide > a default option. > > Something like: > <p> > <b>Access Level</b><br /> > <%= f.text_field :access_level, :options => {:default => 1 }%> # > DON''T do this it''s just made up to illustrate a point > </p> > > > > >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
> def text > self[:text] or "default text" > end > don''t appear to be called by the form builders? If @text isn''t used by > ActiveRecord, I''d have thought the first one would at least return the > default text, and if ActiveRecord does use a hash, then I''d have thought > the second one does work...I think the form builders use the "attribute_before_type_cast" accessor methods. Actually I tried submitting a patch for it: http:// dev.rubyonrails.org/ticket/5427> self.text="default text from init" unless self.text > self.color="#999999" unless self.color > endJust a guess, maybe self.text.empty? Best regards, Andrew --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Andrew France wrote:>> def text >> self[:text] or "default text" >> end >> don''t appear to be called by the form builders? If @text isn''t used by >> ActiveRecord, I''d have thought the first one would at least return the >> default text, and if ActiveRecord does use a hash, then I''d have thought >> the second one does work... > > I think the form builders use the "attribute_before_type_cast" > accessor methods. Actually I tried submitting a patch for it: http:// > dev.rubyonrails.org/ticket/5427Ah, that''s interesting. I wonder why they would do that?>> self.text="default text from init" unless self.text >> self.color="#999999" unless self.color >> end > Just a guess, maybe self.text.empty?That did the trick. Thanks! I did try Robert''s idea of changing the attribute name , but it didn''t work in this case. Thanks for all the contributions above from everyone, I''ve got lots to think about now :) -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Mark Coleman wrote: Dude, don''t feel bad. As a noob last March coming from J2EE-land, I, like you, foolishly thought that ActiveRecord::Base descendants behaved like _normal_ objects. But, alas, they don''t. And as far as I can tell, we all go through the same fire to discover these facts... To summarize for you, and hopefully, the next guy who runs into this: 1)> So, the most obvious place, I thought, was the initialize methods: > link.rb: > def initialize > super > @color="#999999" > @text="default text" > endYou know the answer to this is because you need the (params = nil). So: def initialize(params = nil) super self.color = "#999999" self.text = "default text" end DO not that the reason that the above method works is that initialize only get called on an ActiveRecord object when "new" is invoked, but never after it is saved (on a "find"). The point being that AR::Base descendants are not instantiated like _normal_ Ruby objects (which always call new and hence initialize). But wait, there''s more - the _recommended_ approach to handling attribute initialization is to use "after_initialize". But wait, "after_initialize" gets called after a Obj.new _and_ after a Obj.find, so you have to check and see if it''s a new object so you know when to initialize it (note the if new_record? statement to guard against re-initializing an already saved object): def after_initialize if self.new_record? self.color = "#999999" self.text = "default text" end end But wait, there''s another wrinkle - if you ever _clone_ this object, then after_initialize gets called on the clone target and thus your initialization would overwrite any attribute values in the original object (see Rails trac ticket: http://dev.rubyonrails.org/ticket/7191), so the best way to handle this is: def after_initialize if self.new_record? self.color ||= "#999999" self.text ||= "default text" end end Crazy, huh? 2)> My next effort was to add my own reader methods: > link.rb: > def color > @color || "#999999" > end > > def text > @text || "default text" > end >Form helpers consult the attribute hash in the AR::Base object (@attributes) _directly_, bypassing any overridden getters. The reasoning is that if your overridden getter coerces your attribute value (from the hash) to another type, then it would appear that a text field that you entered as a string would "disappear", and be replaced with whatever the overridden getter returned after doing something to the attribute from the attribute hash. I think an example would be a text field that represents an integer, and the getter method does a to_i on the value from the attribute hash. If you type text into that field, it would turn from "blah" to "0" on a re-render of the page. I don''t 100% get it (maybe 88%), but all I know is that bypassing the getter method is confusing for those of us who expect the ability to overload methods with ease ;). Just one of those things. 3)> OK, so IIRC, my next attempt was using something along the lines of > <p>Text: <%= f.text_field :text, :value=>"default text" %></p> > Which looked promising until I wanted to use the same _partial files for > both the edit and create methods... in which case, "default text" shows > up even when editing an object that already has a value for that > attribute.This behavior makes sense, since the approach above is just hard-coding "default text" as a value. As you now probably know, you would have to lose the :value and just make sure that the field "text" was initialized properly. 4)> After much googling, I came across another idea. For some reason not > clear to me, this should work: > link.rb: > def text > self[:text] or "default text" > endSame as #2, this method is not consulted when the page renders.> But how do I get a default value for the Uri? > I try adding this line to my "new" method: > @link.uri = Uri.new(:loc=>''www.example.com/example.html'') > > It doesn''t work. At this point, I''m all out of ideas, and this is where > I decided that my understanding just has a big whole in it that needs > filling, so I''ve written this long post!Read through all of the API docs. for ActiveRecord::Associations::ClassMethods and this will clear up a lot of confusion about how to properly create/relate association objects. I believe that either @link.build_uri = Uri.new(:loc => ...) OR @link.uris.build(:loc => ...) will work in this case. Wes -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Mild corrections - it''s late: Wes Gamble wrote:> DO note that the reason that the above method works is that initialize > only gets called on an ActiveRecord object once when you invoke new, but > never after it is saved (on a "find"). A find causes an "allocate" followed by some internal AR::Base initialization. A find does not create an object via "new".-- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Wes Gamble wrote:> To summarize for you, and hopefully, the next guy who runs into this: > ... <plus lots more> ...Brilliant! Thanks for posting this, it''s helped me lots! -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Great post... dragging this one to my "saved" folder. :-) b Wes Gamble wrote:> Mark Coleman wrote: > > Dude, don''t feel bad. As a noob last March coming from J2EE-land, I, > like you, foolishly thought that ActiveRecord::Base descendants behaved > like _normal_ objects. But, alas, they don''t. And as far as I can > tell, we all go through the same fire to discover these facts... > > To summarize for you, and hopefully, the next guy who runs into this: > > 1) >> So, the most obvious place, I thought, was the initialize methods: >> link.rb: >> def initialize >> super >> @color="#999999" >> @text="default text" >> end > > You know the answer to this is because you need the (params = nil). So: > > def initialize(params = nil) > super > self.color = "#999999" > self.text = "default text" > end > > DO not that the reason that the above method works is that initialize > only get called on an ActiveRecord object when "new" is invoked, but > never after it is saved (on a "find"). The point being that AR::Base > descendants are not instantiated like _normal_ Ruby objects (which > always call new and hence initialize). > > But wait, there''s more - the _recommended_ approach to handling > attribute initialization is to use "after_initialize". But wait, > "after_initialize" gets called after a Obj.new _and_ after a Obj.find, > so you have to check and see if it''s a new object so you know when to > initialize it (note the if new_record? statement to guard against > re-initializing an already saved object): > > def after_initialize > if self.new_record? > self.color = "#999999" > self.text = "default text" > end > end > > But wait, there''s another wrinkle - if you ever _clone_ this object, > then after_initialize gets called on the clone target and thus your > initialization would overwrite any attribute values in the original > object (see Rails trac ticket: http://dev.rubyonrails.org/ticket/7191), > so the best way to handle this is: > > def after_initialize > if self.new_record? > self.color ||= "#999999" > self.text ||= "default text" > end > end > > Crazy, huh? > > 2) >> My next effort was to add my own reader methods: >> link.rb: >> def color >> @color || "#999999" >> end >> >> def text >> @text || "default text" >> end >> > > Form helpers consult the attribute hash in the AR::Base object > (@attributes) _directly_, bypassing any overridden getters. The > reasoning is that if your overridden getter coerces your attribute value > (from the hash) to another type, then it would appear that a text field > that you entered as a string would "disappear", and be replaced with > whatever the overridden getter returned after doing something to the > attribute from the attribute hash. I think an example would be a text > field that represents an integer, and the getter method does a to_i on > the value from the attribute hash. If you type text into that field, it > would turn from "blah" to "0" on a re-render of the page. I don''t 100% > get it (maybe 88%), but all I know is that bypassing the getter method > is confusing for those of us who expect the ability to overload methods > with ease ;). Just one of those things. > > 3) >> OK, so IIRC, my next attempt was using something along the lines of >> <p>Text: <%= f.text_field :text, :value=>"default text" %></p> >> Which looked promising until I wanted to use the same _partial files for >> both the edit and create methods... in which case, "default text" shows >> up even when editing an object that already has a value for that >> attribute. > > This behavior makes sense, since the approach above is just hard-coding > "default text" as a value. As you now probably know, you would have to > lose the :value and just make sure that the field "text" was initialized > properly. > > 4) >> After much googling, I came across another idea. For some reason not >> clear to me, this should work: >> link.rb: >> def text >> self[:text] or "default text" >> end > > Same as #2, this method is not consulted when the page renders. > >> But how do I get a default value for the Uri? >> I try adding this line to my "new" method: >> @link.uri = Uri.new(:loc=>''www.example.com/example.html'') >> >> It doesn''t work. At this point, I''m all out of ideas, and this is where >> I decided that my understanding just has a big whole in it that needs >> filling, so I''ve written this long post! > > Read through all of the API docs. for > ActiveRecord::Associations::ClassMethods > and this will clear up a lot of confusion about how to properly > create/relate association objects. > > I believe that either > @link.build_uri = Uri.new(:loc => ...) > > OR > > @link.uris.build(:loc => ...) > > will work in this case. > > Wes >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Wes Gamble wrote:> Form helpers consult the attribute hash in the AR::Base object > (@attributes) _directly_, bypassing any overridden getters. The > reasoning is that if your overridden getter coerces your attribute value > (from the hash) to another type, then it would appear that a text field > that you entered as a string would "disappear", and be replaced with > whatever the overridden getter returned after doing something to the > attribute from the attribute hash. I think an example would be a text > field that represents an integer, and the getter method does a to_i on > the value from the attribute hash. If you type text into that field, it > would turn from "blah" to "0" on a re-render of the page. I don''t 100% > get it (maybe 88%), but all I know is that bypassing the getter method > is confusing for those of us who expect the ability to overload methods > with ease ;). Just one of those things.I misspoke here. In fact, it''s not concern about _overridden_ getters but just about the attribute getters (readers) in general. The data type for a given column in the table may not be String, and a cast is performed from String (in the attributes hash) into the appropriate column type when an attribute is set. If the cast would coerce an inappropriate different value (think String -> Integer becomes 0 if the string value is not composed of digits) for the attribute than what was attempted to be set (what was entered into a form), and if you pull the attribute value through the reader, then you might get unexpected attribute values back when submitting an incorrect form entry. So that''s why the reader methods are not consulted to fill in form values. It''s because the attributes hash is basically used as a "form model" object to mediate between a form and the database. You could argue that such a "form model" object should be exposed as it''s own object and data should be marshalled/unmarshalled between it and the AR object, but that kind of interaction is hidden _inside_ the AR object. Wes -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Thanks for digging into these details, I have a similar situation, but I just hacked an rhtml to set defaults when creating the new objects. I quickly refactored after reading through this, but I''m left with a couple of questions. First, when I write... def after_initialize if self.new_record? self.password ||= "password" end end I get blanks when render rhtml value @user.password. However, if I write... def after_initialize if self.new_record? self.password = "password" if self.password.blank? end end I get expected, pre-filled values in the view results. Curious why that would be the case. Is Rails evaluating the ||= differently? Is it defaulting the ruby behavior of evaluations? Also, why is after_initialize recommended to overriding the constructor? Both methods worked for me, although I did refactor from the overriding constructor to the after_initialize - I do like best practices. Is it preferred due to the cloning issue? Regards, /ak Wes Gamble schrieb:> Mark Coleman wrote: > > Dude, don''t feel bad. As a noob last March coming from J2EE-land, I, > like you, foolishly thought that ActiveRecord::Base descendants behaved > like _normal_ objects. But, alas, they don''t. And as far as I can > tell, we all go through the same fire to discover these facts... > > To summarize for you, and hopefully, the next guy who runs into this: > > 1) >> So, the most obvious place, I thought, was the initialize methods: >> link.rb: >> def initialize >> super >> @color="#999999" >> @text="default text" >> end > > You know the answer to this is because you need the (params = nil). So: > > def initialize(params = nil) > super > self.color = "#999999" > self.text = "default text" > end > > DO not that the reason that the above method works is that initialize > only get called on an ActiveRecord object when "new" is invoked, but > never after it is saved (on a "find"). The point being that AR::Base > descendants are not instantiated like _normal_ Ruby objects (which > always call new and hence initialize). > > But wait, there''s more - the _recommended_ approach to handling > attribute initialization is to use "after_initialize". But wait, > "after_initialize" gets called after a Obj.new _and_ after a Obj.find, > so you have to check and see if it''s a new object so you know when to > initialize it (note the if new_record? statement to guard against > re-initializing an already saved object): > > def after_initialize > if self.new_record? > self.color = "#999999" > self.text = "default text" > end > end > > But wait, there''s another wrinkle - if you ever _clone_ this object, > then after_initialize gets called on the clone target and thus your > initialization would overwrite any attribute values in the original > object (see Rails trac ticket: http://dev.rubyonrails.org/ticket/7191), > so the best way to handle this is: > > def after_initialize > if self.new_record? > self.color ||= "#999999" > self.text ||= "default text" > end > end > > Crazy, huh? > > 2) >> My next effort was to add my own reader methods: >> link.rb: >> def color >> @color || "#999999" >> end >> >> def text >> @text || "default text" >> end >> > > Form helpers consult the attribute hash in the AR::Base object > (@attributes) _directly_, bypassing any overridden getters. The > reasoning is that if your overridden getter coerces your attribute value > (from the hash) to another type, then it would appear that a text field > that you entered as a string would "disappear", and be replaced with > whatever the overridden getter returned after doing something to the > attribute from the attribute hash. I think an example would be a text > field that represents an integer, and the getter method does a to_i on > the value from the attribute hash. If you type text into that field, it > would turn from "blah" to "0" on a re-render of the page. I don''t 100% > get it (maybe 88%), but all I know is that bypassing the getter method > is confusing for those of us who expect the ability to overload methods > with ease ;). Just one of those things. > > 3) >> OK, so IIRC, my next attempt was using something along the lines of >> <p>Text: <%= f.text_field :text, :value=>"default text" %></p> >> Which looked promising until I wanted to use the same _partial files for >> both the edit and create methods... in which case, "default text" shows >> up even when editing an object that already has a value for that >> attribute. > > This behavior makes sense, since the approach above is just hard-coding > "default text" as a value. As you now probably know, you would have to > lose the :value and just make sure that the field "text" was initialized > properly. > > 4) >> After much googling, I came across another idea. For some reason not >> clear to me, this should work: >> link.rb: >> def text >> self[:text] or "default text" >> end > > Same as #2, this method is not consulted when the page renders. > >> But how do I get a default value for the Uri? >> I try adding this line to my "new" method: >> @link.uri = Uri.new(:loc=>''www.example.com/example.html'') >> >> It doesn''t work. At this point, I''m all out of ideas, and this is where >> I decided that my understanding just has a big whole in it that needs >> filling, so I''ve written this long post! > > Read through all of the API docs. for > ActiveRecord::Associations::ClassMethods > and this will clear up a lot of confusion about how to properly > create/relate association objects. > > I believe that either > @link.build_uri = Uri.new(:loc => ...) > > OR > > @link.uris.build(:loc => ...) > > will work in this case. > > Wes >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Andy Koch wrote:> Thanks for digging into these details, > > I have a similar situation, but I just hacked an rhtml to set defaults > when creating the new objects. I quickly refactored after reading > through this, but I''m left with a couple of questions. > > First, > > when I write... > > def after_initialize > if self.new_record? > self.password ||= "password" > end > end > > I get blanks when render rhtml value @user.password. > > However, if I write... > > def after_initialize > if self.new_record? > self.password = "password" if self.password.blank? > end > end > > I get expected, pre-filled values in the view results. Curious why that > would be the case. Is Rails evaluating the ||= differently? Is it > defaulting the ruby behavior of evaluations?My guess is that password is defaulted to be an empty string in your DB or somehow so that when you do self.password ||= "password" self.password has a value that happens to be an empty string. If you use script/console to do an interactive command line session in the context of your app., create a new one of these objects and see what the password attribute is. I bet it''s an empty string.> Also, why is after_initialize recommended to overriding the constructor?(someone please correct me on this if I make a mistake, thanks) ActiveRecord doesn''t instantiate objects in the normal "Ruby" way. In a regular Ruby object, when "new" is called, this causes a call to "allocate" followed by the call to "initialize," which is what you would normally override. In AR::Base, "new" is never called directly. Instead, "allocate" is called, and then custom initialization is performed inside of ActiveRecord. The only time that an "initialize" method will be called on an AR::Base descendant is when you do AR_obj.new, never after that, once it''s saved, for example. So, to make a long story short, after_initialize is a hook that AR::Base gives you to allow for behavior that you would normally probably put in an initialize method, but can''t since AR::Base doesn''t invoke it most of the time.> Is it preferred due to the cloning issue?The ||= inside of the new_record? block is necessary to handle cloning, since currently, the cloning occurs as part of the instantiation of the clone, and then after_initialize is called (as usual). If you do straight assignment in after_initialize, you would re-initialize those attributes in your new, cloned, object. And chances are, since you cloned it in the first place, you probably cloned it from an object that has had state change from initialization significantly. ---------------------- I started using after_initialize after slogging through the AR::Base code and seeing that "normal" initialization doesn''t usually occur, so that I wouldn''t be lulled into the idea that AR::Base descendants actually behave like regular Ruby objects. And who knows how the custom initialization sequence will change in future releases of ActiveRecord, so I figured I''d better get on board :). Wes -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---