I am very new to ruby and I just spent time studying patterns from the existing ruby projects in github. Now, I landed on the twitter's ruby project and noticed these lines in their configuration:
client = Twitter::REST::Client.new do |config|
config.consumer_key = "YOUR_CONSUMER_KEY"
config.consumer_secret = "YOUR_CONSUMER_SECRET"
config.access_token = "YOUR_ACCESS_TOKEN"
config.access_token_secret = "YOUR_ACCESS_SECRET"
end
In the declaration of this method call, I also noticed this:
module Twitter
class Client
include Twitter::Utils
attr_accessor :access_token, :access_token_secret, :consumer_key, :consumer_secret, :proxy
def initialize(options = {})
options.each do |key, value|
instance_variable_set("##{key}", value)
end
yield(self) if block_given?
end
...
Now, as I do my practice, I copied the same logic but observe the content of "initialize" method.
module Main
class Sample
attr_accessor :hello, :foo
def initialize(options={})
yield(self) if block_given?
end
def test
#hello
end
end
end
And call it (same on how twitter code above does)
sample = Main::Sample.new do |config|
config.hello = "world"
config.foo = "bar"
end
puts "#{sample.hello} #{sample.foo}" # outputs => world bar
puts sample.test # outputs => world
Now, my question is that even though I don't have these lines in my code (see the code block from twitter above) inside my "initialize" method,
options.each do |key, value|
instance_variable_set("##{key}", value)
end
the code
puts "#{sample.hello} #{sample.foo}" and puts sample.test still works fine. Why is this so? How was the instance variable really set here?
It's because you're manually calling them with thing like config.hello= and config.foo=.
What won't work without that chunk of code is this:
Main::Sample.new(hello: 'world')
You'll need that part to pick up the options and apply them.
That Twitter version is pretty slack. Normally you'd want to test that there's a property with that name instead of just randomly assigning instance variables. Typically this is done with a white-list of some sort:
ATTRIBUTES = %i[ hello world ]
attr_accessor *ATTRIBUTES
def initialize(options = nil)
options and options.each do |attr, value|
if (ATTRIBUTES.include?(attr))
send("#{attr}=", value)
else
raise "Unknown attribute #{attr.inspect}"
end
end
yield self if (block_given?)
end
That will raise exceptions if you call with invalid options.
Related
I am building a DSL and have this module
module EDAApiBuilder
module Client
attr_accessor :api_client, :endpoint, :url
def api_client(api_name)
#apis ||= {}
raise ArgumentError.new('API name already exists.') if #apis.has_key?(api_name)
#api_client = api_name
#apis[#api_client] = {}
yield(self) if block_given?
end
def fetch_client(api_name)
#apis[api_name]
end
def endpoint(endpoint_name)
raise ArgumentError.new("Endpoint #{endpoint_name} already exists for #{#api_client} API client.") if fetch_client(#api_client).has_key?(endpoint_name)
#endpoint = endpoint_name
#apis[#api_client][#endpoint] = {}
yield(self) if block_given?
end
def url=(endpoint_url)
fetch_client(#api_client)[#endpoint]['url'] = endpoint_url
end
end
end
so that I have tests like
context 'errors' do
it 'raises an ArgumentError when trying to create an already existent API client' do
expect {
obj = MixinTester.new
obj.api_client('google')
obj.api_client('google')
}.to raise_error(ArgumentError,'API name already exists.')
end
it 'raises an ArgumentError when trying to create a repeated endpoint for the same API client' do
expect {
obj = MixinTester.new
obj.api_client('google') do |apic|
apic.endpoint('test1')
apic.endpoint('test1')
end
}.to raise_error(ArgumentError,"Endpoint test1 already exists for google API client.")
end
end
I would rather have #api_clientwritten as an assignment block
def api_client=(api_name)
so that I could write
obj = MixinTester.new
obj.api_client = 'google' do |apic| # <=== Notice the difference here
apic.endpoint('test1')
apic.endpoint('test1')
end
because I think this notation (with assignment) is more meaningful. But then, when I run my tests this way I just get an error saying that the keyworkd_do is unexpected in this case.
It seems to me that the definition of an assignment block is syntactic sugar which won't contemplate blocks.
Is this correct? Does anyone have some information about this?
By the way: MixinTester is just a class for testing, defined in my spec/spec_helper.rb as
class MixinTester
include EDAApiBuilder::Client
end
SyntaxError
It seems to me that the definition of an assignment [method] is syntactic
sugar which won't contemplate blocks.
It seems you're right. It looks like no method with = can accept a block, even with the normal method call and no syntactic sugar :
class MixinTester
def name=(name,&block)
end
def set_name(name, &block)
end
end
obj = MixinTester.new
obj.set_name('test') do |x|
puts x
end
obj.name=('test') do |x| # <- syntax error, unexpected keyword_do, expecting end-of-input
puts x
end
Alternative
Hash parameter
An alternative could be written with a Hash :
class MixinTester
def api(params, &block)
block.call(params)
end
end
obj = MixinTester.new
obj.api client: 'google' do |apic|
puts apic
end
#=> {:client=>"google"}
You could adjust the method name and hash parameters to taste.
Parameter with block
If the block belongs to the method parameter, and not the setter method, the syntax is accepted :
def google(&block)
puts "Instantiate Google API"
block.call("custom apic object")
end
class MixinTester
attr_writer :api_client
end
obj = MixinTester.new
obj.api_client = google do |apic|
puts apic
end
# =>
# Instantiate Google API
# custom apic object
It looks weird, but it's pretty close to what you wanted to achieve.
Normally, we can do this with Proc objects:
[15] pry(main)> pr = -> { puts "test message" }
=> #<Proc:0x000000057b8a90#(pry):12 (lambda)>
[16] pry(main)> pr.call
test message
=> nil
[17] pry(main)> define_method("test_method", pr)
=> :test_method
[18] pry(main)> test_method
test message
But, what if I have a raw code string and want to load it into a proc? Fake code below:
raw_code = "puts 'test message'"
pr = -> { load(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr}
test_method # => test message
Actually, my original problem is how to write a method hook like this:
class TestClass
def test_method
puts url
end
def test_method_a
puts url
end
before :test_method, :test_method_a do
url = __method__.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
My problem is more complicated, this is just a simple example to illustrate the key problem.
how to define the load method to get this working?
By spelling it eval:
raw_code = "puts 'test message'"
pr = -> { eval(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr)
test_method # => test message
--output:--
test message
test message
Actually, my original problem is how to write a method hook...
class TestClass
def initialize
#url = %q{__method__.to_s.split("_").join("/")}
end
def test_method
puts(eval #url)
end
def test_method_a
puts(eval #url)
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
Actually, my original problem is how to write a method hook like this:
Module TestClass
def test_method
puts url
end
The problem is that url can never refer to a value outside the def. A def cuts off the visibility of local variables outside the def.
class TestClass
def test_method
puts #url
end
def test_method_a
puts #url
end
def self.before(*method_names, &block)
method_names.each do |method_name|
alias_method "first_#{method_name}", method_name
define_method(method_name, block) #This changes __method__ inside the block from nil to method_name
alias_method "second_#{method_name}", method_name
define_method(method_name) do
send "second_#{method_name}" #This method executes: #url = __method__.to_s.split(...
send "first_#{method_name}" #This method executes: puts #url
end
end
end
before :test_method, :test_method_a do
#url = __method__.to_s.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
Short version: load loads code from a file. If you want to run code that you already have in a string, you can use eval, or one of it's friends, class_eval or instance_eval.
If you do end up using eval, however, you need to be very careful so that you won't accidentally run code that could delete your files, install malware or whatever.
Longer version: For a working version (in ruby 2.2.3) with load, you would need to put your TestClass class in a file:
class TestClass
def test_method
puts "OHAI"
end
end
Let's save this class in a file called "test_class.rb".
With that, the following should just work:
pr = -> { load(File.join(__dir__, "test_class.rb")) }
pr.call
TestClass.new.test_method
This will not solve your "original problem", but it should give you a little better understanding on how load works.
My own solution and more detail shown here after reading reply:
Maybe there is a lot of confusion about why does my problem method hook matter with load raw source code to Proc instance.
And also, I've solved my problem,now.
So,let me explain the whole thing in detail:
1, my original problem comes from that I need to extract a bunch of duplicated code at head of some methods of a module just like this:
module TestClass
extend self
def test_method
url = __method__.to_s.split("_").join("/") # duplicated code,need to extract
puts url
end
def test_method_a
url = __method__.to_s.split("_").join("/") # duplicated code, need to extract
puts url
end
2, Then, after a lot of thinking, I come up with an idea, that's get test_method test_method_a's source code, and modify the source code by adding url = __method__.to_s.split("_").join("/") at the head of it, then redefine the method with new code.
3, After lot of hack, I failed with using eval and then, post here asking help.
4, After reading reply, I make sure eval is definitely what I want.
5, Finally, I succeed, code show here:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar[1..-2].join("\n")
define_method(name, -> { eval(method_source_code) })
end
end
TestClass.test_method # => test/method
TestClass.test_method_a # => test/method/a
6, More concise code version show:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar.join("\n")
eval(method_source_code)
end
end
7, As eval,for me, I think the key to understand it is,thinking not as if you're in writing mode, but as if code was running line by line until reaching the eval line code, by that time, what do you want the code to do? Sure, just eval the method_source_code string.
Heres what I have/want:
module Observable
def observers; #observers; end
def trigger(event, *args)
good = true
return good unless (#observers ||= {})[event]
#obersvers[event].each { |e| good = false and break unless e.call(self, args) }
good
end
def on(event, &block)
#obersvers ||= {}
#obersvers[event] ||= []
#observers[event] << block
end
end
class Item < Thing
include Observable
def pickup(pickuper)
return unless trigger(:before_pick_up, pickuper)
pickuper.add_to_pocket self
trigger(:after_pick_up, pickuper)
end
def drop(droper)
return unless trigger(:before_drop, droper)
droper.remove_from_pocket self
trigger(:after_drop, droper)
end
# Lots of other methods
end
# How it all should work
Item.new.on(:before_pickup) do |item, pickuper|
puts "Hey #{pickuper} thats my #{item}"
return false # The pickuper never picks up the object
end
While starting on trying to create a game in Ruby, I thought it would be great if it could be based all around Observers and Events. The problem is have to write all of these triggers seems to be a waste, as it seems like a lot of duplicated code. I feel there must be some meta programming method out there to wrap methods with functionality.
Ideal Sceanrio:
class CustomBaseObject
class << self
### Replace with correct meta magic
def public_method_called(name, *args, &block)
return unless trigger(:before_+name.to_sym, args)
yield block
trigger(:after_+name.to_sym, args)
end
###
end
end
And then I have all of my object inherit from this Class.
I'm still new to Ruby's more advanced meta programming subjects, so any knowledge about this type of thing would be awesome.
There are a several ways to do it with the help of metaprogramming magic. For example, you can define a method like this:
def override_public_methods(c)
c.instance_methods(false).each do |m|
m = m.to_sym
c.class_eval %Q{
alias #{m}_original #{m}
def #{m}(*args, &block)
puts "Foo"
result = #{m}_original(*args, &block)
puts "Bar"
result
end
}
end
end
class CustomBaseObject
def test(a, &block)
puts "Test: #{a}"
yield
end
end
override_public_methods(CustomBaseObject)
foo = CustomBaseObject.new
foo.test(2) { puts 'Block!' }
# => Foo
Test: 2
Block!
Bar
In this case, you figure out all the required methods defined in the class by using instance_methods and then override them.
Another way is to use so-called 'hook' methods:
module Overrideable
def self.included(c)
c.instance_methods(false).each do |m|
m = m.to_sym
c.class_eval %Q{
alias #{m}_original #{m}
def #{m}(*args, &block)
puts "Foo"
result = #{m}_original(*args, &block)
puts "Bar"
result
end
}
end
end
end
class CustomBaseObject
def test(a, &block)
puts "Test: #{a}"
yield
end
include Overrideable
end
The included hook, defined in this module, is called when you include that module. This requires that you include the module at the end of the class definition, because included should know about all the already defined methods. I think it's rather ugly :)
I'm working on a class that reads some sensor information and returns it as a hash. I would like to use the hash keys as accessors, but I'm not having much luck getting it work. Here are the relevant parts of my code so far:
I've tried it both with method_missing and by using the :define_method method.
attr_reader :sensor_hash
def method_missing(name, *args, &blk)
if args.empty? && blk.nil? && #sensor_hash.has_key?(name.to_s)
#sensor_hash[name.to_s]
else
super
end
end
def sensor(*sensor_to_return)
sensor_output = run_command(this_method_name)
sensor_output = sensor_output.split("\n")
sensor_output.map! { |line| line.downcase! }
unless sensor_to_return.empty?
sensor_to_return = sensor_to_return.to_s.downcase
sensor_output = sensor_output.grep(/^#{sensor_to_return}\s/)
end
#sensor_hash = Hash.new
sensor_output.each { |stat| #sensor_hash[stat.split(/\s+\|\s?/)[0].gsub(' ','_').to_sym] = stat.split(/\s?\|\s?/)[1..-1].each { |v| v.strip! } }
#sensor_hash.each do |k,v|
puts v.join("\t")
self.class.send :define_method, k { v.join("\t") }
end
return #sensor_hash
The data returned is a hash with the sensor name as the key and and the value is an array of everything else returned. My goal is to be able to call Class.sensor.sensor_name and get the output of Class.sensor[:sensor_name]. Currently, all I'm able to get is an undefined method error. Anybody have any idea what I'm doing wrong here?
Maybe OpenStruct does what you want. From the doc :"It is like a hash with a different way to access the data. In fact, it is implemented with a hash, and you can initialize it with one."
require 'ostruct'
s=OpenStruct.new({:sensor_name=>'sensor1',:data=>['something',1,[1,2,3]]})
p s.sensor_name
#=> "sensor1"
Just a quick example. Do you have any reasons to not monkey-patch your Hash?
irb(main):001:0> class Hash
irb(main):002:1> def method_missing(name, *args, &blk)
irb(main):003:2> if self.keys.map(&:to_sym).include? name.to_sym
irb(main):004:3> return self[name.to_sym]
irb(main):005:3> else
irb(main):006:3* super
irb(main):007:3> end
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):012:0> h = {:hello => 'world'}
=> {:hello=>"world"}
irb(main):013:0> h.hello
=> "world"
You could use a wrapper class with method missing so you don't have to monkey patch Hash.
class AccessibleHash
def initialize(hash)
#hash = hash
end
def method_missing(name, *args, &block)
sname = name.to_sym
if #hash.keys.include? sname
return #hash[sname]
else
super
end
end
end
Or, if you are working with Rails it has some nice built in object delegation with SimpleDelegator. That would allow you to define accessors on your hash as well as any nested hashes within it.
class AccessibleHash < SimpleDelegator
def initialize
define_accessors(self.keys)
end
def define_accessors(keys)
keys.each do |key|
defind_accessors(body[key].keys)
self.define_singleton_method(key) { self[key] }
end
end
end
ah = AccessibleHash.new({ some: 'hash', with: { recursive: 'accessors' })
ah.with.recursive == 'accessors'
=> true
This would be less performant at instantiation than method_missing, because it has to run recursively over your delegatee object as soon as it's created. However, it's definitely safer than method_missing, and certainly way safer than monkey patching your Hash class. Of course, safety is relative to your goals, If it's all your application does then monkey patch away.
And if you want the recursive, nested accessors without rails you could do something like this with a combination of the above...
class AccessibleHash
def initialize(hash)
#hash = hash
define_accessors(#hash.keys)
end
def define_accessors(keys)
keys.each do |key|
#hash[key] = self.class.new(#hash[key]) if #hash.keys.present?
self.define_singleton_method(key) { self[key] }
end
end
end
But at that point you're getting pretty crazy and it's probably worth reevaluating your solution in favor of something more Object Oriented. If I saw any of these in a code review it would definitely throw up a red flag. ;)
I'm writing a module in Ruby 1.9.2 that defines several methods. When any of these methods is called, I want each of them to execute a certain statement first.
module MyModule
def go_forth
a re-used statement
# code particular to this method follows ...
end
def and_multiply
a re-used statement
# then something completely different ...
end
end
But I want to avoid putting that a re-used statement code explicitly in every single method. Is there a way to do so?
(If it matters, a re-used statement will have each method, when called, print its own name. It will do so via some variant of puts __method__.)
Like this:
module M
def self.before(*names)
names.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
yield
m.bind(self).(*args, &block)
end
end
end
end
module M
def hello
puts "yo"
end
def bye
puts "bum"
end
before(*instance_methods) { puts "start" }
end
class C
include M
end
C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"
This is exactly what aspector is created for.
With aspector you don't need to write the boilerplate metaprogramming code. You can even go one step further to extract the common logic into a separate aspect class and test it independently.
require 'aspector'
module MyModule
aspector do
before :go_forth, :add_multiply do
...
end
end
def go_forth
# code particular to this method follows ...
end
def and_multiply
# then something completely different ...
end
end
You can implement it with method_missing through proxy Module, like this:
module MyModule
module MyRealModule
def self.go_forth
puts "it works!"
# code particular to this method follows ...
end
def self.and_multiply
puts "it works!"
# then something completely different ...
end
end
def self.method_missing(m, *args, &block)
reused_statement
if MyModule::MyRealModule.methods.include?( m.to_s )
MyModule::MyRealModule.send(m)
else
super
end
end
def self.reused_statement
puts "reused statement"
end
end
MyModule.go_forth
#=> it works!
MyModule.stop_forth
#=> NoMethodError...
You can do this by metaprogramming technique, here's an example:
module YourModule
def included(mod)
def mod.method_added(name)
return if #added
#added = true
original_method = "original #{name}"
alias_method original_method, name
define_method(name) do |*args|
reused_statement
result = send original_method, *args
puts "The method #{name} called!"
result
end
#added = false
end
end
def reused_statement
end
end
module MyModule
include YourModule
def go_forth
end
def and_multiply
end
end
works only in ruby 1.9 and higher
UPDATE: and also can't use block, i.e. no yield in instance methods
I dunno, why I was downvoted - but a proper AOP framework is better than meta-programming hackery. And thats what OP was trying to achieve.
http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html
Another Solution could be:
module Aop
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_filter(method_name, options = {})
aop_methods = Array(options[:only]).compact
return if aop_methods.empty?
aop_methods.each do |m|
alias_method "#{m}_old", m
class_eval <<-RUBY,__FILE__,__LINE__ + 1
def #{m}
#{method_name}
#{m}_old
end
RUBY
end
end
end
end
module Bar
def hello
puts "Running hello world"
end
end
class Foo
include Bar
def find_hello
puts "Running find hello"
end
include Aop
before_filter :find_hello, :only => :hello
end
a = Foo.new()
a.hello()
It is possible with meta-programming.
Another alternative is Aquarium. Aquarium is a framework that implements Aspect-Oriented Programming (AOP) for Ruby. AOP allow you to implement functionality across normal object and method boundaries. Your use case, applying a pre-action on every method, is a basic task of AOP.