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 nil
s.
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
Appending Headers to Rspec Controller Tests
How to Set the Rails Environment for My Somewhat Stand Alone Ruby Script
Write a Migration with Reference to a Model Twice
How to Remove a Non-Breaking Space in Ruby
Adding Username to Devise Rails 4
Understanding Rails Instance Variables
How to Set Custom User-Agent for Mechanize in Rails
Alias_Method and Class_Methods Don't Mix
Equivalent of Iconv.Conv("Utf-8//Ignore",...) in Ruby 1.9.X
How to Mock Aws Sdk (V2) with Rspec
In a Sinatra App on Heroku, Session Is Not Shared Across Dynos
How I Know My Document's Size Inside Mongodb with the Ruby Driver
Ruby Rest-Client File Upload as Multipart Form Data with Basic Authenticaion
Installing MySQL2 Gem for Ruby on Rails 3.1.0
Installing Native Ruby Extensions on Windows for Jekyll