Dynamic Methods on a Rails Model - ruby-on-rails

I realize this is not necessarily the smartest way to do this, but now my curiosity is active and I am curious how to do it.
I have a model in the Rails project. We'll call it Deal. Per ActiveRecord and all that cool stuff there are columns defined in the database like UPDATED_AT and those become methods on Deal: deal.updated_at => '04/19/1966 3:15am'
Say I wanted to instead have methods that told me the day of the week rather than the whole date and time thing. I realize there are methods ON the DateTime class so I can do
deal.updated_at.day_of_week => 'Monday' (*)
but what if I just wanted
deal.updated_day => 'Monday'
I can write in deal.rb
def update_day
self.updated_at.day_of_week
end
Got it.
But what if I wanted it to ALWAYS have the method available for ANY date column that was added to the model?
I saw define_method out there (some here on StackOverflow). So I understand that. But I would want to call it right after ActiveRecord did its magic, right? So if my Deal model had updated_at, created_at, offered_at and lawsuit_at I would want matching methods for each one. More importantly, if another developer came and added a column called scammed_at I would want scammed_day created along with the scammed_at method.
How would I do that?
Thanks.
(*) Uh, or something like that, I always look that call up.

I guess something like the following should do the trick. In your model:
# looping through all model's columns
self.columns.each do |column|
#if column's name ends with "_at"
if column.name =~ /_at$/
#create method like "udpated_day"
define_method "#{column.name[0..-4]}_day" do
self.send(column.name).day_of_week
end
end
end
But it implies every column has a valid day_of_week method...
Well you get the idea I think. Don't hesitate to ask for details

Related

Rails methods that can work with multiple tables and column names

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.

rails/ActiveRecord: How can I specify a different class for a particular attribute?

In rails 3, I have a database model that has a birthday field, of type date. For various reasons (e.g. a date without a known year), I'd like to have this be of a class other than Date, and/or to mix something in to the Date class for only objects created from this attribute. What's the best way to go about this?
For example, suppose I want to do something like:
class BirthDate < Date
def to_s
case year
when 1 # if I store a date with a null year, it sets it to 1
strftime("%m/%d")
else
super
end
end
end
That class does get me the behavior I want (for the moment; it's entirely possible I'll modify it in future), and I can get my model to give me access to this using something like:
class Person < ActiveRecord::Base
def bday
BirthDate.new(birthday.year, birthday.month, birthday.day)
end
end
And then just change my views to use bday instead of birthday. This seems kind of wasteful, though (having to create a new object from one already created -- never mind that Date doesn't seem to have a "copy constructor" type thing? (Or am I just missing it somewhere?)
Anyway, I would think that perhaps ActiveRecord might provide something that might look like:
class Person < ActiveRecord::Base
class_for :birthday => BirthDate
end
Is there anything like that? I've done a bunch of google searches, and looked through a number of docs, and haven't found it. But since I don't know what it might be called,
(Perhaps it's time to start digging through the source -- I've heard that's a good way to learn more about a platform. Then again, some say otherwise ;))
You can override the getter for the attribute manually:
def birthday
BirthDate.parse(birthday_before_type_cast)
end

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

With Rails 2.x, how do I handle a table with a "valid" column?

I've got a table that includes a column named "valid". This has caused a problem after updating to Rails 2. ActiveRecord is expecting "def valid?" to do validation, not return a boolean value from the database.
How do I work around this problem? Is renaming the column my only option?
As documented elsewhere, there are things you can do, but I'm going to suggest that they're probably going to be more trouble in the long run than biting the bullet and renaming the column.
If your database is not open to other apps, that is - otherwise you're just going to suffer to some extent whatever you do...
Why rename? One of the greatest benefits that we get from Rails is convention over configuration. The "magic", if you will. (Some say that it's actually a bad thing, but go with me one this). If you retain a column named "valid", then nyou're making your models inconsistent: this one needs to work differently from the others and that's bad. Or you could monkey-patch ActiveRecord::Base perhaps, so then all your models work the same but your app no longer follows convention.
From personal experience: I created a column named "user_id" which ActiveRecord, by convention, considered a foreign key (as it does anything ending in "_id"). I coded around it, which I now think was a mistake. Another item on the to-do list...
It's not necessarily wrong to go against Rails conventions: there are plenty of places where you can do so and they're well-documented. On the ActiveRecord side, many are specifically designed to reduce difficulty in connecting to legacy database schemas, for example. Take a good look at the pros and cons, as you're obviously doing, and weigh up your options.
I can prevent the crash by adding the following to my model, but it's not entirely satisfactory:
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'valid?'
super
end
end
Do you need to see the column in your model? If not, overriding ActiveRecord::Base.columns will do the trick...
def self.columns
super.delete_if {|c| c.name == 'valid' }
end
You can access the attribute through the [] notation:
row[:valid] = "foo"
You'll get the DangerousAttributeError if you try to initialize an object like this:
row = MyModel.new :valid => "foo"
To prevent that, you can define an attribute setter for valid, like this:
def valid=(x)
self[:valid] = x
end
The valid? method will still be for row validation. You could define a different question method, like val? to get at the boolean, like this:
def val?
query_attribute('valid')
end
Now you can use row.val? to test the boolean

Best practice for converting integer value column to string representation

Lets say you have a model like the following:
class Stock < ActiveRecord::Base
# Positions
BUY = 1
SELL = 2
end
And in that class as an attribute of type integer called 'position' that can hold any of the above values. What is the Rails best practice for converting those integer values into human readable strings?
a) Use a helper method, but then you're force to make sure that you keep the helper method and model in sync
def stock_position_to_s(position)
case position
when Stock::BUY
'buy'
when Stock::SELL
'sell'
end
''
end
b) Create a method in the model, which sort of breaks a clean MVC approach.
class Stock < ActiveRecord::Base
def position_as_string
...snip
end
end
c) A newer way using the new I18N stuff in Rails 2.2?
Just curious what other people are doing when they have an integer column in the database that needs to be output as a user friendly string.
Thanks,
Kenny
Sounds to me like something that belongs in the views as it is a presentation issue.
If it is used widely, then in a helper method for DRY purposes, and use I18N if you need it.
Try out something like this
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
end
It lets you to save position as an integer, as well as use select helpers easily.
Of course, views are still a problem. You might want to either use helpers or create position_name for this purpose method
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
def position_name
positions.index(position)
end
end
Is there a good reason for the app be converting the integer to the human readable string programmatically?
I would make the positions objects which have a position integer attribute and a name attribute.
Then you can just do
stock.position.name
#HermanD: I think it's a lot better to store the values in an integer column rather than a string column for numerous reasons.
It saves database space.
Easier/faster to index on an integer than a string.
Your not hard coding a human readable string as values in a database. (What happens if the client says that "Buy" should become "Purchase"? Now the UI shows "Purchase" everywhere but you need to keep setting "Buy" in the database.)
So, if you store certain values in the database as integers, then at some point, you're going to need to show them to the user as strings, and I think the only way you can do that is programatically.
You could move this info into another object but, IMHO, I'd say this is overkill. You'd then have to add another database table. Add another 'admin' section for adding, removing and renaming these values and so on. Not to mention that if you had several columns, in different models that needed this behavior, you'd either have to create lots of these objects (ex: stock_positions, stock_actions, transaction_kinds, etc...) or you'd have to design it generically enough to use polymorphic associations. Finally, if the position name is hard coded, then you lose the ability to easy localize it at a later date.
#frankodwyer: I'd have to agree that using a helper method is probably the best way to go. I was hoping their might be a "slicker" way to do this, but it doesn't look like it. For now, I think the best method is to create a new helper module, maybe something like StringsHelper, and stuff a bunch of methods in their for converting model constants to strings. That way I can use all the I18N stuff in the helper to pull out the localized string if I need to in the future. The annoying part is that if someone needs to add a new value to the models column, then they will also have to add a check for that in the helper. Not 100% DRY, but I guess "close enough"...
Thanks to both of you for the input.
Kenny
Why not use the properties of a native data structure? example:
class Stock < ActiveRecord::Base
ACTIONS = [nil,'buy','sell']
end
Then you could grab them using Stock::ACTIONS[1] #=> 'buy' or Stock::ACTIONS[2] #=> 'sell'
or, you could use a hash {:buy => 1, :sell => 2} and access it as Stock::ACTIONS[:buy] #=> 1
you get the idea.
#Derek P. That's the implementation I first went with and while it definitely works, it sort of breaks the MVC metaphor because the model, now has view related info defined in its class. Strings in controllers are one thing, but strings in models (in my opinion) are definitely against the spirit of clean MVC.
It also doesn't really work if you want to start localizing, so while it was the method I originally used, I don't think it's the method for future development (and definitely not in an I18N world.)
Thanks for the input though.
Sincerely,
Kenny
I wrote a plugin that may help a while ago. See this. It lets you define lists and gives you nice methods ending in _str for display purposes.

Resources