How to Detect the End of a Method Chain in Ruby

How to detect the end of a method chain in Ruby

Since I can't think of any way to detect the last method call, I'm going to suggest a different approach. It is similar to what ActiveRecord does; a proxy class that builds the options and doesn't fetch the data until you call a method that operates on the data.

class Flickr
def initialize
@result = FlickrResult.new
end

def method_missing(method, *args, &block)
if @result.data.respond_to?(method)
@result.run(method, args, block)
else
@result.append(method, args[0])
return self
end
end

class FlickrResult
attr_reader :data

def initialize
@data = []
@keys = []
@options = {}
end

def append(key, options)
@keys << key
@options.merge!(options) if options
end

def run(method, args, block)
if !@did_run
fetch_data
end

@data.send(method, *args, &block)
end

def fetch_data
puts "Only runs once!"
@url = @keys.join(".") + "?" + @options.map {|k, v| "#{k}=#{v}" }.join("&")
# use @url and fetch data..
@data = ["foo", "bar"]

@did_run = true
end
end
end

@flickr = Flickr.new
@flickr.groups(:group_id => "123").pools.thing.users(:user_id => "456")

@flickr.each {|f| p f }
# => "Only runs once!"
# => "foo"
# => "bar"
p @flickr.map {|f| f.upcase }
# => ["FOO", "BAR"]

It only fetches the data when you each or map it or whatever (any array method).

When method chaining, how can you determine which return value will be returned?

The return value is always the last thing in the chain, no exceptions. The result is not necessarily the thing you'd expect, though, as the last thing in the chain might do some tricky stuff.

For example, just to be clear:

Restaurant.new.import_line do |line|
line + '!'
end

There's no obligation here for import_line to return the result of that block, and it's often the case that it won't.

You can also have occasions where the thing you're chaining switches completely and catches you off guard so you have to be sure about the return values from each function in the chain you're calling.

Example here:

"help!".gsub!(/!/, '?').length
# => 5

"help?".gsub!(/!/, '?').length
#! NoMethodError: undefined method `length' for nil:NilClass

In this case gsub! returns the string if and only if a change was made, otherwise nil, which means you can't chain it in that case.

How to chain a method call to a `do ... end` block in Ruby?

yes, you can call a method here.

In your case,

array_variable = collection.map do |param|
# some value with param
end.compact

OR

array_variable = collection.map{ |param| some value with param }.compact

As pointed out by @Stefan, assignment is not required, you can directly use return and if that's the last line of method you can omit return too..

How does ActiveRecord detect last method call in chain?

You're seeing an artifact of using irb to investigate things.

When you say this:

> Product.first.title
#=> nil

Your method_missing will be called to lazy-load the title method and you get nil.

When you say this:

> Product.first

You're effectively doing this:

> p = Product.first; puts p.inspect

The first Product instance will be loaded and then irb will call inspect on it and AR will add the accessor methods along the way. The result is that Product will now have a title method. Hence, doing this:

> Product.first
> Product.first.title

won't call your method_missing at all as there will be a real title method for Product.first.title to call.

If you try again like this:

> Product.first; nil
> Product.first.title

You'll see two nils.


As far as chaining goes, ActiveRecord doesn't really detect the end, it is just that some method calls naturally require real data from the database and some don't.

If you call where, order, or any of the other querying methods, you get an ActiveRecord::Relation instance back and you can chain more query methods and scopes on that relation object. For example, where (which ActiveRecord::Relation gets by including ActiveRecord::QueryMethods) looks like this:

def where(opts, *rest)
return self if opts.blank?

relation = clone
relation.where_values += build_where(opts, rest)
relation
end

so it just makes a copy of the current query, adds a few things to the copy, and gives you the copy back.

If you call first, last, to_a, all, any of the Enumerable methods (i.e. you call each), ... then you're asking about specific instances and ActiveRecord will have to execute the query to realize the model instance(s) in question. For example, ActiveRecord::Relation#to_a looks like this:

def to_a
logging_query_plan do
exec_queries
end
end

and all is little more than a wrapper around to_a.

ActiveRecord doesn't really know where the end of the chain is, it just doesn't load anything from the database until it has to so you tell it where the chain ends by saying go forth and retrieve me some data.

Nils and method chaining

I think you can find a great solution in rails but that solution follows a different approach. Take a look at the try method. It's a clean approach.

How to chain methods in ruby passing the output of one method to consecutive methods

You can leave the ()

def a s
"hello #{s}"
end

def b s
"hi #{s}"
end

puts b a "Tom"

If you have many methods :

puts [:a,:b].inject("Tom"){|result,method| self.send(method,result)}

If you want to use those methods with any object (including Classes) :

module Kernel
def chain_methods(start_value, *methods)
methods.inject(start_value){|result,method| self.send(method,result)}
end
end

class D
def a s
"hello #{s}"
end

def b s
"hi #{s}"
end
end

class E
class << self
def a s
"hello #{s}"
end

def b s
"hi #{s}"
end
end
end

# Example with instance methods
puts D.new.chain_methods("Tom", :a, :b)

# Example with class methods
puts E.chain_methods("Tom", :a, :b)

# Thanks mudasobwa :
E.chain_methods("Tom", :a, :b, :puts)


Related Topics



Leave a reply



Submit