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
Related
hope you are all well!
I have a general enquiry that I was wondering if some kind soul could help with? it's really a matter of curiosity at the minute but I feel like it could be quite a useful snippet of information in the future.
Is it possible to write a method that can be passed the name of a table and the name of an attribute (column) and perform operations on these? I suppose the main use for such methods would be for keeping code dry when doing repetitive operations on tables.
as an example (though entirely a toy example) suppose I had a method:
def switch(table_name, column_name)
#do some operation on table_name.column_name
end
I have figured out how to access a table by doing something like this:
def model_for_table(table_name)
table_name.to_s.classify.constantize
end
this will take an underscored_lowercase_string and return the table name so that something like model_for_table("registered_user").find(1) though this is unnecessary in situations where the table name can be hard coded
But it does not like model_for_table("registered_user").column_name as used in the example above. is there something analogous to the model_for_table method supplied above to turn a string into an attribute name?
Does anybody know how I could implement this? is it even possible?
Thanks in advance
The problem is that you need an instance of the model you are working on in order to access a column. If you have a RegisteredUser model try doing (in a Rails console) RegisteredUser.id (or any attribute name). It won't work. However, if you did RegisteredUser.first.id (assuming you have one saved) it will work.
So it depends on what you want to accomplish. If your switch method is meant to do something with instances of your model, then this can still work.
def switch(table_name, column_name)
model = model_for_table(table_name)
model.all.each do |model_instance|
puts "model_instance #{column_name} is #{model_instance.send(column_name)}"
end
end
Note: The send method takes in a symbol or a String and executes the method with that name on the instance it was called on. This is a normal Ruby thing, not a Rails thing.
Remember, your model_for_table method is returning back the class, not an instance.
I set up a search model and want to require at least one field is filled out. I found a question that helps with the validation, Rails: how to require at least one field not to be blank. (I tried all answers, but Voyta's seems the best.)
The validation is working, except when I want to redefine the getter/setter via attr_accessor or attr_writer. (I have virtual attributes on the form that need to be apart of the validation.) To figure out what the problem is, I tested with an attribute that is a regular attribute item_length. If I add attr_accessor :item_length, the validation stops to work. So, I guess the question is how to I read an attribute's value without using dot notation. Since the validation uses strings, I can't use the normal way of reading.
Here is a snippet:
if %w(keywords
item_length
item_length_feet
item_length_inches).all?{|attr| read_attribute(attr).blank?}
errors.add(:base, "Please fill out at least one field")
end
Like I said, the virtual attrbutes (length_inches and length_feet) do not work at all, and the normal attribute (length) works except if I redefine the getter/setter.
You should consider read_attribute as a private method for reading Active Record columns. Otherwise you should always use readers directly.
self.read_attribute(:item_length) # does not work
self.item_length # ok
Since you are trying to call this dynamically, you can use generic ruby method public_send to call the specified method
self.public_send(:item_length) # the same as self.item_length
As stated in comment, use send
array.all? {|attr| send(attr).blank?}
For those wondering if send is ok in this case, yes it is: object calls its own instance methods.
But send is a sharp tool, so whenever you use with other objects, ensure you use their public api with public_send
I have a model which has start_at and end_at attributes. In the form for the end-user, I am displaying start_at using the standard datetime_select, but I'd rather not have a second datetime select presented to the user. I'm hoping I can create two fields that represent a duration; one for hours, the other for minutes. My question is, how in my view do I use form helpers to automatically fill in the fields when editing an existing entry. Furthermore, how would I connect that to the model and subsequently save the recording using the real attribute, end_at?
Thanks in advance for any advice you can give!
I have to do this a bunch and i've been doing the following:
Use the FormTagHelper versions of the calls for the field to be handled specially.
In the controller, read the form_tag values out of the params object.
delete the extra values:
params[:examplemodelname].delete :distance if params[:examplemodelname].has_key? :distance
put the 'real' values into the params object (in your example, ends_at)
call ExampleModelName.new(params[:examplemodelname]) or #examplemodelname.update_attributes(params[:examplemodelname]) as per usual.
Wouldn't logic like this be better
suited for the model? Fat model,
skinny controller?
I think this is absolutely right. I despise using a controller for stuff like this. In my opinion, controllers are best used for a few things:
Shuffling data between views and models, ala CRUD operations
Access control (usually with before_filters)
Support to fancy UI actions (multiple select, wizards, etc)
Of course everyone has to find their own balance, but I find "making an exception" for something like this tends to lead to exceptions everywhere. Not to mention the controller logic is harder to test.
To answer your question: you should simply define virtual attributes in your model that help you convert between start_at and a duration. Something like:
# Return the duration in seconds. Will look different if you want to use
# multiparameter assignment (which you definitely should consider)
def duration
(end_at - start_at).seconds
end
# Store the duration as an instance variable. Might need to use multiparameter
# assignment if you use a time_select() in the view.
def duration=(arg)
#duration = arg
end
before_save :set_end_at
def set_end_at
end_at = start_at + #duration.seconds
end
I'd usually set the actual attribute in a before_save to avoid any race conditions from the form assignment. You have no guarantee that start_at will get assigned before or after duration, and that can introduce bugs in your otherwise good logic.
I have a datetime column in a model and I want to display the form for this as separate text_fields for the time instead of the datetime_select helper drop down menus.
I know I will somehow have to validate and recreate the date to go back to the DB, but I am not sure how.
Should I use virtual attributes getter and setter methods? EG...
def virtual_minute=(minute)
...
end
def virtual_hour=(hour)
...
end
Or maybe in a callback?
A bit stucko!
You don't need to write these yourself if you're using an ActiveRecord model. Multi-parameter attributes can be submitted via forms in pieces. As an example look at how the datetime_select method you're not too fond of works:
http://apidock.com/rails/ActionView/Helpers/DateHelper/datetime_select
Internally the various parameters get submitted with order and type information so that they can be properly encoded. You can build off of this functionality by simply naming your fields the same way. It is not too hard to copy a helper method you like and put the adjusted version in your ApplicationHelper module.
The values here show up as parameters named something along the lines of:
model_type[attribute(1i)]
model_type[attribute(2i)]
model_type[attribute(3i)]
These are combined in the correct order, converted as required, and submitted to a constructor for that attributes particular type, such as DateTime.
This avoids having to create your own wrapper methods.
In any case, the easy way to create attributes is to use the attr_accessor method, such as:
class MyModel < ActiveRecord::Base
attr_accessor :some_date
end
That will create the appropriate accessor and mutator methods.
You can use a before_validation trigger to recombine the values in a single pass, or write a wrapper around the date retrieval method to reconsruct it as required.
Virtual attributes are good IMO since they store the user data in an instance variable.
Then in a before_save you construct a Time object assign it to the real variable.
I was trying to split the time from the date (i think this is your question) when I took a look at the source code and found the :ignore_date option:
time_select will also generate 3 input hidden tags, for the actual
year, month and day unless the option :ignore_date is set to
true.
If you set the :ignore_date to true, you must have a date_select on the same method within the form otherwise an
exception will be raised.
In other words, by setting it to true it's possible to split the date and the time.
See time_select and date_select
(it's also possible to use the datetime select with :discard_day, :discard_hour ...)
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.