I'm having trouble creating a new model row in the database using ActiveRecord in a Sinatra app I'm developing. The object in question is being created without any errors (using save!, no exceptions are raised), but most of the data I specify for the save is not present.
class ProjectMeta < ActiveRecord::Base
attr_accessor :completion_ratio, :num_stories, :num_completed_stories, :original_target_date, :current_target_date
...
def self.create_from_project(project)
meta = ProjectMeta.new
meta.project_id = project.id
meta.num_stories = project.num_stories
meta.num_completed_stories = project.num_completed_stories
meta.completion_ratio = ProjectMeta.calculate_ratio(project.num_completed_stories, project.num_stories)
meta.current_target_date = project.current_target_date
meta.save!
meta
end
...
end
All inspections on the data from the project object I'm sending as well as the new meta object I'm creating show that the data is present. But when I do a meta.inspect before and after the save, it shows that all the data (aside from project_id) is in it's default state (zeroes). I've also checked meta.errors.nil? and sure enough, there aren't any errors after the save.
What is most puzzling is that if I turn around and get a new meta instance with that project_id and put the data in, it saves no problem to the db.
This is frustrating me because I've built several sites in Rails and Sinatra with ActiveRecord. This one issue is completely perplexing me. Can anyone tell me what I'm doing wrong?
Here is how it works
On first access to model, columns from corresponding database table are retrieved and stored inside model data. This information can be retrieved through ::columns class method.
When you access some model's attribute, Ruby doesn't find corresponding method in class and launches #method_missing method. That method inspects model's ::columns to check if corresponding column exists. If so, it creates an accessors for that column so that next time you access that model's attribute, an accessor method will be called directly, without need to call #method_missing (the later is slower).
The accessors look like this:
def my_attribute
read_attribute(:my_attribute)
end
def my_attribute=(value)
write_attribute(:my_attribute, value)
end
For #read_attribute and #write_attribute methods there is a shortcut: #[] and #[]=. If for some reason you will need to access underlying data directly (e.g. do some data conversion), you can write them short:
def my_attribute
self[:my_attribute]
end
def my_attribute=(value)
self[:my_attribute] = value
end
Model has a special accessor -- #attributes -- which returns a "column_name => value" Hash.
NOTE: the data for each column is stored in a special Hash instance inside your model instance, not in "#column_name" instance variables. When you define accessors with #attr_accessor, you block the usual way of defining attribute accessors via #method_missing. Your data is stored in instance variables instead of "attributes" hash, so it is not saved into database.
If you want to add new attribute to your model, you actually need to add column to database table that correspond to that model and then reload the whole application.
There's an important distinction between database fields and temporary attr_accessor declared properties. If you've declared your columns, then attr_accessor declarations are unnecessary.
Keep in mind that the data should be stored in the model's attributes property to be properly saved, not as individual instance variables.
For example, to see what's scheduled to be saved:
class MyModel < ActiveRecord::Base
attr_accessor :not_saved
end
model = MyModel.new(:not_saved => 'foo')
puts model.attributes.inspect
There are methods to get information on what columns are available in a model, such as:
MyModel.columns_names
The attr_accessors will never be saved to the DB. These are internal variables within the instance. If you want to save the values, you have to create real columns.
Make a migration to declare the columns then try again.
Related
We are trying to serialize an attribute called differences in order to save the value of this attribute as a Hash into database. The type of differences is text. it used to be saved as Hash in the database. But recently we found out it is saved yaml. is there any way to prevent it saving as yaml?
Class A < ActiveRecord::Base
has_many :bs
serialize :differences
def diff(other)
b_diffs = {}
bs.zip(other.bs).each do |a,b|
b_diffs.merge!(:bs => a.diff?(b)
end
end
def diff_against_last_a
last_a = ....
self.differences = last_a.diff(self)
end
end
Any suggestion?
EDIT:
my problem is i can't retrieve differences as Hash.
Interestingly, when i check on console, it is saved as yaml, and retrieved as yaml. and then i rerun the the method diff(other) and save the result to differeces, it shows as Hash and i can retrieve Hash. But it doesn;t work this way when running on my app.
I think the problem here is the serialized data are no longer returned Hash.
UPDATE:
So i found out under the development environment, there is such a problem. But in production and staging (we use heroku), it practises fine. so We'll consider it ok then.
In addition to my comment, here is the official description:
If you have an attribute that needs to be saved to the database as an
object, and retrieved as the same object, then specify the name of
that attribute using this method and it will be handled automatically.
The serialization is done through YAML. If class_name is specified,
the serialized object must be of that class on retrieval or
SerializationTypeMismatch will be raised.
source: http://apidock.com/rails/ActiveRecord/Base/serialize/class
In layman's terms "When Rails serializes a hash to save in the db, all it does is convert it to YAML so that it can be stored as a string."
As the last line of official description reads, you have define the class of retrieval. Hash in your case. So declare serialize like this:
serialize :differences, Hash
Say I have a user model. It has an instance method called status. Status is not an association. It doesn't follow any active record pattern because it's a database already in production.
class User < ActiveRecord::Base
def status
Connection.where(machine_user_id: self.id).last
end
end
So I do this.
#users = User.all
First of all I can't eager load the status method.
#users.includes(:status).load
Second of all I can't cache that method within the array of users.
Rails.cache.write("user", #users)
The status method never gets called until the view layer it seems like.
What is the recommended way of caching this method.
Maybe this instance method is not what I want to do. I've looked at scope but it doesn't look like what I want to do.
Maybe I just need an association? Then I get the includes and I can cache.
But can associations handle complex logic. In this case the instance method is a simple query. What if I have complex logic in that instance method?
Thanks for any help.
Have You tried to encapsulate this logic inside some plain Ruby object like this (I wouldn't use this for very large sets though):
class UserStatuses
def self.users_and_statuses
Rails.cache.fetch "users_statuses", :expires_in => 30.minutes do
User.all.inject({}) {|hsh, u| hsh[u.id] = u.status; hsh }
end
end
end
After that You can use some helper method to access cached version
class User < ActiverRecord::Base
def cached_status
UserStatuses.users_and_statuses[id]
end
end
It doesn't solve Your eager loading problem, Rails doesn't have any cache warming up techniques built in. But by extracting like this, it's easily done by running rake task in Cron.
Also in this case I don't see any problems with using association. Rails associations allows You to submit different options including foreign and primary keys.
I have an object PersistentObject which you can think of as plucked out of an ORM, it's an object which you can use natively in your programming language (agnostic to the backend), and it has methods load and save for committing changes to a database.
I want my PersistentObject to be faultable, i.e. I want to be able to initialize it as a lightweight pointer which server only to reference the object in the database. And when (if) the moment comes then I can fault it into memory by actually going to the database and fetching it. The point here is to be able to add this object to collections as a reference without ever needing to fetch the object. I also want to be able to initialize the object the old fashioned way with classic constructor and then commit it to the database (this is handy when you need to create a new object from scratch, rather than manipulating an existing one).
So I have an object which has multiple constructors: a classic one, and one that creates a fault based on the object GUID in the database. And when the object is initialized as a fault, I want instance methods to be able to access that state as an instance variable because operations on a fault are different to those on a fully loaded object. But for obvious reasons, I don't want clients messing with my inner state so I don't want to create an accessor for the ivar. So my question is, how do I init/set an ivar from a class method in an object instance in such a way that outside clients of my class can't mess with it (i.e. set its value to something else)?
Sorry for all the words, the code should make it a lot clearer. I've tried something which obviously doesn't work but illustrates the point nicely. Apologies if this is an elementary question, I'm quite new to Ruby.
class PersistentObject
def initialize(opts={})
#id = opts[:id] || new_id
#data = opts[:data] || nil
end
def self.new_fault(id)
new_object = PersistentObject.new
new_object.#fault = true #<----- How do you achieve this?
new_object
end
def new_id
#returns a new globally unique id
end
def fault?
#fault
end
def load
if fault?
#fault in the object from the database by fetching the record corresponding to the id
#fault = false
end
end
def save
#save the object to the database
end
end
#I create a new object as a fault, I can add it to collections, refer to it all I want, etc., but I can't access it's data, I just have a lightweight pointer which can be created without ever hitting the database
o = PersistentObject.new_fault("123")
#Now let's suppose I need the object's data, so I'll load it
o.load
#Now I can use the object, change it's data, etc.
p o.data
o.data = "foo"
#And when I'm ready I can save it back to the database
o.save
EDIT:
I should say that my heart isn't set on accessing that instance's ivar from the class method, I'd be more than happy to hear of an idiomatic Ruby pattern for solving this problem.
You could use instance_eval:
new_object.instance_eval { #fault = true }
or instance_variable_set:
new_object.instance_variable_set(:#fault, true)
If your goal is to set the instance variable then I agree with Stephan's answer. To answer your edit, another approach is to add another option to the constructor:
class PersistentObject
def initialize(opts={})
#id = opts[:id] || new_id
#data = opts[:data] || nil
#fault = opts[:fault] || false
end
def self.new_fault(id)
self.new(fault: true)
end
...
Unfortunately, Ruby's unconventional implementation of private/protected make them non-viable for this problem.
This is not possible. And I am not talking about "not possible in Ruby", I am talking about mathematically, logically impossible. You have two requirements:
Another object should not be allowed to set #fault.
Another object should be allowed to set #fault. (Remember, PersistentObject is just yet another object.)
It should be immediately obvious that those two requirements contradict each other and thus what you want simply cannot be done. Period.
You can create an attr_writer for #fault, then PersistentObject can write to it … but so can everybody else. You can make that writer private, then PersistentObject needs to use metaprogramming (i.e. send) to circumvent that access protection … but so can everybody else. You can use instance_variable_set to have PersistentObject set #fault directly … but so can everybody else.
consider the following example
module DataMapper
class Property
class CustomType < DataMapper::Property::Text
def load(value)
# do stuff and return formatted value
end
end
end
end
Class A
property :name, String
property :value, CustomType
end
now when I do A.first or A.first.value the load method gets executed, but the calculations that I need to do inside load is dependent on that instance's name property. So how do I get the context of this instance/resource(as referred inside source code) inside the load method ?
Please let me know if the question is not clear yet !
You are attempting to break encapsulation. The name and value are different properties, and thus each should be in ignorance of the other's existence, let alone value.
The correct solution is to move the "stuff" to an object which has visibility over both properties. The two options are:
the A class (as suggested by user1376019); or
a complex data type, e.g. NameAndValue < DataMapper::Property::Object, which encapsulates both properties.
If you need to perform aggregate functions over the individual properties, the second option would not work, unless you can somehow override the complex property to have multiple fields.
In either case, value cannot refer to name without a reference to it.
Let's say I have a FireNinja < Ninja object in my database, stored using single table inheritance. Later, I realize he's really a WaterNinja < Ninja. What's the cleanest way to change him to the different subclass? Even better, I'd love to create a new WaterNinja object and just replace the old FireNinja in the DB, preserving the ID.
Edit
I know how to create the new WaterNinja object from my existing FireNinja, and I also know I can delete the old one and save the new one. What I'd like to do is mutate the class of the existing item. Whether I do that by creating a new object and doing some ActiveRecord magic to replace the row, or by doing some sort of crazy thing to the object itself, or even by deleting it and reinserting with the same ID is part of the question though.
You can make your FireNinja act as a WaterNinja by doing
#ninja.becomes(WaterNinja)
If you want permanently change classes, then just overwrite the type attribute.
#ninja.type = "WaterNinja"
#ninja.save!
You need to do two things:
Create WaterNinja < Ninja
In ninjas table run something like UPDATE ninjas SET (type = 'WaterNinja') WHERE (type = 'FireNinja')
That's about it.
For doing runtime conversion this will do it, but I don't recommend.
class Ninja
def become_another_ninja(new_ninja_type)
update_attribute(:type, new_ninja_type)
self.class.find(id)
end
end
#water_ninja = #fire_ninja.become_another_ninja('WaterNinja')
The problem with this is that #fire_ninja will now be a throwaway object.
Favor composition over inheritance - you should be using the strategy pattern to change this type of behavior at runtime.
http://en.wikipedia.org/wiki/Strategy_pattern
You will have to define a WaterNinja#to_fireninja method. Ruby has no way to change the class of an object while keeping it the same object.
class WaterNinja < Ninja
def to_fireninja
FireNinja.new :name => name
end
end