Building dynamic fields using ActiveRecord::Serialization.to_xml - ruby-on-rails

I'm curious about people's experiences using AR's to_xml() to build non-entity fields (as in, not an attribute of the model you are serializing, but perhaps, utilizing the attributes in the process) from a controller.
to_xml seems to supply a few options for doing this.
One is by passing in references to methods on the object being acted on: during the serialization process, these methods are invoked and their results are added to the generated document. I'd like to avoid this path because some of the generated data, while depending on the object's attributes, could be outside of the scope of the model itself -- e.g., building a URL to a particular items "show" action. Plus, it requires too much forethought. I'd like to just be able to change the resultant document by tweaking the to_xml code from the controller. I don't want the hassle of having to declare a method in the object as well.
The same goes for overriding to_xml in each object.
The other two options seem to fit the bill a little better: one is by passing in procs in the serialization options that generate these fields, and the other is by passing in a block that will yielded to after serialization the objects attributes. These provide the kind of at-the-point-of-invocation customizing that I'm looking for, and in addition, their declarations bind the scope to the controller so that they have access to the same stuff that the controller does, but these methods seem critically limited: AFAICT they contain no reference to the object being serialized. They contain references to the builder object, which, sure I guess you could parse within the block/proc and find the attributes that have already been serialized and use them, but that's a harangue, or at least uneasy and suboptimal.
Correct me if I'm wrong here, but what is the point of having procs/blocks available when serializing one or more objects if you have to access to the object itself.
Anyway, please tell me how I'm wrong, because it seems like I must be overlooking something here.
Oh and yeah, I know that I could write my own view. I'm trying to leverage respond_to and to_xml to achieve minimal extra files/lines. (Though, that is what I resorted to when I couldn't figure out how to do this with AR's serialization.)
**EDIT 3.29.09 -- I just submitted a patch for this to Rails. If you're interested, show some support :) https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2373-record-sensitive-procs-for-to_xml

Actually the Proc is passed the same options hash (minus the procs option) you passed into to_xml. So you can pass in any extra objects the Proc needs to do it's job:
proc = Proc.new {|options| options[:builder].tag!('reverse-name', options[:object].name.reverse)}
object.to_xml :object => object, :procs => [ proc ]
Since you're getting the proc is getting the same options to_xml is, this is allows you to pass in whatever options you need.

Woo! My patch to handle this scenario was accepted: http://github.com/rails/rails/commit/c39151a84768397d3bb025c6e8f877eac59ebbf9 It's a part of ActiveModel now, and I'm not exactly sure what the release schedule for that is; I'm thinking Rails 3.

Related

attr_accessor for a date and problems using select_date

I'm trying to use attr_accessor for a date which normally works fine except when I attempt to use it with the select_date helper method.
Looking at the code behind the helper method I'm guessing it looks for the table column with date type. And in this case since there is no table it's not handling it correctly and I get:
ActiveRecord::MultiparameterAssignmentErrors
"search"=>{"number_of_days"=>"3",
"searchable_id"=>"6933",
"startdate(1i)"=>"2011",
"startdate(2i)"=>"2",
"startdate(3i)"=>"11"}}
Is there a way around this? Or do I need to create some kind of before filter in the controller? I'd prefer doing it on the model level, but I'm not sure how to handle this case? An attr_accessor for each seems a bit over kill. Anyone else have an elegant solution?
attr_accessor fields don't usually get saved when you save/update to the model. How are you updating the model?
Also, you can convert the startdate params to a date object like this :
#start_date = Date.civil(params[:search][:"startdate(1i)"].to_i,params[:search][:"startdate(2i)"].to_i,params[:search][:"startdate(3i)"].to_i)
Check here
select_date is for building the dropdowns which are not associated with a model field (with the idea that you can then pick them up on the other side and do what you want with them). I assume you're meaning date_select which does run off the model?
In any case, as far as I know, long story short, there's no nice and pretty way to get this to work. It's not because of the way the helper works, but because of the way that active record deals with these attributes split into multiple parameters.
In a bit more detail if you're interested, the reason why this doesn't work easily is because when Active Record is dealing with the params you've passed in, it goes through execute_callstack_for_multiparameter_attributes which interprets the keys which have been split into the "date(1i)" style, and mungs them into the applicable class which they should be (a date or time object). The way it works out whether it should create a date or time is by checking it against the type of the attribute (see here), but since an your 'startdate' attribute isn't bound to a particular type, it doesn't get treated as a date or datetime column in the db would.
I think I would deal with it similarly to #Phyo-Wai-Win, but use select_date to set a different param, outside of the 'search' namespace which you then pass into the model as appropriate in the controller. This way, it's not much work, and it means you're not messing with the way you initialize the record or what attributes it expects.
Coming in way late, but in case anyone else stumbles by, the answer for modern rails lies in include ActiveRecord::AttributeAssignment in your model.
This answer did it for me.
I'm a little late here, but I just came across this problem and did not like the top answer. I found a method in the ActiveRecord source called extract_callstack_for_multiparameter_attributes (this is different than the method idlefingers mentioned)
I have the following method in my model. I am calling this method manually but you could probably override update_attributes to run it automatically when you save from the controller. The params argument is actually params[:my_model] from the controller.
attr_accessor :submit_from, :submit_to
def set_dates(params)
dates = extract_callstack_for_multiparameter_attributes(params)
dates.each_pair do |field, date_array|
send "#{field}=", Date.new(*date_array)
end
end

If we cache params into a local var in an action, will it help or its the same?

So we run a code quality tool called reek once in a while as part of our project. The tool basically looks for code smells and reports them. Here, we observed that we get "Duplication" smell every time we try to access a key in params more than once (As if we are making a method-call twice with same parameters or we are duplicating an if condition etc). However, params is just a Hash, right? Other hashes don't get duplication smell when their keys are accessed more than once.
Why is this so? What are params exactly? Does it make sense to cache params in a local variable then use them? Will it help or its the same? Or is there something wrong with the tool? Help!
With the current version it's best to run Reek only on your app/models folder, because it raises false positives against views and controllers.
params is a kind of DTO (data transfer object) close to the system boundary, and so its characteristics should be different than regular code. But Reek doesn't know that (yet). I plan to improve Reek in the near future so that it plays better with Rails. For now though, your best bet is to restrict it to looking at app/models (and maybe app/helpers and lib).
params is a method call that does a #params ||= #request.params
It might be that it thinks params is a complicated method, so it wants you to try and cache it in a variable, but, dont think that would be worth it especially since it is memoized (based on my rack_process.rb from Rails 2.2)
params[:foo] is a method-call to Hash#[], so reek is correct. I'm not familiar with reek, so I can't tell why other Hash accesses don't get counted the same. Hash#[] should be fast enough that you don't need to store it in a local variable unless you're in a very performance critical part of your code.
The only difference between the params Hash and a regular Hash is that it uses with_indifferent_access, meaning you can access any key with a String or a Symbol.
I believe every time you call params, there is an initialization step which generates method calls, i suppose you can try creating a params and checking number of calls.
this could be blind guess. :-)

How to iterate ActiveRecord Attributes, including attr_accessor methods

I've looked everywhere for an elegant solution. The essential problem seems to be that ActiveRecord attributes that map to database columns are handled completely differently in ActiveRecord::Base than attr_accessor methods.
I would like to do something like:
model.attribute_names.each do |name|
# do stuff
end
in a way that also includes attr_accessor fields, but not any other instance methods. I know this in not built-in, but what is the most elegant way to do it?
You can't really solve this. You can approximate a hack, but it's not something that will ever work nicely.
model.attribute_names should get you all the ActiveRecord ones, but the attr_accessor fields are not fields. They are just ordinary ruby methods, and the only way to get them is with model.instance_methods.
Idea 1
You could do model.attribute_names + model.instance_methods, but then you'd have to filter out all your other normal ruby methods initialize, save, etc which would be impractical.
To help filter the instance_methods you could match them up against model.instance_variables (you'd have to account for the # sign in the instance variables manually), but the problem with this is that instance variables don't actually exist at all until they are first assigned.
Idea 2
In your environment.rb, before anything else ever gets loaded, define your own self.attr_accessor in ActiveRecord::Base. This could then wrap the underlying attr_accessor but also save the attribute names to a private list. Then you'd be able to pull out of this list later on. However I'd advise against this... monkey-patching core language facilities like attr_accessor is guaranteed to bring you a lot of pain.
Idea 3
Define your own custom_attr_accessor in ActiveRecord::Base, which does the same thing as Idea 2, and use it in your code where you want to be able to retrieve the attribute names. This would be safe as you won't be clobbering the built-in attr_accessor method any more, but you'll have to change all your code to use custom_attr_accessor where neccessary
I guess in summary, what are you trying to do that needs to know about all the attr_accessor fields? Try look at your problem from a different angle if you can.
I came here looking to do the same thing, and found out it was the wrong approach altogether thanks to Orion's answer.
Incase anyone else's use case is similar to mine, here's my solution. I was using attr_accessor to add extra properties to the models after querying them from ActiveRecord. I then wanted to output the results as JSON etc.
A better solution is to first convert the Models from ActiveRecord into regular hashes, and then add the attr_accessor properties as regular hash keys.
Example:
model_hash = model_from_activerecord.attributes.to_options
model_hash[:key] = value
The solution I came up with for myself builds upon Orion Edwards' answer.
The code:
klass_attributes = klass.column_names + klass.instance_methods(false).
map(&:to_s).keep_if{|a| a.include?('=')}.map{|a| a.sub('=', '')}
The breakdown:
klass.instance_methods(false) brings back only instance methods, and not inherited methods.
map(&:to_s) converts the array of symbols into an array of strings so we can use the include? method. Also needed to merge with array of strings returned by column_names.
keep_if{|a| a.include?('=')} will remove all strings within the array that do not have an equals sign. This was important, since I noticed that attr_accessor attributes all had '='. Ex: 'app_url' vs 'login_url='
map{|a| a.sub('=', '')} is the final piece that then removes the '=' from each string in the array.
You end up with an array of strings that represent attributes, including attr_accessor attributes.

constant values in Rails

I have some data that I want to store somewhere in my Rails app because I use it for generating form fields, checking a submitted form to ensure its values are valid, etc. Basically, I want the data in one location because I make use of it in several places.
Previously, I was defining an initialize method in my controller and initializing instance variables within that method, e.g. #graph_types = ['bar', 'line']. This seemed a bad idea because that's really all initialize was being used for (initializing those values) and the instance variables could be changed later, which I don't want.
Now, I define constants outside of any method in my controller, right up at the top after my filters, and I freeze them, e.g. GraphTypes = ['bar', 'line'].freeze.
I didn't want to store such data in a config file because then I would have to keep track of an extra file, read in the file and parse it, etc. I didn't want to store this data in the database because that seems like overkill; I don't need to do any crazy LEFT OUTER JOIN-type queries combining available graph types with another of my constants, say Themes = ['Keynote', 'Odeo', '37 Signals', 'Rails Keynote'].freeze. I didn't want to store the data in environment.rb because this data only pertains to a particular controller.
Considering all this, am I going about this 'the Ruby way'?
For constants that don't really belong anywhere else I have a StaticData class.
class StaticData
GRAPH_TYPES = ['bar', 'line']
SOMETHING_ELSE = ['A', 'B']
end
Then I get at it with
StaticData::GRAPH_TYPES
The same answer I wrote previously to a similar question applies and posting as this answer still comes up in search results.
Putting a constant in the controller makes some sense as the constant pertains directly to it. Constants should otherwise be put in the dedicated initializer file: Rails.root/config/initializers/constants.rb.
As per the comment listed in application.rb:
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded
This is still valid as of Rails 3.
I believe what you are currently doing is fine; you said the data only pertains to one controller, and therefore that's where it belongs. If it was needed for multiple controllers, or if they were more complex than constant values, other approaches may make sense.
Yes, what you are doing is fine. It's more idiomatic Ruby to call your constant GRAPH_TYPES though.
Incidentally, I would avoid defining initialize in your controllers. Seems like it could lead to trouble.
I would agree some what with IDBD and paradisepete. Using constants in the model would be the best way to go so that the controller is skinny and the model fat. see Rails view tips
For example if you had a metrics controller linked to a metric model. In the metric model
class Metric < ActiveRecord::Base
GRAPHTYPES = ['bar', 'line']
Then in the view you could do something like
f.select :graph_type, Metric::GRAPHTYPES
If you are generating forms that are related to some resource then it will be good variant to store it in the models. You don't need to store it in DB because it can be simple class or instance variables/methods.
The same idea is for validation. If you are validating resources/model instances then it will be reasonable choice to store validation parameters inside model class.
Anyways, it will be much closer to the 'thick model and thin controller' pattern then any of the variants you mentioned.

The differences between .build, .create, and .create! and when should they be used?

So I've been seeing people using .build, .create, and .create! within their controllers more and more lately. What's the difference from just using .new and passing the param'd object and then .save? Are there pros and cons? Does using these other methods offer benefits?
There are a couple differences, but they're not big:
.create is equivalent to .new followed by .save. It's just more succinct.
.create! is equivalent to .new followed by .save! (throws an error if saving fails). It's also just a wee bit shorter
I think .build is mostly an alias for .new. It works one way in Rails 3 and another way in Rails < 3.x
The most important part, however, is that these methods can be called through an association (has_many, etc.) to automatically link the two models.
Although it is correct that create calls new and then save there is a big difference between the two alternatives in their return values.
Save returns either true or false depending on whether the object was saved successfully to the database or not. This can then be used for flow control as per the first example in the question above.
Create will return the model regardless of whether the object was saved or not. This has implications for the code above in that the top branch of the if statement will always be executed even if the object fails validations and is not saved.
If you use create with branching logic you are at risk of silent failures which is not the case if you use new + save.
create! doesn't suffer from the same issue as it raises and exception if the record is invalid.
The create alternative can be useful in controllers where respond_with is used for API (JSON/XML) responses. In this case the existence of errors on the object will cause the errors to be returned in the response with a status of unprocessable_entity, which is exactly what you want from an API.
I would always use the new + save option for html, especially if you are relying on the return value for flow control.
#create is shorter version of new and save.
#create! is throwing exception if validation was not positive.
I'd second the above answers. Plus for create, one cannot pass false as an argument which you can do with save. Passing false as an argument will skip all rails validations

Resources