Understanding "attribute_will_change!" method - ruby-on-rails

I want to overwrite the store_accessor's getter. Which can be found here. Code here:
# File activerecord/lib/active_record/store.rb, line 74
def store_accessor(store_attribute, *keys)
keys = keys.flatten
_store_accessors_module.module_eval do
keys.each do |key|
define_method("#{key}=") do |value|
write_store_attribute(store_attribute, key, value)
end
define_method(key) do
read_store_attribute(store_attribute, key)
end
end
end
self.stored_attributes[store_attribute] ||= []
self.stored_attributes[store_attribute] |= keys
end
I achieved the functionality I was after, however if I were to also overwrite the setter, there is a method that is not clear to me, which is within the write_store_attribute(...) method (found here).
The code is here:
# File activerecord/lib/active_record/store.rb, line 108
def write_store_attribute(store_attribute, key, value)
attribute = initialize_store_attribute(store_attribute)
if value != attribute[key]
send :"#{store_attribute}_will_change!"
attribute[key] = value
end
end
The method I don't understand is "#{whatever_store_att}_will_change!".
If I were to overwrite the setter I would use update_attributes or update_column. Is this method making the assignment attribute[key]=value actually modify the field in the DB, making it equivalent to a update_attributes?

This is part of activemodel's change tracking system, which active record uses to know what it needs to write to the db (if anything).
When you call save on a record, then activerecord creates an update query for all the attributes that it considers changed (and if there are no changed attributes then it does nothing at all).
The accessors that activerecord generates for you handle this for you automatically, but in some cases its useful to tell activerecord that an attribute is dirty and needs changing (for example if you update something in place, circumventing the usual change tracking mechanism).
This is exactly what happens here: when you set a key for the stored attribute - rails modifies the hash (as opposed to assigning a new hash), and so a call to _will_change! is needed to keep the change tracking informed that the underlying attribute will need writing to the db the next time save is called.

Related

How does one access the current id sequence value in the activerecord initializer?

I'm building an activerecord to model a conversation tree, using an array column type to represent the materialized path of the record's place in that tree, using postgres 9.1, rails 4.0, and the pg gem.
What I really want to do is access currval('conversations_id_seq') when I create a new conversation object, so that I can pass in [grandparent_id, parent_id ... current_id] as the array to the object initializer. That way I can specify that this column is not null as a database constraint, and in the event of a parentless conversation, have it still default to [current_id].
The problem I have is getting access to the model's id value before I save it the first time. I could always relax the not null constraint and add an after_create hook, but that feels kludgy. I'm hopeful that there's a way I can grab the value that's getting pushed into #id inside the initializer, before the first save to the database.
EDIT to clarify for the bounty: In an ideal world, there would be a special token I could pass in to the object's create method: Conversation.create(reply_chain: [:lastval]), where the gem took that to mean lastval() in the generated SQL.
something like:
def before_create
self.id=Conversation.connection.execute("SELECT nextval('conversations_id_seq')")
self.path = [... , self.id];
true
end
or use a before insert/update trigger to maintain the path.
You could alias the attribute if you don't need the column in the database.
alias_attribute :current_id, :id
Or you could query for the id when you need it.
def self.last_val
ActiveRecord::Base.connection.execute("SELECT lastval('conversations_id_seq')")
end
def self.next_val
ActiveRecord::Base.connection.execute("SELECT nextval('conversations_id_seq')")
end
Conversation.create(reply_chain: Conversation.next_val)
Using after_save isn't the ugliest of code either.

Help with ruby and def respond_to?(method,*args, &block) in library

I have a Gem that deals with images that get modified. I want to modify it to update the old image but, I need to be able to get the object id.
Right now it uses the following code:
def respond_to?(method,*args, &block)
puts ("++++METHOD #{method.to_s} ARGS #{args} BLOCK #{block}")
args.each do |value|
puts ("ARGS #{value}")
end
but I don't know how to get id from something I pass in nor do I know how to pass the id in, I've tried
#asset.image.s_245_245(:asset_id=>#asset.id) with no success. Args returns nothing. What am I doing wrong?
Update: I am currently reading http://www.simonecarletti.com/blog/2009/09/inside-ruby-on-rails-extract_options-from-arrays/
Update: This too returned blank.
Your question is very unclear. How is the method respond_to? related to your problem with object id?
I am guessing that in reality you wanted to override method_missing, because now you do not call respond_to?, or it is not shown in your examples.
If you have not defined such method, calling image.s_245_245 will trigger method_missing (in the image object) with the parameters you used for respond_to?.
There is a rule, which says that if you use method_missing to handle some calls, then you should also modify respond_to?, and make it returning true when asked for the methods handled by method_missing.
As for object ID, there are two possibilities:
Every object in ruby responds to .object_id (which returns an internal identifier of every object)
ActiveRecord objects respond to .id (which is a primary key in the database).
This is just a side-note, because I suppose that if you start experimenting with method_missing instead of respond_to? you will know which one you want.

Is the Active Record Base update method deprecated?

I'm trying to update many active records at the same time using the :update method and they don't seem to update fine.
#drop_ship_order_line_items = DropShipOrderLineItem.update(params[:drop_ship_order_line_items].keys, params[:drop_ship_order_line_items].values).reject { |dsoli| dsoli.errors.empty? }
params[:drop_ship_order_line_items] returns the following hash:
{"11"=>{"available"=>"1"}, "2"=>{"available"=>"1"}}
But the models don't seem to update correctly...anyone with insides?
AFAIK you can't update models like this on rails, you would have to do it like this:
params[:drop_ship_order_line_items].each do |key,value|
DropShipOrderLineItem.find( key ).update_attributes( value )
end
EDIT
There's probably an attr_protected call somewhere in your code, you should check which attributes are protected or not in there.
If you think you can safely ignore the protection on this specific call, you can use some sending do work out the magic (disclaimer: this is on your own, i'm just showing a possibility):
params[:drop_ship_order_line_items].each do |key,value|
ship = DropShipOrderLineItem.find( key )
value.each do |property,value|
ship.send( "#{property}=", value )
end
ship.save
end
This is going to overcome the attribute protection, but you should make sure this is a safe call and you're not going to burn yourself by doing this.

Is there any built-in way to automatically enforce a type/class on an instance variable in Ruby?

I'm working with Ruby and Rails, so any Rails extension of Ruby should be fine too.
I'm wondering if there's a way to effectively force a type on instance variables (rather, their setters and getters) that's easier than manually defining them.
The class method attr_accessor and the like don't enforce a type. I noticed for instance that Rails' ActiveRecord::Base class does automatic casting on setters. It knows from the database that a particular active record variable is an integer, and setting it with #record.integer_variable = '1' automatically casts the argument and allows further access through #record.integer_variable # => 1.
Is there a way to tap into this?
I could write my own getters and setters as class methods, but if smarter folk have already done the heavy lifting, I'd rather not have to trust myself.
I don't know if there's already something about it, but you can solve this problem with just a few lines of meta-programming:
module EnforceTypes
def attr_accessor_of_type(name, type)
send :define_method, name do
instance_variable_get("##{name}")
end
send :define_method, "#{name}=" do |v|
raise ArgumentException unless v.is_a? type
instance_variable_set("##{name}", v)
end
end
end
Usage:
class MyClass
extend EnforceTypes
attr_accessor_of_type :some_string, String
end
Of course you can make it a little smart by changing the 2nd emitted method, performing some conversions, etc.
Here's a nice reference: http://www.raulparolari.com/Ruby2/attr_accessor
And remember, almost anything that you can do by manually copy-and-pasting lots of code, can be solved with meta-programming.

Rename ActiveResource properties

I am consuming JSON data from a third party API, doing a little bit of processing on that data and then sending the models to the client as JSON. The keys for the incoming data are not named very well. Some of them are acronyms, some just seem to be random characters. For example:
{
aikd: "some value"
lrdf: 1 // I guess this is the ID
}
I am creating a rails ActiveResource model to wrap this resource, but would not like to access these properties through model.lrdf as its not obvious what lrdf really is! Instead, I would like some way to alias these properties to another property that is named better. Something so that I can say model.id = 1 and have that automatically set lrdf to 1 or puts model.id and have that automatically return 1. Also, when I call model.to_json to send the model to the client, I dont want my javascript to have to understand these odd naming conventions.
I tried
alias id lrdf
but that gave me an error saying method lrdf did not exist.
The other option is to just wrap the properties:
def id
lrdf
end
This works, but when I call model.to_json, I see lrdf as the keys again.
Has anyone done anything like this before? What do you recommend?
Have you tried with some before_save magic? Maybe you could define attr_accessible :ldrf, and then, in your before_save filter, assign ldrf to your id field. Haven't tried it, but I think it should works.
attr_accessible :ldrf
before_save :map_attributes
protected
def map_attributes
{:ldrf=>:id}.each do |key, value|
self.send("#{value}=", self.send(key))
end
end
Let me know!
You could try creating a formatter module based on ActiveResource::Formats::JsonFormat and override decode(). If you had to update the data, you'd have to override encode() also. Look at your local gems/activeresource-N.N.N/lib/active_resource/formats/json_format.rb to see what the original json formatter does.
If your model's name is Model and your formatter is CleanupFormatter, just do Model.format = CleanupFormatter.
module CleanupFormatter
include ::ActiveResource::Formats::JsonFormat
extend self
# Set a constant for the mapping.
# I'm pretty sure these should be strings. If not, try symbols.
MAP = [['lrdf', 'id']]
def decode(json)
orig_hash = super
new_hash = {}
MAP.each {|old_name, new_name| new_hash[new_name] = orig_hash.delete(old_name) }
# Comment the next line if you don't want to carry over fields missing from MAP
new_hash.merge!(orig_hash)
new_hash
end
end
This doesn't involve aliasing as you asked, but I think it helps to isolate the gibberish names from your model, which would never have to know those original names existed. And "to_json" will display the readable names.

Resources