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
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.
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?
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.
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.
You can use instance_eval
:
obj.instance_eval("foo.bar")
You can even access the instance variable directly:
obj.instance_eval("@foo.bar")
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
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With