3

I'm new to Ruby and I would like to find out what the best way of doing things is.

Assume the following scenario:

I have a text field where the user can input strings. Based on what the user inputs (after validation) I would like to access different fields of an instance variable.

Example: @zoo is an instance variable. The user inputs "monkey" and I would like to access @zoo.monkey. How can I do that in Ruby?

One idea that crossed my mind is to have a hash:

zoo_hash = { "monkey" => @zoo.monkey, ... }

but I was wondering if there is a better way to do this?

Thanks!

4 Answers 4

3

@zoo.attributes gives you a hash of the object attributes. So you can access them like

@zoo.attributes['monkey']

This will give nil if the attribute is not present. Calling a method which doesn't exist will throw NoMethodError

Sign up to request clarification or add additional context in comments.

4 Comments

+1: This is safer and more maintainable than a send approach
It would be neat if this were true, but it isn't in any release of Ruby I'm aware of. Maybe this is a Rails thing?
Ah, I did just notice the ruby-on-rails tag, so maybe that's okay.
You can use instance_variable_get, it's vanilla ruby.
3

In your controller you could use the public_send (or even send) method like this:

def your_action
  @zoo.public_send(params[:your_field])
end

Obviously this is no good, since someone can post somehing like delete_all as the method name, so you must sanitize the value you get from the form. As a simple example:

ALLOWED_METHODS = [:monkey, :tiger]

def your_action
  raise unless ALLOWED_METHODS.include?(params[:your_field])
  @zoo.public_send(params[:your_field])
end

2 Comments

The problem here is maintaining the ALLOWED_METHOD constant. That's technical debt.
The edit does solve the problem of safe methods, but why bother when you can specifically target attributes?
2

There is much better way to do this - you should use Object#send or (even better, because it raises error if you try to call private or protected method) Object#public_send, like this:

message = 'monkey'
@zoo.public_send( message )

5 Comments

This is dangerous, see toro2k's answer.
rmihalyi didn't say the @zoo was an ActiveRecord object. :)
We're talking about fields and attributes, not sending any message to an object, especially if the message comes from the user! How about instance_eval { system "ifconfig" } ? shell access!
I'm not defending my answer here. It's incomplete indeed, it shows only the way to achieve the target, not full implementation. Should I delete it, since there are more complete answers?
It's ok, it informs about a pitfall. You should leave it I think.
0

You could implement method_missing in your class and have it interrogate @zoo for a matching method. Documentation: http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-method_missing

require 'ostruct' # only necessary for my example

class ZooKeeper

  def initialize
    @zoo = OpenStruct.new(monkey: 'chimp')
  end

  def method_missing(method, *args)
    if @zoo.respond_to?(method)
      return @zoo.send(method)
    else
      super
    end
  end
end

keeper = ZooKeeper.new
keeper.monkey  #=> "chimp"
keeper.lion    #=> NoMethodError: undefined method `lion'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.