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
Related
As a little hobby project, I'm trying to build up my own object system. I was wondering if there is a way of changing the default inheritance of all classes from Object to my base class Entity, so that whenever I create a new class I don't have to explicitly say class Thing < Entity; ideally, I would just be able to say class Thing and have its default superclass be my Entity class.
Sure you could do this by modifying the relevant part of the Ruby source and recompiling Ruby:
VALUE
rb_define_class_id(ID id, VALUE super)
{
VALUE klass;
if (!super) super = rb_cObject; // <-- where the default is set
klass = rb_class_new(super);
// ...
But that’s a huge hassle and requires patching and running a custom Ruby and probably has a lot of gotchas and things that are hard-coded to assume Object is the default.
And, on top of that, what’s the point? If you replace Object with something else as the default superclass, every class—including those in Ruby core—will now inherit from this new default superclass. You could get the same effect (just without the different name) far more easily and without needing a custom Ruby by just changing Object itself. That’s the beauty of being able to reopen classes! For example:
class Object
def foo
'bar!'
end
end
class A; end
A.new.foo #=> 'bar!'
If you wanted to be kind you might even just put all the relevant methods in an Entity module instead of a class and then include it into Object.
No, unfortunately this is not possible in Ruby. Ruby does not have a Meta-Object Protocol like e.g. CLOS that would allow you to manipulate the core semantics of the Object Model. It would be nice, though!
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.
I want to create multiple instances of a class without saving them to a database. Then I want to loop through all the objects that I've created. Any thoughts?
Sounds like you're looking for ObjectSpace.each_object:
ObjectSpace.each_object(YourClass) do |o|
# Do something interesting with object 'o' here
end
If you can manually keep track of your objects then I think that would be better, I tend to see ObjectSpace as a last resort.
I'm trying to generalize a data retrieval mechanism with Ruby, and can't seem to find a way to retrieve a CSV file and access one of the row's columns by using a dot operator like so:
Let's say I have a CSV table:
#some_file.csv
name,age
albert,13
And I create a FasterCSV table from it:
a = FasterCSV.new(File.open('some_file.csv'), :headers => :first_row)
Then, when accessing a row, I'd like to be able to say:
a[0].name
=> 'albert'
Instead of
a[0]['name']
=> 'albert'
Anyone know how to do that?
Well, if you don't find one, you can always monkey-patch FasterCSV::Row class, something like:
class FasterCSV::Row
def method_missing(m,*args)
if self.field?(m.to_s)
return self[m.to_s]
else
super
end
end
end
(Haven't tried the code myself.)
PS. As you are generalizing data retrieval mechanism, I assume that CSV is just one of several data sources you plan to support. The logical thing then would be to create a single wrapper class for each of your data sources, with some common interface (which might or might not use accessors for accessing row fields). But underneath it should still access CSV row usual way, using [] method. So, as Glenjamin already asked, why do you need this at all? ;)
The simplest answer would be.. why?
I'll assume its mostly as syntactic sugar, so here's a little monkeypatch that should do what it is you want:
class FasterCSV::Row
def method_missing(row)
field(row)
end
end
Note that any field names conflicting with existing Row methods wont work like this.
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.