Ruby / Methods / message passing

From WhyNotWiki

Jump to: navigation, search


Article Metadata:

Flag those sections that include non-core-ruby stuff (for example, requires another gem) with "Requires #{what_it_requires}"

intersection of [Category:Ruby:Messages] and [Category:Examples]: flag with heading prefix "[Example]".

Further classification:

  • concepts

Contents

[edit] Example of: Passing hash as if we had named parameters

Source: http://docs.rubyrake.org/read/chapter/4#pa

task :name => [:prereq1, :prereq2]

NOTE: Although this syntax looks a little funky, it is legal Ruby. We are constructing a hash where the key is :name and the value for that key is the list of prerequisites. It is equivalent to the following …

hash = Hash.new
hash[:name] = [:prereq1, :prereq2]
task(hash)

[edit] "returning": Tell it what you're returning in case you forget and accidentally return the result of a method call!

Part of ActiveSupport, but [Should be core Ruby (category)]

It also feels a little more concise and elegant and Rubyish...

(modified quote) (source: http://errtheblog.com/post/29)

def change_state(object_id, new_state)
  returning find(object_id) do |object|
    object.state = new_state
    object.save
  end
end

instead of:

def change_state(object_id, new_state)
  object = find(object_id)
  object.state = new_state
  object.save
  object
end

(/modified quote)

Because otherwise you might forget that Ruby automatically returns the result of the last line in the block and accidentally return object.save or something:

def change_state(object_id, new_state)
  object = find(object_id)
  object.state = new_state
  object.save
end

Another example:

def cd_rails_root
  returning(FileUtils.getwd) { FileUtils.cd(RAILS_ROOT) }
end

[edit] Silliness: alias and alias_method

I prefer to use alias_method rather than alias.

alias_method :orig_meth, :meth
def meth # override it...
alias_method :new_meth, :old_meth

The order of arguments is reversed for alias...stupidly.

[edit] [Caveat (category)]: Object#send invokes private methods

It totally bypasses any privacy restrictions that the author of that class may have put in. In other words, it lets you call methods that have been marked as "private".

It is arguably a [good thing (category)] that Ruby provides a way to call private methods -- for those times when you really need to -- but in general this should be considered a [bad practice (category)].

Sometimes, it can even lead to some unexpected behavior. For instance, when a private method by that name exists but a public method does not. Example:

This example is taken from a real-world problem we ran across with Rails.

We (Lance/Jim) discovered some really strange behavior where it threw an error if we tried to invoke a method via send (which the plugin in question (ActiveScaffold) was probably doing, since it was probably just looping through a list of column names) but it worked fine if we invoked the method directly!

record.send(:task)
=> ArgumentError (0 for 1)

but:

record.task
=> "test"

Very unintuitive for these two methods of calling a method should have different behavior!

The reason the send(:task) was raising an ArgumentError in our particular case was because we were somehow requiring "rake" somewhere in our code (something we probably shouldn't have been doing), and Rake was mixing in some Rake-specific private methods into the Object class (something it probably shouldn't have been doing), among them task. This private method was defined by Rake to expect 1 argument.

We were only passing 0 arguments, however, because we expected task to be a normal old attribute reader method for our ActiveRecord model, and obviously you shouldn't have to pass any arguments just to ask a model what the current value of one of its attributes is.

However, due I believe to some performance optimizations done by the ActiveRecord developers, those methods are not even created until they are needed. In other words, the first time you call an attribute reader, the message is caught by method_missing, which then dynamically creates the reader methods for you.

So in our case, we just got unlucky with our timing. If we had already accessed one of the models attributes in the "normal" (non-send) way prior to this point in the code, then the public method would have already existed, the private task method would not have been called, and this code would have worked as designed.

(Should also be filed under / transcluded by: "Caveat: ActiveRecord attribute accessor methods aren't created until first accessed", "Ruby / Problems with creating methods at run-time")

Wouldn't it be nice, then, if there were a "send" method that would only send to public methods?

Fortunately, the Facets core library provides just that — in the form of object_send. It will invoke the public method provided as its first argument, or (if there isn't a matching method by that name) method_missing, or (if there is no method_missing), raise raise an error (not sure if I like that behavior -- shouldn't it just send method_missing, in case we want a parent class to handle it?).

Currently (1.8.51) implemented as:

  # File lib/facets/core/kernel/object_send.rb, line 9
  def object_send(name,*args,&blk)
    #instance_eval "self.#{name}(*args)"
    if respond_to?(name)
      send(name,*args,&blk)
    elsif respond_to?(:method_missing)
      method_missing(name,*args,&blk)
    else
      raise NoMethodError
    end
  end

Question: Why is that in Kernel and not in Object?

Really, Object#send should only send to public methods (by default)! It should only send to private methods if you specifically request it to consider private methods as candidates (perhaps via a :ignore_privacy => true) option.

We shouldn't have to use a custom-written method to get the public-only behavior.

Personal tools