I need to create a Ruby class on the fly, i.e. dynamically, that derives from ActiveRecord::Base. I use eval for the time being:
eval %Q{
class ::#{klass} < ActiveRecord::Base
self.table_name = "#{table_name}"
end
}
Is there an equivalent, and at least equally concise way to do this without using eval?
You can use the Class class, of which classes are instances. Confused yet? ;)
cls = Class.new(ActiveRecord::Base) do
self.table_name = table_name
end
cls.new
Of course, there is :)
class Foo
class << self
attr_accessor :table_name
end
end
Bar = Class.new(Foo) do
self.table_name = 'bars'
end
Bar.table_name # => "bars"
Related
I want to be able to dynamically create classes, for scripting outside my Rails app, that inherit from ActiveRecord.
I'm stuck on something like this:
require 'active_record'
def create_arec(table_name)
Class.new ActiveRecord::Base do
self.table_name = table_name
yield
end
end
Band = create_arec 'bands' do
scope :only_rock, -> {where genre: 'rock'}
end
rock_bands = Band.only_rock #undefined method `only_rock'
How do I make it work, or can someone show me better way to do it?
Nailed it:
def create_arec(table_name, &block)
klass = Class.new(ActiveRecord::Base){self.table_name = table_name}
klass.class_eval &block
klass
end
thanks #phoet
I would like to make a class which delegates all instance and class methods to another class. My aim is to be able to give a class which is set in the app options a name which appears to fit in with the code around it.
This is what I have so far:
require 'delegate'
class Page < DelegateClass(PageAdapter)
# Empty
end
However, this only seems to delegate instance methods, not class methods.
You could define your own delegation scheme:
class Page < DelegateClass(PageAdapter)
##delegate_class=PageAdaptor
def self.delegate_class_method(*names)
names.each do |name|
define_method(name) do |*args|
##delegate_class.__send__(name, *args)
end
end
end
delegate_class_method ##delegate_class.singleton_methods
end
You don't want delegation, you want direct subclassing:
class Page < PageAdapter
# Empty
end
Proof:
class Foo
def self.cats?
"YES"
end
def armadillos?
"MAYBE"
end
end
class Bar < Foo; end
p Bar.new.armadillos?
#=> "MAYBE"
p Bar.cats?
#=> "YES"
I often write stuff that looks like this:
def AModel < ActiveRecord::Base
belongs_to :user
def SomeCodeThatDoesSomeCalculations
# some code here
end
def SomeCodeThatDoesSomeCalculations!
self.SomeCodeThatDoesSomeCalculations
self.save
end
end
Is there a better way to generate the functions with the suffix "!" ?
If you're doing it really often you can do smth like that:
class Model < ActiveRecord::Base
def self.define_with_save(method_name)
define_method "#{method_name}!" do
send method_name
save
end
end
def save # stub method for test purpose
puts 'saving...'
end
def do_stuff
puts 'doing stuff...'
end
define_with_save :do_stuff
end
m = Model.new
m.do_stuff
# => 'doing stuff...'
m.do_stuff!
# => 'doing stuff...'
# => 'saving...'
If you want that in multiple models may be you'd like to create your own base class for them containing this define_with_save class method, or you can add it to ActiveRecord::Base itself if you are sure you need it.
Btw, I hope you're not really naming you your methods in SomeCodeThatDoesSomeCalculations notation as they are usually named like some_code_that_does_some_calculations.
I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?
I'm trying to do an instance_eval followed by a attr_accessor inside initialize, and I keep getting this: ``initialize': undefined method 'attr_accessor'`. Why isn't this working?
The code looks kind of like this:
class MyClass
def initialize(*args)
instance_eval "attr_accessor :#{sym}"
end
end
You can't call attr_accessor on the instance, because attr_accessor is not defined as an instance method of MyClass. It's only available on modules and classes. I suspect you want to call attr_accessor on the instance's metaclass, like this:
class MyClass
def initialize(varname)
class <<self
self
end.class_eval do
attr_accessor varname
end
end
end
o1 = MyClass.new(:foo)
o2 = MyClass.new(:bar)
o1.foo = "foo" # works
o2.bar = "bar" # works
o2.foo = "baz" # does not work
A cleaner implementation (NB: This will add the accessor to ALL instances of the class, not just the single instance, see comments below):
class MyClass
def initialize(varname)
self.class.send(:attr_accessor, varname)
end
end
Rob d'Apice has it almost right. You just need to write:
self.singleton_class.send(:attr_accessor, varname)
or
self.singleton_class.class_eval "attr_accessor :#{varname}"
or my favorite variant
self.singleton_class.class_exec do attr_accessor varname end
assuming the value of varname is a symbol