Ruby - Code someone explain the following code - ruby-on-rails

I have the following piece of code in a library, Could someone explain what does the code ("#{k}=") means in the following piece of code?
if respond_to?("#{k}=")
public_send("#{k}=", v)
else
raise UnknownAttributeError.new(self, k)
end
I understand respond_to is a default function in Ruby but there is no definition/explanation given for this syntax, please help us.
Edited :
I get the exception (unknown attribute 'token' for PersonalAccessToken. (ActiveModel::UnknownAttributeError)) for the above code
/opt/gitlab/embedded/lib/ruby/gems/2.5.0/gems/activemodel-5.0.7.1/lib/active_model/attribute_assignment.rb:40:in `block in _assign_attributes'
/opt/gitlab/embedded/lib/ruby/gems/2.5.0/gems/activemodel-5.0.7.1/lib/active_model/attribute_assignment.rb:48:in `_assign_attribute': unknown attribute 'token' for PersonalAccessToken. (ActiveModel::UnknownAttributeError)
so considering k as 'token', In which scenario will I get the exception (In which scenario It goes to the else state?)

This code public_send("#{k}=", v) dynamically calls a setter for what is stored in k variable. Consider the following example:
class FooBarBaz
attr_accessor :foo, :bar, :baz
def set_it what, value
public_send("#{what}=", value)
end
end
It’s roughly an equivalent to:
def set_it what, value
case what
when "foo" then public_send("foo=", value)
when "bar" then public_send("bar=", value)
when "baz" then public_send("baz=", value)
end
end
It’s roughly an equivalent to:
def set_it what, value
case what
when "foo" then self.foo=(value)
...
end
end
It’s roughly an equivalent to:
def set_it what, value
case what
when "foo" then self.foo = value
...
end
end
respond_to? is called in advance to check if the setter in indeed defined for this k on this instance, to prevent somewhat like:
FooBarBaz.new.set_it :inexisting, 42
#⇒ NoMethodError: undefined method `inexisting=' for #<FooBarBaz:0x0056247695a538>
A modified, correct version of the class in this answer:
class FooBarBaz
attr_accessor :foo, :bar, :baz
def set_it what, value
public_send("#{what}=", value) if respond_to?("#{what}=")
end
end
It does not throw an exception.
FooBarBaz.new.set_it :inexisting, 42
#⇒ nil

"#{}" is string interpolation in ruby. so for example:
k = 'world'
puts "hello #{k}"
# hello world
So in your example it looks like it is creating a string with the value of k and =
e.g.
k = 'something'
"#{k}="
# something=
If you want to know what k is, you can add puts k.to_s in the line above then run the code and check your console.
Better still if you are using something like RubyMine, just use the debugger and stick a breakpoint on that line.

I think that you saw the _assign_attribute method in this docs
k is the abbreviation meaning of 'key', v is same thing meaning of 'value'
"#{k}=" is some kind of method name to dynamic method by string literal.
It may be "update" or "create", "split", anything else.
Equal symbol("=") is meaning of they are methods to assign something like this user.attributes = { :username => 'Phusion', :is_admin => true }
In the above case, "k" is .attributes and "k=" is attributes= and "v" is { :username => 'Phusion', :is_admin => true }
And public_send is method to send a method in public scope.
As a result public_send("#{k}=", v) is meaning of call a method having a name as "k" in public method, and this method will be assigned to "v" as the value.
I hope that this explain help to you.
Add some example for comment
k is a input from programmer, therefore it does not match method name in Class or Module.
In Reality, there are common mistake case.
class User
# this attribute can be called from a instance of User
attr_accessor :name
end
# this causes a error when call assign_attribute
User.new.wrong_name
# this is fine
User.new.name
assignment method reference

Related

How to call local variable value in a method through a parameter?

In my Rails app I have this (rather silly) method:
def my_method(param)
foo = "hey"
bar = "ho"
if param == :foo
return foo
elsif param == :bar
return bar
end
end
I don't like the if/else block, though.
Is there a simpler way to return the value of the local variable foo if :foo is provided as a parameter?
Or will I have to use an array or a hash here?
If you're using the very latest Ruby, you can use binding.local_variable_get(param). A hash seems cleaner to me, but your mileage may vary.
This should look simpler, don't think introducing a new data structure is required:
def my_method(param)
return 'hey' if param == :foo
return 'ho' if param == :bar
end
You can use a Hash:
def my_method(param)
objs = {
foo: "hey",
bar: "ho"
}
objs[param]
end
This is really a good time to use a case statement:
def my_method(param)
case param
when :foo
'hey'
when :bar
'ho'
else
# what do you want to do here?
end
end
Something to consider is, you're using an if/elseif, but what happens if neither of those hit? Do you want to return nil, or trap an error? As you look around in other people's code, you'll sometimes find long chains of if/elseif tests, with no final else, which opens up a potential logic error and can result in a hard-to-find bug.

metaprograming String#scan and globals?

My goal is to replace methods in the String class with other methods that do additional work (this is for a research project). This works for many methods by writing code in the String class similar to
alias_method :center_OLD, :center
def center(args*)
r = self.send(*([:center_OLD] + args))
#do some work here
#return something
end
For some methods, I need to handle a Proc as well, which is no problem. However, for the scan method, invoking it has the side effect of setting special global variables from the regular expression match. As documented, these variables are local to the thread and the method.
Unfortunately, some Rails code makes calls to scan which makes use of the $& variable. That variable gets set inside my version of the scan method, but because it's local, it doesn't make it back to the original caller which uses the variable.
Does anyone know a way to work around this? Please let me know if the problem needs clarification.
If it helps at all, all the uses I've seen so far of the $& variable are inside a Proc passed to the scan function, so I can get the binding for that Proc. However, the user doesn't seem to be able to change $& at all, so I don't know how that will help much.
Current Code
class String
alias_method :scan_OLD, :scan
def scan(*args, &b)
begin
sargs = [:scan_OLD] + args
if b.class == Proc
r = self.send(*sargs, &b)
else
r = self.send(*sargs)
end
r
rescue => error
puts error.backtrace.join("\n")
end
end
end
Of course I'll do more things before returning r, but this even is problematic -- so for simplicity we'll stick with this. As a test case, consider:
"hello world".scan(/l./) { |x| puts x }
This works fine both with and without my version of scan. With the "vanilla" String class this produces the same thing as
"hello world".scan(/l./) { puts $&; }
Namely, it prints "ll" and "ld" and returns "hello world". With the modified string class it prints two blank lines (since $& was nil) and then returns "hello world". I'll be happy if we can get that working!
You cannot set $&, because it is derived from $~, the last MatchData.
However, $~ can be set and that actually does what you want.
The trick is to set it in the block binding.
The code is inspired by the old Ruby implementation of Pathname.
(The new code is in C and does not need to care about Ruby frame-local variables)
class String
alias_method :scan_OLD, :scan
def scan(*args, &block)
sargs = [:scan_OLD] + args
if block
self.send(*sargs) do |*bargs|
Thread.current[:string_scan_matchdata] = $~
eval("$~ = Thread.current[:string_scan_matchdata]", block.binding)
yield(*bargs)
end
else
self.send(*sargs)
end
end
end
The saving of the thread-local (well, actually fiber-local) variable seems unnecessary since it is only used to pass the value and the thread never reads any other value than the last one set. It probably is there to restore the original value (most likely nil, because the variable did not exist).
One way to avoid thread-locals at all is to create a setter of $~ as a lambda (but it does create a lambda for each call):
self.send(*sargs) do |*bargs|
eval("lambda { |m| $~ = m }", block.binding).call($~)
yield(*bargs)
end
With any of these, your example works!
I wrote simple code simulating the problem:
"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $&; }
class String
alias_method :origin_scan, :scan
def scan *args, &b
args.unshift :origin_scan
#mutex ||= Mutex.new
begin
self.send *args do |a|
break if !block_given?
#mutex.synchronize do
p $&
case b.arity
when 0
b.call
when 1
b.call a
end
end
end
rescue => error
p error, error.backtrace.join("\n")
end
end
end
"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $& }
And found the following. The change of containment of the variable $& became inside a :call function, i.e. on 3-rd step before :call $& contains a valid value, but inside the block it becomes the invalid. I guess this become due to the singularity stack and variable restoration during the change process/thread context, because, probably, :call function can't access the :scan local state.
I see two variants: the first is to avoid to use global variables in the specific function redefinitions, and second, may to dig sources of ruby more deeply.

How to get a list of the arguments a method is called with

How do I get a list of the arguments passed to a method, preferably one that I can iterate through?
For example something like
def foo(a,b,c)
puts args.inspect
end
foo(1,2,3)
=> [1,2,3]
?
Thanks!
You can always define a method that takes an arbitrary number of arguments:
def foo(*args)
puts args.inspect
end
This does exactly what you want, but only works on methods defined in such a manner.
The *args notation means "zero or more arguments" in this context. The opposite of this is the splat operator which expands them back into a list, useful for calling other methods.
As a note, the *-optional arguments must come last in the list of arguments.
If you define your method as you specified, you'll always have 3 args, or the method call is invalid. So "all the args" is already defined for you. So you would just change your method to:
def foo(a,b,c)
[a, b, c]
end
To define a method that can be called with any args (and to then access those args) you can do something like this:
def foo(*args)
args
end
What the * does is put all args after that point into an array.
As others pointed out you can use the splat operator (*) for achieving what you want. If you don't like that, you can use the fact that Ruby methods can take a hash as last argument with nicer syntax.
def foo(args)
raise ArgumentError if args.keys.any? { |arg| arg.nil? || !arg.kind_of?(Integer) }
end
puts foo(:a => 1, :b => 2, :c => "a") # raise an ArgumentError
To access the arguments inside the method you have to use args[:a] etc.

Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]

This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]

Adding data of an unknown type to a hash

Ruby seems like a language that would be especially well suited to solving this problem, but I'm not finding an elegant way to do it. What I want is a method that will accept a value and add it to a hash like so, with specific requirements for how it is added if the key already exists:
Adding 'foo' to :key1
{:key1 => 'foo'}
Adding 'bar' to :key1
{:key1=> 'foobar'}
Adding ['foo'] to :key2
{:key2 = ['foo']}
Adding ['bar'] to :key2
{:key2 => [['foo'], ['bar']]
Adding {:k1 => 'foo'} to :key3
{:key3 => {:k1 => 'foo'}}
Adding {:k2 => 'bar'} to :key3
{:key3 => {:k1 => 'foo', :k2 => 'bar'}}
Right now I can do this but it looks sloppy and not like idiomatic Ruby. What is a good way to do this?
To make it more Ruby-like you might want to extend the Hash class to provide this kind of functionality across the board, or make your own subclass for this specific purpose. For instance:
class FancyHash < Hash
def add(key, value)
case (self[key])
when nil
self[key] = value
when Array
self[key] = [ self[key], value ]
when Hash
self[key].merge!(value)
else
raise "Adding value to unsupported #{self[key].class} structure"
end
end
end
This will depend on your exact interpretation of what "adding" means, as your examples do seem somewhat simplistic and don't address what happens when you add a hash to a pre-existing array, among other things.
The idea is that you define a handler that accommodates as many possibilities as reasonable and throw an exception if you can't manage.
If you want to utilize the polymorphic feature of oop, you might want to do:
class Object; def add_value v; v end end
class String; def add_value v; self+v end end # or concat(v) for destructive
class Array; def add_value v; [self, v] end end # or replace([self.dup, v]) for destructive
class Hash; def add_value v; merge(v) end end # or merge!(v) for destructive
class Hash
def add k, v; self[k] = self[k].add_value(v) end
end

Resources