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
Related
TDD
gem 'minitest', '~> 5.2'
require 'minitest/autorun'
require 'minitest/pride'
require './tire'
class TireTest < Minitest::Test
def test_the_tire_does_not_start_out_flat
tire = Tire.new
refute tire.flat?, "This tire is brand new"
end
def test_the_tire_has_a_blowout
tire = Tire.new
tire.blow_out
assert tire.flat?, "I ran over a nail!"
end
end
CODE
class Tire
attr_reader :tire
def initialize
#tire = false
end
def flat?
#tire
end
def blow_out
#tire = true
end
end
Better way to solve for this test than what I have? While the simpler you can break down a code and have it make sense to anyone else reading it.
Some minor changes you can apply to your existing solution.
#flat would be more appropriate name for an attribute since it is itself an attribute of a tire.
Encapsulate attr_reader unless you need a concrete case of its public usage.
Prefer using attr_reader over directly calling instance variables.
class Tire
def initialize
#flat = false
end
def flat?
flat
end
def blow_out
#flat = true
end
private
attr_reader :flat
end
module Framework
class CreateTableDefinition
attr_accessor :host, :username, :password
end
end
def create_table(table_name)
obj = Framework::CreateTableDefinition.new
yield(obj) if block_given?
end
create_table :users do |config|
config.host :localhost
end
And here is the error I get
-:13:in `block in <main>': wrong number of arguments (1 for 0) (ArgumentError)
from -:9:in `create_table'
from -:12:in `<main>'
If I change the code to
config.host = :localhost
it works fine. But what I want is to work as described above config.host :localhost
You missed assignment:
config.host = :localhost
Edit
If you want to get rid of assignments, you need to define setter methods without = at the end. This might generate quote a lot of code, so I would rather go with some meta-programming (because it's fun!)
class MyConfigClass
def self.attributes(*args)
args.each do |attr|
define_method attr do |value|
#attributes[attr] = value
end
end
end
def initialize
#attributes = {}
end
def get(attr)
#attributes[attr]
end
end
class CreateTableDefinition < MyConfigClass
attributes :host, :username, :password
end
c = CreateTableDefinition.new
c.host :localhost
c.get(:host) #=> :localhost
Try manually making a method that does what you want instead of using the attr_accessor shortcut.
class CreateTableDefinition
attr_accessor :username, :password
def host(sym)
#host = sym
end
def get_host
#host
end
end
If you don't like the idea of writing those methods for every attribute, look into writing your own helper, something like attr_rails_like, and mix it in to the Class object. This article might be helpful.
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"
I'm new to Ruby and trying to determine how I can call a class from a child object. Something like the below; however when I try it, I get an error saying "undefined local variable or method `me'"
class my_object < Object
attr_accessor :me
def initialize(attributes ={})
end
def setvalue(passed_value)
#passed_value = passed_value.to_s
end
def search(passed_value)
#passed_value.include?(passed_value)
end
end
def getMe
me_too = my_object.new
me_too.me = "test"
me_too.me.search("test")
end
end
instance.class
will give you a reference to the class
This works:
But your code had multiple errors.
class MY
attr_accessor :me
def initialize(attributes ={})
end
def setvalue(passed_value)
passed_value = passed_value.to_s
end
def search(passed_value)
passed_value.include?(passed_value)
end
def getMe
me_too = MY.new
me_too.me = "test"
me_too.search("test")
end
end
my = MY.new
my.getMe
You don't need to explicity extend Object, everything extends Object in ruby.
Your class name needs to start with a capital letter.
class MyObject
attr_accessor :me
end
me_too = MyObject.new
me_too.me = "test"
in console
me_too => #<MyObject:0x106b2e420 #me="test">
Check out some introductory ruby tutorials maybe http://ruby.learncodethehardway.org/
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?