Existing Rails model without fetching it from the database - ruby-on-rails

Does anyone know if its possible to create a model instance and apply the ID and any other attributes without having to load it from the database? I tried doing this, but the associations are not fetched from the database :( Any ideas?
EDIT
What I want to accomplish is simply this:
Fetch an existing record from the database.
Store as "hashed" output of the record into redis or some other memory store.
Next time when that record is fetched, fetch the cached store first and if it is not found then goto step 1.
If there is a cache hit, then load all the cached attributes into that model and make that model instance behave as if it were a model fetched from the database with a finite set of columns.
This is where I am stuck, what I've been doing is creating a Model.new object and setting each of the params manually. This works, but it treats the instantiated model object as a new record. There has got to be an intermediate subroutine in ActiveRecord that does the attribute setting.

I solved the problem by doing the following.
Create a new model class which extends the model class that I want to have cached into memory.
Set the table_name of the new class to the same one as the parent class.
Create a new initialize method, call the super method in it, and then allow a parameter of that method to allow for a hash variable containing all the properties of the parent class.
Overload the method new_record? and set that to false so that the associations work.
Here's my code:
class Session < User
self.table_name = 'users'
METHODS = [:id, :username] # all the columns that you wish to have in the memory hash
METHODS.each do |method|
attr_accessor method
end
def initialize(data)
super({})
if data.is_a?(User)
user = data
data = {}
METHODS.each do |key|
data[key] = user.send(key)
end
else
data = JSON.parse(data)
end
data.each do |key,value|
key = key.to_s
self.send(key+'=',value)
end
end
def new_record?
false
end
end

The memcached gem will allow you to shove arbitrary Ruby objects into it. This should all get handled for you transparently, if you're using it.
Otherwise, take a look at ActiveRecord::Base#instantiate to see how it's done normally. You're going to have to trace through a bunch of rails stack, but that's what you get for attempting such hackery!

Related

Use regular attribute assign and save or use update_attribute?

I recently 'discovered' the update_attribute method. So, I started changing sequences like
self.attribute = "foo"; save
in model or controller methods by
self.update_attribute(:attribute, "foo")
Now, the more I'm doing this, the more I'm wondering whether this is "good practice", and whether this method was intended to be used this way.
Any input from the "pro's" on this?
I would suggest using update_attribute for flags or any update operation that does not need validations since it does not fire validations. From rails documentation we can read:
Updates a single attribute and saves the record without going through
the normal validation procedure. This is especially useful for boolean
flags on existing records. The regular update_attribute method in Base
is replaced with this when the validations module is mixed in, which
it is by default.
Whereas update_attributes does:
Updates all the attributes from the passed-in Hash and saves the
record. If the object is invalid, the saving will fail and false will
be returned.
Let's look at the code now:
def update_attribute(name, value)
send(name.to_s + '=', value)
save(false)
end
def update_attributes(attributes)
self.attributes = attributes
save
end
It's always better to use update_attribute, or update_attributes if you need to update a single instance with simple data, as you can read "UPDATE" and know that you are "UPDATING".
You must know also that there is a method called update_column, that does 'kinda' the same stuff, but, update_column does NOT update the updated_at timestamp on the database.
Also, if you need to edit a large amount of instances/rows in the database with the same value, you have a method called update_all. Here is an example
#instances = Instance.all
#instances.update_all(:attribute, value)
and that will update all the attributes of that table. You will find this usefull after doing werid migrations.
Besides all of this, you can always use the 'save' way, I strongly recomend this when you have to calculate a lot of data to update a single instance. Here is an example:
#BAD
def updater_method
foo = Bar.first
foo.update_attributes(attr_one: some_calcule_method, attr_two: some_other_calcule_method, attr_three: some_more_calcule_method)
end
#GOOD
def saver_method
foo = Bar.first
foo.attr_one = some_calcule_method
foo.attr_two = some_other_calcule_method
foo.attr_three = some_more_calcule_method
etc
foo.save!
end
This will help you in debbuging, so if any method fails, you can see it clearly, with the line number and all that stuff.
Regards, Lucas.

Can't pull objects updated attributes from db

After updating object attributes in my db, I can't seem to retrieve the values back in a subsequent method call. At the point I attempt to retrieve the data, I've confirmed the attributes have updated in my db.
def task
update_objects(data)
retrieve_data
end
def update_objects(data)
data.each do |item|
keyword = self.keywords.find_by_description(item.keyword)
keyword.update_attributes(:total_value => item.totalValue.to_f, :avg_revenue_per_transaction => item.revenuePerTransaction.to_f)
end
end
def retrieve_data
keywords = self.keywords # The updated attributes in keywords are nil
# Do stuff with keywords
end
This is because the keywords collection has probably already loaded before you call retrieve_data. However, calling find_by_description doesn't make use of the loaded collection (since it's using the query API) and fetches a new object from the database directly which you are updating. The original loaded collection doesn't know about this. There are several ways to fix this:
You can call reload to refresh the collection from the database:
def retrieve_data
keywords = self.keywords(true)
end
Or don't use the query API, but rather use the collection itself:
keyword = self.keywords.detect{|k| k.description == item.keyword}
Or simply avoid loading the collection in advance.

ActiveRecord::Base Class Not Mutable?

I have a class I've extended from ActiveRecord::Base...
class Profile < ActiveRecord::Base
and I collect the records from it like so...
records = #profile.all
which works fine, but it doesn't seem that I can successfully Update the attributes. I don't want to save them back to the database, just modify them before I export them as JSON. My question is, why can't I update these? I'm doing the following (converting date formats before exporting):
records.collect! { |record|
unless record.term_start_date.nil?
record.term_start_date = Date.parse(record.term_start_date.to_s).strftime('%Y,%m,%d')
end
unless record.term_end_date.nil?
record.term_end_date = Date.parse(record.term_end_date.to_s).strftime('%Y,%m,%d')
end
record
}
At first I had just been doing this in a do each loop, but tried collect! to see if it would fix things, but no difference. What am I missing?
P.S. - I tried this in irb on one record and got the same results.
I suggest a different way to solve the problem, that keeps the logic encapsulated in the class itself.
Override the as_json instance method in your Profile class.
def as_json(options={})
attrs = super(options)
unless attrs['term_start_date'].nil?
attrs['term_start_date'] = Date.parse(attrs['term_start_date'].to_s).strftime('%Y,%m,%d')
end
unless attrs['term_end_date'].nil?
attrs['term_end_date'] = Date.parse(attrs['term_end_date'].to_s).strftime('%Y,%m,%d')
end
attrs
end
Now when you render the records to json, they'll automatically use this logic to generate the intermediate hash. You also don't run the risk of accidentally saving the formatted dates to the database.
You can also set up your own custom option name in the case that you don't want the formatting logic.
This blog post explains in more detail.
Try to add record.save! before record.
Actually, by using collect!, you just modifying records array, but to save modified record to database you should use save or save! (which raises exception if saving failed) on every record.

why does klass.create(donor) always create a new donor while donors_controller#create does not

When I go to my donors_controller#create from a form in my view, it will update a record if it matches on ID or the collection of columns in my find_by_*
But if I call that create from a different controller, it always creates new records.
My donors_controller has a create method with:
def create
# need to find donor by id if given, else use find_or_create_by_blahblahblah
unless #donor = Donor.find_by_id(params[:donor][:id])
#donor = Donor.find_or_initialize_by_company_and_prefix1_and_first_name1_and_last_name1_and_address1(params[:donor])
end
if #donor.new_record?
...
else
...
end
end
My other controller has :
class_name = 'Donor'
klass = ActiveRecord.const_get(class_name)
... code to populate myarray with appropriate values
klass.create(myarray)
I am pretty sure myarray is populated with the necessary params since it creates valid records with everything in the right place. I can even run the same record through more than once and it creates duplicate (except for the Donor.id of course) records.
What am I doing wrong here?
I noticed I could do this in my other controller and it works, but why can't I call the create from the donors_controller and have it work without always creating a new record?
#klass.create(myarray)
#mydonor = klass.find_or_initialize_by_company_and_prefix1_and_first_name1_and_last_name1_and_address1(myarray)
#mydonor.save
your question is very unclear and hard to follow, so i'm not sure my answer will match your needs. It seems you're confusing a controller#create method with a model#create method.
On the model : it is a class method
create, as its name implies, instantiates a new object of the Donor class and calls save on it:
#donor = Donor.create
# is the same thing as
#donor = Donor.new
#donor.save
Active Record uses new_record? to determine if it should perform an insert or an update on the db when you call save on an instanciated record. So with create it will always be an insert since the record is inevitably new ; but if you call save on a record retrieved from the database, it will be updated.
On a controller : it is an instance method
... but does not directly manages persistence, that's the role of the model. It is an action for this controller ; it is called create for the sake of RESTful naming conventions. create is called on a controller instance when a POST request is routed to it ; its purpose is to manage that request, which often (but not always) means to create records using the appropriate model, using YourModelName.new or YourModelName.create.
so it is absolutely possible (but not advisable) to do:
class DonorsController < ApplicationController
def create
# only works well if params[:donor][:id] is nil,
# will raise an error if a record with the
# same id exists in the database
#donor = Donor.create(params[:donor])
end
end
as it is generally needed to perform several operations before saving your record, and to check if save is successful, the process is broken down :
class DonorsController < ApplicationController
def create
#donor = Donor.new(params[:donor])
# perform operations on #donor (instance of Donor class)
if #donor.save # save returns true if successfully performed, false either
# all is fine
else
# #donor could not be saved
end
end
end

What is the best way to store and retrieve marshaled object as attribute in db via Active Record

I have gateway_response object that represents a high level ActiveMerchant gateway response. I'd like to hang on to this object in case I need it for any issues in the future.
I'd like to store it in the DB, and have marshaled it as follows. I've overwritten the getter/setter methods to marshal on assignment and unmarshal on retrieval. This seems to work, but I imagine Active Record has a leaner way to do this:
def gateway_response=(r)
write_attribute(:gateway_response, Marshal.dump(r))
end
def gateway_response
Marshal.load(read_attribute(:gateway_response))
end
Use the serialize method.
class Order
# add a text column called gateway_response in the `orders` table.
serialize :gateway_response
end
Now:
order.gateway_response = r
order.save
order.gateway_response # response object

Resources