38

Could someone please help me to understand what the 'send()' method listed below is used for? The code below, when I am reading it, makes no sense what purpose it's serving.

It's a Rails app using Ruby 1.8.7 with Rails 1.2.3. Please don't harp on me about upgrading, it's a client's environment, so I don't have that sort of leisure.

Needless to say though, the statement I am referring to is like this;

def do_schedule
  @performance = Performance.new(params[:performance])
  @performer = Performer.find(params[:performer_id])
  selected_track = params[:selected_track]
  if FileTest.exists?(File.expand_path(@performer.photo))
    @performance.photo = File.open(File.expand_path(@performer.photo))
  end

  @performance.audio = File.open(File.expand_path(@performer.send(selected_track)))

  if @performance.save
    flash[:notice] = 'Performer scheduled.'
    redirect_to :controller => :performer, :action => :index
  else
    render :action => 'schedule'
  end
end

Performer Model

class Performer < ActiveRecord::Base
  file_column :audio_one
  file_column :audio_two
  file_column :audio_three
  file_column :photo

  belongs_to :festival
  validates_presence_of :name, :first_name, :last_name, :address, :city, :state, :zip, :daytime_phone, :availability, :stages
  validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
  validates_confirmation_of :email

  validates_presence_of :audio_one, :audio_two, :audio_three, :photo, :if => :submitted

  after_create :salt_access_key
  serialize :availability
  serialize :stages

  attr_accessor :other_type_of_music
  before_save :set_other_type

  def set_other_type
    if type_of_music == 'Other'
      self.type_of_music = "Other - #{other_type_of_music}" unless other_type_of_music.blank?
    end
  end

  def salt_access_key
    update_attribute(:access_key, Digest::SHA1.hexdigest("--#{self.id}--#{self.name}--#{self.festival.year}"))
  end

  def preferred_stages
    stages = []
    festival = Festival.find(self.festival_id.to_i)
    self.stages.collect { | key, value |
      id = key.gsub(/[\D]/, '').to_i
      if id > 0
        stages << festival.performance_stages.find(id).name
      end
    }
    return stages
  end
end

The controller that this is contained in is Performance. I have been scouring Google trying to figure out what purpose that '@performer.send(selected_track)' is actually doing, but feel like I'm rowing against a whirlpool.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Skittles
  • 2,866
  • 9
  • 31
  • 37

3 Answers3

71

The Ruby implementation for the send method, which is used to send a method message to an object, works like this:

class Car
  
  def start
    puts "vroom"
  end

  private

  def engine_temp
    puts "Just Right"
  end

end

@car = Car.new
@car.start # output: vroom
@car.send(:start) # output: vroom

That's the basics, an additional piece of important information is that send will allow you you send in messages to PRIVATE methods, not just public ones.

@car.engine_temp  # This doesn't work, it will raise an exception
@car.send(:engine_temp)  # output: Just Right

As for what your specific send call will do, more than likely there is a def method_missing in the Performer class that is setup to catch that and perform some action.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Chris Cherry
  • 28,118
  • 6
  • 68
  • 71
  • Thanks ctcherry! Great example! As for the method_missing reference you just made. No, there is actually only 4 methods in the Performer class. 3 (index, show, & destroy) and a private lookup_festival. Does that mean that that send is incapable of working properly? – Skittles Oct 25 '11 at 20:27
  • Send will still work. What it is triggering depends on what `selected_track` variable is that is being passed into it. You can log it and show it to us and we might be able to give you more information. – Chris Cherry Oct 25 '11 at 20:32
  • Also, are you looking in the Performer model or controller? You want to be looking at the model code for this send call issue. – Chris Cherry Oct 25 '11 at 20:33
  • @Skittles It's unlikely the `index`, `show`, and `destroy` methods are in the `Performer` class, which is almost certainly a model given the code in your question. Does the `Performer` model has either a `selected_track` method or DB column? – Dave Newton Oct 25 '11 at 20:35
  • @Dave Newton, I was looking in the Controller. I can paste the model for you guys if that would clear things up a little. There appears to only be one attr_accessor of :other_type_of_music and 3 defs of set_other_type, salt_access_key & preferred_stages. – Skittles Oct 25 '11 at 20:41
  • @Skittles Models also get methods based on their DB table definition. – Dave Newton Oct 25 '11 at 20:49
  • Looks like it would probably be loading one of the `file_columns` such as `:audio_one`, which is treated as a file name and then passed into `File.read` and the file data itself is assigned to `@performance.audio` – Chris Cherry Oct 25 '11 at 21:16
  • Ahhh...that makes sense! They have directories that are named the id of the performer and their mp3 files for that id reside inside that directory. Excellent! Thank you all for outstanding assistance! This was truly a great learning experience! :) – Skittles Oct 25 '11 at 22:13
  • So in this example anyone with the ability to send a request to the `do_schedule` action can call any method on `Performer`? Seems like a bad idea to me... – Ajedi32 Jul 16 '13 at 19:36
  • You are correct, in production code you would want to whitelist that param to make sure something malicious isn't going on. Or use a different method. The question was about explaining existing code – Chris Cherry Jul 16 '13 at 19:47
14

send is used to pass a method (and arguments) to an object. It's really handy when you don't know in advance the name of the method, because it's represented as a mere string or symbol.

Ex: Performer.find(params[:performer_id]) is the same as Performer.send(:find, params[:performer_id])

Beware here because relying on params when using send could be dangerous: what if users pass destroy or delete? It would actually delete your object.

apneadiving
  • 114,565
  • 26
  • 219
  • 213
  • Thanks apneadiving! I see. Some of this is beginning to make more sense now. And perhaps that is partially explaining an error that the page is throwing as the 'selected_track' variable was never passed to the do_schedule method from what I can tell. – Skittles Oct 25 '11 at 20:24
6

The send method is the equivalent of calling the given method on the object. So if the selected_track variable has a value of 1234, then @performer.send(selected_track) is the same as @performer.1234. Or, if selected_track is "a_whiter_shade_of_pale" then it's like calling @performer.a_whiter_shade_of_pale.

Presumably, then, the Performer class overrides method_missing such that you can call it with any track (name or ID, it isn't clear from the above), and it will interpret that as a search for that track within that performer's tracks.

Jacob Mattison
  • 50,258
  • 9
  • 107
  • 126
  • Thanks for the fast response, JacobM! Your answer really helps a lot. I think what's got me a tad confused is that the Performer class doesn't have a method defined that would be called as you described. Is your example of @performer.1234 indicative of the index method of Performer being called as a result of the send usage or what? I noticed that there's a before_filter being used in the Performer class also. – Skittles Oct 25 '11 at 20:21
  • 1
    Ah, the code you posted clarified things. The `method_missing` in question is in ActiveRecord::Base -- the way ActiveRecord works is that I can define a column (in the database) on a model, and then make a call to it even though it's not a defined method in the model. `Method_missing` catches that method call and ActiveRecord matches up the "method" I called with the column/attribute name. – Jacob Mattison Oct 26 '11 at 00:04
  • This is all starting to make much better sense now. I come from the old school of programming. PHP & Perl and am only now getting my head wrapped around object-driven strict ORM development structures. Thanks for the insight. It was greatly appreciated. :) – Skittles Oct 29 '11 at 15:14
  • Let me clarify that when I say "old school" I'm only using that to describe those languages loose coding principles. Been programming since the late 80's. :) – Skittles Oct 29 '11 at 15:16
  • "The send method is the equivalent of calling the given method on the object." is absolutely the best answer. – Michael Schmitz Oct 21 '12 at 00:10