Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby nested send

Say I have an object with a method that accesses an object:

def foo
   @foo
end

I know I can use send to access that method:

obj.send("foo")  # Returns @foo

Is there a straightforward way to do a recursive send to get a parameter on the @foo object, like:

obj.send("foo.bar")  # Returns @foo.bar
like image 927
Lynn Avatar asked Apr 07 '13 12:04

Lynn


People also ask

What are the different types of routing paradigms in Ruby?

This comes in handy when you’re trying to define a route that is different from the seven routes defined by the RESTful routing by default. An example of such a route can be a preview or search feature. Ruby offers two different kinds of routing paradigms to handle such routes and their context – model routes and collection routes.

What is an example of a nested attribute?

The simplest example of Nested Attributes is with a one-to-one association. To add Nested Attributes support to the product model all you need to do is add the following line: What does this do, exactly?

Why Ruby on Rails is the best choice for web development?

One of the reasons why Ruby on Rails is considered a strong contender in the world of web development and is largely favored by startups worldwide is because it is highly time-efficient. One of the aspects that makes Ruby easy to use is its super-simple routing setup.

What is a resource in Ruby?

In the case of Ruby, it is generally a database table, which can be represented by a model, and accessed by a controller. For instance, a typical Post resource in a Ruby application would be linked with a posts table in the database. It would also have a controller mapped to it, namely posts_controller by map.resources :posts.


3 Answers

You can use instance_eval:

obj.instance_eval("foo.bar")

You can even access the instance variable directly:

obj.instance_eval("@foo.bar")
like image 196
Michaël Witrant Avatar answered Oct 19 '22 05:10

Michaël Witrant


While OP has already accepted an answer using instance_eval(string), I would strongly urge OP to avoid string forms of eval unless absolutely necessary. Eval invokes the ruby compiler -- it's expensive to compute and dangerous to use as it opens a vector for code injection attacks.

As stated there's no need for send at all:

obj.foo.bar

If indeed the names of foo and bar are coming from some non-static calculation, then

obj.send(foo_method).send(bar_method)

is simple and all one needs for this.

If the methods are coming in the form of a dotted string, one can use split and inject to chain the methods:

'foo.bar'.split('.').inject(obj, :send)

Clarifying in response to comments: String eval is one of the riskiest things one can do from a security perspective. If there's any way the string is constructed from user supplied input without incredibly diligent inspection and validation of that input, you should just consider your system owned.

send(method) where method is obtained from user input has risks too, but there's a more limited attack vector. Your user input can cause you to execute any 0-arghument method dispatchable through the receiver. Good practise here would be to always whitelist the methods before dispatching:

VALID_USER_METHODS = %w{foo bar baz}
def safe_send(method)
  raise ArgumentError, "#{method} not allowed" unless VALID_USER_METHODS.include?(method.to_s)
  send(method)
end
like image 25
dbenhur Avatar answered Oct 19 '22 07:10

dbenhur


A bit late to the party, but I had to do something similar that had to combine both 'sending' and accessing data from a hash/array in a single call. Basically this allows you to do something like the following

value = obj.send_nested("data.foo['bar'].id")

and under the hood this will do something akin to

obj.send(data).send(foo)['bar'].send(id)

This also works with symbols in the attribute string

value = obj.send_nested('data.foo[:bar][0].id')

which will do something akin to

obj.send(data).send(foo)[:bar][0].send(id)

In the event that you want to use indifferent access you can add that as a parameter as well. E.g.

value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)

Since it's a bit more involved, here is the link to the gist that you can use to add that method to the base Ruby Object. (It also includes the tests so that you can see how it works)

like image 1
prschmid Avatar answered Oct 19 '22 07:10

prschmid