Why is this ruby method not being defined in my class? - ruby

Here's the code:
module Dog
class Breed < Animal::Base
class << self
def all
get '/v1/breeds'
end
def find(hashed_id)
get "/v1/breeds/#{breed_id}"
end
end
def bark
"woof"
end
end
end
And for the base:
module Dog
class Base < ActiveRecord::Base
include HTTParty
base_uri 'https://api.dogs.com'
format :json
default_params api_password: ENV['ANIMAL_PASSWORD']
end
end
module HTTParty
module ClassMethods
def get(path, options = {}, &block)
response = perform_request(Net::HTTP::Get, path, options, &block)
if response.is_a? Array
methodize_array response
elsif response.is_a? Hash
new_ros response
end
end
def methodize_array(response)
array = []
response.each do |res|
array << new_ros(res)
end
array
end
private
def new_ros(object)
RecursiveOpenStruct.new(object, recurse_over_arrays: true)
end
end
end
This is a silly example, but it should work in theory. What happens is is that we grab some data from an API. When that data is grabbed, we discover that its a Hash. We don't like Hashes, so we reopen the get request within HTTParty and have it perform a recursive open struct to make it an object.
We perform this get as Dog::Breed.all. We receive an array that, thankfully to ROS, was converted to an object.
Now, when I call Dog::Breed.all.bark it doesn't work:
undefined method `bark' for #<Array:0x007fc7acbb6108>
If I make it:
def self.bark
"woof"
end
And then call Dog::Breed.bark, it will woof at me. How do I make it so that I can add methods to the Breed class so that I can do Dog::Breed.all.bark or Dog::Breed.find(2).bark?
The output from Dog::Breed.all was a #<RecursiveOpenStruct>. Disclaimer: That was an example case, not real life, dog.com leads to petsmart.

Dog::Breed.all returns an Array of Breed, not Breed. If you want all dogs to bark you need to iterate over the array, and make each one bark:
Dog::Breed.all.each(&:bark)

Related

How could I implement something like Rails' before_initialize/before_new in plain Ruby?

In Rails we can define a class like:
class Test < ActiveRecord::Base
before_initialize :method
end
and when calling Test.new, method() will be called on the instance. I'm trying to learn more about Ruby and class methods like this, but I'm having trouble trying to implement this in plain Ruby.
Here's what I have so far:
class LameAR
def self.before_initialize(*args, &block)
# somehow store the symbols or block to be called on init
end
def new(*args)
## Call methods/blocks here
super(*args)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I'm trying to figure out where to store the blocks in self.before_initialize. I originally tried an instance variable like #before_init_methods, but that instance variable wouldn't exist in memory at that point, so I couldn't store or retrieve from it. I'm not sure how/where could I store these blocks/procs/symbols during the class definition, to later be called inside of new.
How could I implement this? (Either having before_initialize take a block/proc/list of symbols, I don't mind at this point, just trying to understand the concept)
For a comprehensive description, you can always check the Rails source; it is itself implemented in 'plain Ruby', after all. (But it handles lots of edge cases, so it's not great for getting a quick overview.)
The quick version is:
module MyCallbacks
def self.included(klass)
klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
end
module ClassMethods
def initialize_callbacks
#callbacks ||= []
end
def before_initialize(&block)
initialize_callbacks << block
end
end
def initialize(*)
self.class.initialize_callbacks.each do |callback|
instance_eval(&callback)
end
super
end
end
class Tester
include MyCallbacks
before_initialize { puts "hello world" }
end
Tester.new
Left to the reader:
arguments
calling methods by name
inheritance
callbacks aborting a call and supplying the return value
"around" callbacks that wrap the original invocation
conditional callbacks (:if / :unless)
subclasses selectively overriding/skipping callbacks
inserting new callbacks elsewhere in the sequence
... but eliding all of those is what [hopefully] makes this implementation more approachable.
One way would be by overriding Class#new:
class LameAR
def self.before_initialize(*symbols_or_callables, &block)
#before_init_methods ||= []
#before_init_methods.concat(symbols_or_callables)
#before_init_methods << block if block
nil
end
def self.new(*args, &block)
obj = allocate
#before_init_methods.each do |symbol_or_callable|
if symbol_or_callable.is_a?(Symbol)
obj.public_send(symbol_or_callable)
else
symbol_or_callable.(obj)
end
end
obj.__send__(:initialize, *args, &block)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end

Ruby method chaining with an Enumerable class

I'm attempting to adapt the method-chaining example cited in this posting (Method chaining and lazy evaluation in Ruby) to work with an object that implements the Enumerable class (Implement a custom Enumerable collection class in Ruby )
Coffee class:
class Coffee
attr_accessor :name
attr_accessor :strength
def initialize(name, strength)
#name = name
#strength = strength
end
def <=>(other_coffee)
self.strength <=> other_coffee.strength
end
def to_s
"<name: #{name}, strength: #{strength}>"
end
end
Criteria class:
class Criteria
def initialize(klass)
#klass = klass
end
def criteria
#criteria ||= {:conditions => {}}
end
# only show coffee w/ this strength
def strength(strength)
criteria[:strength] = strength
self
end
# if there are multiple coffees, choose the first n=limit
def limit(limit)
criteria[:limit] = limit
self
end
# allow collection enumeration
def each(&block)
#klass.collection.select { |c| c[:strength] == criteria[:strength] }.each(&block)
end
end
CoffeeShop class:
class CoffeeShop
include Enumerable
def self.collection
#collection=[]
#collection << Coffee.new("Laos", 10)
#collection << Coffee.new("Angkor", 7)
#collection << Coffee.new("Nescafe", 1)
end
def self.limit(*args)
Criteria.new(self).limit(*args)
end
def self.strength(*args)
Criteria.new(self).strength(*args)
end
end
When I run this code:
CoffeeShop.strength(10).each { |c| puts c.inspect }
I get an error:
criteria.rb:32:in block in each': undefined method '[]' for #<Coffee:0x007fd25c8ec520 #name="Laos", #strength=10>
I'm certain that I haven't defined the Criteria.each method correctly, but I'm not sure how to correct it. How do I correct this?
Moreover, the each method doesn't support the limit as currently written. Is there a better way to filter the array such that it is easier to support both the strength and limit?
Other coding suggestions are appreciated.
Your Coffee class defines method accessors for name and strength. For a single coffee object, you can thus get the attributes with
coffee.name
# => "Laos"
coffee.strength
# => 10
In your Criteria#each method, you try to access the attributes using the subscript operator, i.e. c[:strength] (with c being an Instance of Coffee in this case). Now, on your Coffee class, you have not implemented the subscript accessor which resulting in the NoMethodError you see there.
You could thus either adapt your Criteria#each method as follows:
def each(&block)
#klass.collection.select { |c| c.strength == criteria[:strength] }.each(&block)
end
or you could implement the subscript operators on your Coffee class:
class Coffee
attr_accessor :name
attr_accessor :strength
# ...
def [](key)
public_send(key)
end
def []=(key, value)
public_send(:"#{key}=", value)
end
end
Noe, as an addendum, you might want to extend your each method in any case. A common (and often implicitly expected) pattern is that methods like each return an Enumerator if no block was given. This allows patterns like CoffeeShop.strength(10).each.group_by(&:strength).
You can implement this b a simple on-liner in your method:
def each(&block)
return enum_for(__method__) unless block_given?
#klass.collection.select { |c| c.strength == criteria[:strength] }.each(&block)
end

Ruby assignment methods won't receive a block?

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.

Add method-call to setter (serialize)

I have several classes that are serialized to a file with YAML. In order to serialize it when an attribute changes, I've implement custom setters for each of them:
def serialize
File.open(#inipath, 'w') do |file|
file << YAML.dump(self)
end
end
def numbering=(value)
#numbering = value
serialize
end
def savepath=(value)
#savepath = value
serialize
end
def active=(value)
#active = value
serialize
end
...
Can this be done without the repetition?
I would probably use a bit of metaprogramming secret sauce here.
Here's the sauce:
module OptInSerialization
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def serialize_on_changes_in(*names)
names.each do |name|
alias_method "old_#{name}=", "#{name}="
define_method "#{name}=" do |val|
send("old_#{name}=", val)
serialize
end
end
end
end
end
Here's how you use it:
class Foo
include OptInSerialization
attr_accessor :hello, :there
serialize_on_changes_in :hello
def serialize
puts 'serialized'
end
end
f = Foo.new
f.hello = '1234' # >> serialized
f.there = 'asdf'
Note that here serialization is not triggered by assigning to there, because you didn't specify it.
Disclaimer: This MP magic may be way above your current level, so don't use it, if you don't understand it.
Here ClassName would be name of the Model and column_names will give you all the columns of that model.
ClassName.column_names.each do |type|
define_method("#{type}=(value)") do
eval"##{type}"
instance_variable_set("##{type}",value )
serialize
end
def column_names
['numbering','savepath']
end

Ruby: Performing additional commands on delegated method

I'd like to use delegate to pass map from a string on to chars, then join back to a string result.
require 'forwardable'
module Map
module String
extend Forwardable
def self.included(base)
base.send :extend, Forwardable
end
# Map for String
delegate map: :chars
end
end
class String
include Map::String
end
As it's originally a string I'd like to perform join after the delegated method has performed its duties. How do I modify the delegate line to include the additional change? The closest thing I've seen online is SimpleDelegator with __setobj__. But that's not well documented and I'm not able to ascertain how to use it for this.
I'm strictly looking for an answer in regards to delegate or SimpleDelegate
The equivalent behavior I'm looking for is this:
module Map
module String
def map(*args, &block)
if (!args.compact.empty? || !block.nil?)
self.chars.map(*args,&block).join
else
self.chars.map(*args,&block)
end
end
end
end
class String
include Map::String
end
I'm looking to understand how to do this with delegate.
The Fowardable docs are hilarious--as if that first example will run without a hundred errors. Your pseudo code tells ruby to forward the method call String#map, which doesn't exist, to String#chars, and you want to join() the result of that? Skip all the method calls and just write puts "some_string". So your question doesn't seem to make a lot of sense. In any case, Forwardable#delegate() does not allow you to map one name to another name.
With regards to SimpleDelegat**or**, you can do this:
module Map
require 'delegate'
class MyStringDecorator < SimpleDelegator
def map
chars.shuffle.join('|')
end
end
end
d = Map::MyStringDecorator.new 'hello'
puts d.map
--output:--
h|o|l|l|e
Response to edit: The equivalent behavior I'm looking for..
The problem is ruby won't let you do this:
class String < SomeClass
end
which is what include does, and you need to be able to do that in order to use delegate to forward all the method calls sent to one class to another class. This is the best you can do:
require 'delegate'
class MyString < DelegateClass(String)
def map(*args, &block)
if (!args.compact.empty? || !block.nil?)
self.chars.map(*args,&block).join
else
self.chars.map(*args,&block)
end
end
end
s = MyString.new 'hello'
puts s.upcase
puts s.map {|letter| letter.succ }
--output:--
HELLO
ifmmp
Or:
require 'forwardable'
class MyString
extend Forwardable
def initialize(str)
#str = str
end
def_delegators :#str, :upcase, :capitalize, :[], :chars #etc., etc., etc.
#Or: delegate({[:upcase, :capitalize, :[], :chars] => :#str})
#Or: instance_delegate({[:upcase, :capitalize, :[], :chars] => :#str})
def map(*args, &block)
if (!args.compact.empty? || !block.nil?)
self.chars.map(*args,&block).join
else
self.chars.map(*args,&block)
end
end
end
s = MyString.new('hello')
puts s.upcase
puts s.map {|letter| letter.succ }
--output:--
HELLO
ifmmp
Of course, you could always override String#method_missing() to do what you want. What is it that you read about delegate that made you think it could replace include?

Resources