I don't understand the keywords like attr_reader or property in the following example:
class Voiture
attr_reader :name
attr_writer :name
property :id, Serial
property :name, String
property :completed_at, DateTime
end
How do they work? How can I create my own? Are they functions, methods?
class MyClass
mymagickstuff :hello
end
That are just class methods. In this example has_foo adds a foo method to an instance that puts a string:
module Foo
def has_foo(value)
class_eval <<-END_OF_RUBY, __FILE__, __LINE__ + 1
def foo
puts "#{value}"
end
END_OF_RUBY
end
end
class Baz
extend Foo
has_foo 'Hello World'
end
Baz.new.foo # => Hello World
Those are class methods, you can add them to a class, or create your own class that has addition methods. In your own class:
class Voiture
def self.my_first_class_method(*arguments)
puts arguments
end
end
Or to add to a class:
Voiture.class_eval do
define_method :my_second_class_method do |*arguments|
puts arguments
end
end
Once such a class method is defined, you can use it like this:
class VoitureChild < Voiture
my_first_class_method "print this"
my_second_class_method "print this"
end
There are also ways to do this by adding modules to a class, which is often how rails does such things, such as using a Concern.
You will want to monkey patch the class Module. That's where methods like attr_reader reside.
class Module
def magic(args)
puts args.inspect
end
end
class A
magic :hello, :hi
end
#=> [:hello, :hi]
As The Tin Man mentioned, monkey-patching base-level classes can be dangerous. Consider it like time-traveling into the past and adding something in the past. Just make sure that what you are adding isn't going to overwrite some other event or else you could come back to a Ruby script/timeline that isn't the same one you left.
Related
I have a module and would like to mixin some methods as class methods and some as instance methods.
For example:
module Foo
def self.class_method
end
def instance_method
end
end
class Bar
include Foo
end
Usage:
Bar.class_method
Bar.new.instance_method
Is it possible to do this in Ruby?
If not, is it possible to define which methods are class methods and which are instance methods within the Bar class?
I don't want the same method defined as both a class and instance method.
This pattern is very common in Ruby. So common, in fact, that ActiveSupport::Concern abstracts it a bit.
Your typical implementation looks like this:
module Foo
def self.included(other_mod)
other_mod.extend ClassMethods
end
def instance_method
end
module ClassMethods
def class_method
end
end
end
class Bar
include Foo
end
You can't accomplish this easily as you describe without somehow splitting the included module into multiple pieces, though, unfortunately.
You can, but not quite like that. This is a common pattern for including both instance and class methods in one module.
module Foo
def self.included(base)
base.extend ClassMethods
end
def instance_method
puts 'instance'
end
module ClassMethods
def class_method
puts 'class'
end
end
end
class Bar
include Foo
end
bar = Bar.new
Bar.class_method #=> 'class'
bar.instance_method #=> 'instance'
You are close. You probably noticed that the instance method works fine. The problem with the class method is that self => Foo when it's defined, so it does not respond to Bar. If you add the line puts "I'm a module method" in self.class_method, you will find
Foo.class_method => "I'm a module method"
Here's an easy way to accomplish what you want to do:
module Foo_class
attr_accessor :cat
def class_method
puts "I'm a class method"
end
end
module Foo_instance
def instance_method
puts "I'm an instance method"
end
end
class Bar
extend Foo_class
include Foo_instance
end
Bar.class_method #=> I'm a class method
Bar.cat = "meow"
Bar.cat #=> "meow"
Bar.new.instance_method #=> I'm an instance method
I added a class instance variable, #cat, and an accessor for it, just to show how easy that is to do.
Object#extend is great, because you can just add instance variables and methods to a module, just as you would do with Object#include to mixin instance variables and methods, and extend mixes them in as class instance variables and class methods. You can also do this:
bar = Bar.new
bar.extend Foo_class
to have the instance variables and methods in Foo_class apply to the instance bar.
I'm trying to define a couple of modules to easily add in some instance and class methods to other classes, here's what I'm doing:
module Foo
module Bar
def speak
puts "hey there"
end
end
module Baz
extend Foo::Bar
def welcome
puts "welcome, this is an instance method"
end
end
end
class Talker
include Foo::Baz
end
Talker.new.welcome
Talker.speak
The output of this is:
welcome, this is an instance method
undefined method 'speak' for Talker.class (NoMethodError)
I was expecting Talker to have the 'speak' method since it includes Foo::Baz which itself extends Foo::Bar.
What am I missing?
You can try this:
module Baz
extend Foo::Bar
def self.included(base)
base.send :extend, Foo::Bar
end
def welcome
puts "welcome, this is an instance method"
end
end
This will auto-extend all classes in wich Baz is included.
PS:
extend Foo::Bar in module Baz was in original snippet, this code do not influence on method def self.included(base).
try this:
class Talker
extend Foo::Baz
end
since you want to call Talker.speak as a class method and not as an instance method (like Talker.new.speak) you have to include the Foo:Baz in a way that the class will take the methods itself.
One possibility is to use 'extend' (as above) the other is modifying it's eigenclass:
class Talker
class << self
include Foo::Baz
end
end
I'm greatly confused by Ruby's behavior when defining const_missing and other class methods inside a class << self definition as opposed to using the def self.foo syntax.
I was trying to do something like this:
class Foo
class << self
def foo
puts MISSING
end
def const_missing(name)
puts "#{name} missing"
end
end
end
Foo.foo
I mostly use the the class << self syntax to define class methods. However, it did not work as expected. const_missing is never called. The above results in a NameError.
Defining both methods like this works as expected:
def self.foo
puts MISSING
end
def self.const_missing(name)
puts "#{name} missing"
end
I thought that the class << self syntax is just another way to define class methods, but completely equivalent to def self.foo? I've tested the above with MRI 1.8.7, 1.9.2 and JRuby 1.5.6. So obviously I'm missing something here?
Any hint is greatly appreciated.
Thanks, Martin
class << self is not a shortcut to define class methods. This syntax (I don't know the exact naming) opens the eigenclass from a object (in your case, a class). With that you can define methods to the object (not instance methods). But when you call a constant into the eigenclass, you are calling a constant from the eigenclass, not from the class. In this case you have to define a class method on the eigenclass to the const_missing, two ways to do that:
class Test
class << self
def foo
p MISSING
end
# First way: (syntax sugar for the second way)
def self.const_missing(name)
name
end
# Second way:
class << self # eigenclass of the eigenclass of the class
def const_missing(name)
name
end
end
end
end
Test.foo #=> :MISSING
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
What's the best way to abstract this pattern:
class MyClass
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
A good solution should take superclasses into consideration and be able to handle still being able to have an initializer to do more things. Extra points for not sacrificing performance in your solution.
A solution to that problem already (partially) exists, but if you want a more declarative approach in your classes then the following should work.
class Class
def initialize_with(*attrs, &block)
attrs.each do |attr|
attr_accessor attr
end
(class << self; self; end).send :define_method, :new do |*args|
obj = allocate
init_args, surplus_args = args[0...attrs.size], args[attrs.size..-1]
attrs.zip(init_args) do |attr, arg|
obj.instance_variable_set "##{attr}", arg
end
obj.send :initialize, *surplus_args
obj
end
end
end
You can now do:
class MyClass < ParentClass
initialize_with :foo, :bar
def initialize(baz)
#initialized = true
super(baz) # pass any arguments to initializer of superclass
end
end
my_obj = MyClass.new "foo", "bar", "baz"
my_obj.foo #=> "foo"
my_obj.bar #=> "bar"
my_obj.instance_variable_get(:#initialized) #=> true
Some characteristics of this solution:
Specify constructor attributes with initialize_with
Optionally use initialize to do custom initialization
Possible to call super in initialize
Arguments to initialize are the arguments that were not consumed by attributes specified with initialize_with
Easily extracted into a Module
Constructor attributes specified with initialize_with are inherited, but defining a new set on a child class will remove the parent attributes
Dynamic solution probably has performance hit
If you want to create a solution with absolute minimal performance overhead, it would be not that difficult to refactor most of the functionality into a string which can be evaled when the initializer is defined. I have not benchmarked what the difference would be.
Note: I found that hacking new works better than hacking initialize. If you define initialize with metaprogramming, you'd probably get a scenario where you pass a block to initialize_with as a substitute initializer, and it's not possible to use super in a block.
This is the first solution that comes to my mind. There's one big downside in my module: you must define the class initialize method before including the module or it won't work.
There's probably a better solution for that problem, but this is what I wrote in less than a couple of minutes.
Also, I didn't keep performances too much into consideration. You probably can find a much better solution than me, especially talking about performances. ;)
#!/usr/bin/env ruby -wKU
require 'rubygems'
require 'activesupport'
module Initializable
def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
alias_method_chain :initialize, :attributes
class_inheritable_array :attr_initializable
end
end
module ClassMethods
def attr_initialized(*attrs)
attrs.flatten.each do |attr|
attr_accessor attr
end
self.attr_initializable = attrs.flatten
end
end
module InstanceMethods
def initialize_with_attributes(*args)
values = args.dup
self.attr_initializable.each do |attr|
self.send(:"#{attr}=", values.shift)
end
initialize_without_attributes(values)
end
end
end
class MyClass1
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
class MyClass2
def initialize(*args)
end
include Initializable
attr_initialized :foo, :bar
end
if $0 == __FILE__
require 'test/unit'
class InitializableTest < Test::Unit::TestCase
def test_equality
assert_equal MyClass1.new("foo1", "bar1").foo, MyClass2.new("foo1", "bar1").foo
assert_equal MyClass1.new("foo1", "bar1").bar, MyClass2.new("foo1", "bar1").bar
end
end
end
class MyClass < Struct.new(:foo, :bar)
end
I know this is an old question with perfectly acceptable answers but I wanted to post my solution as it takes advantage of Module#prepend (new in Ruby 2.2) and the fact that modules are also classes for very simple solution. First the module to make the magic:
class InitializeWith < Module
def initialize *attrs
super() do
define_method :initialize do |*args|
attrs.each { |attr| instance_variable_set "##{attr}", args.shift }
super *args
end
end
end
end
Now let's use our fancy module:
class MyClass
prepend InitializeWith.new :foo, :bar
end
Note that I left our the attr_accessible stuff as I consider that a separate concern although it would be trivial to support. Now I can create an instance with:
MyClass.new 'baz', 'boo'
I can still define an initialize for custom initialization. If my custom initialize take an argument those will be any extra arguments provided to the new instance. So:
class MyClass
prepend InitializeWith.new :foo, :bar
def initialize extra
puts extra
end
end
MyClass.new 'baz', 'boo', 'dog'
In the above example #foo='baz', #bar='boo' and it will print dog.
What I also like about this solution is that it doesn't pollute the global namespace with a DSL. Objects that want this functionality can prepend. Everybody else is untouched.
This module allows an attrs hash as an option to new(). You can include the module in a class with inheritance, and the constructor still works.
I like this better than a list of attr values as parameters, because, particularly with inherited attrs, I wouldn't like trying to remember which param was which.
module Attrize
def initialize(*args)
arg = args.select{|a| a.is_a?(Hash) && a[:attrs]}
if arg
arg[0][:attrs].each do |key, value|
self.class.class_eval{attr_accessor(key)} unless respond_to?(key)
send(key.to_s + '=', value)
end
args.delete(arg[0])
end
(args == []) ? super : super(*args)
end
end
class Hue
def initialize(transparent)
puts "I'm transparent" if transparent
end
end
class Color < Hue
include Attrize
def initialize(color, *args)
p color
super(*args)
p "My style is " + #style if #style
end
end
And you can do this:
irb(main):001:0> require 'attrize'
=> true
irb(main):002:0> c = Color.new("blue", false)
"blue"
=> #<Color:0x201df4>
irb(main):003:0> c = Color.new("blue", true, :attrs => {:style => 'electric'})
"blue"
I'm transparent
"My style is electric"