Related
I got a sample code that turns a block into an object using Proc.new so that it can be executed independently. There are two puts at the end to print the result, in this example, the array is brought into the block for execution. At this point, I have something that I don't understand.
In the following code, why is .iterate!(square) valid? They are the method defined in the class and the name of a separate block object. Why can they be written together?
Second, what is the working process of def iterate!(code)? Why is the value being carried into (code)? And what are self and code.call here?
square = Proc.new do |n|
n ** 2
end
class Array
def iterate!(code)
self.map {|n| code.call(n)}
end
end
puts [1, 2, 3].iterate!(square)
puts [4, 5, 6].iterate!(square)
I'm a beginner, please explain as detailed as possible, thank you.
square = Proc.new do |n|
n ** 2
end
This is a simple proc, which expect an integer argument, and return square of the argument.
eg:
square.call(5) => 25
square.(5) => 25
Now, You have opened Array class and added iterate! method which takes an argument. and the method just work on self (self here refers to same array on which you are calling the iterate method. here self = [1,2,3] in your first case and [4,5,6] in you second case.
in the iterate! method definition, you are mapping/looping the array elements and you are calling the square proc with each element as arguments.
So it is more like
square.call(1) => 1
square.call(2) => 4
square.call(3) => 9
I am studying Ruby and can't figure this out. I have an exercise where I have to add a method into a Library class which contains games. Each game is an instance of a class Game. So the solution is the following:
class Library
attr_accessor :games
def initialize(games)
self.games = games
end
def has_game?(search_game)
for game in games
return true if game == search_game
end
false
end
def add_game(game)
#games << game
end
end
I can't understand how << works in this case. Is this a bitwise left shift? Does Library class just assumes that games is an array, I believe I can pass anything to the Library class when I am initialising, single game or an array of games?
When you have:
#games << game
<< is actually a method. If it is a method, you ask, why isn't it written in the normal way:
#games.<<(game)
? You could, in fact, write it that way and it would work fine. Many Ruby methods have names that are symbols. A few others are +, -, **, &, || and %. Ruby knows you'd prefer writing 2+3 instead of 2.+(3), so she let's you do the former (and then quietly converts it to the latter). This accommodation is often referred to as "syntactic sugar".
<< is one of #games' methods (and game is <<'s argument), because #games is the receiver of the method << (technically :<<). Historically, it's called the "receiver" because with OOP you "send" the method to the receiver.
To say that << is a method of #games means that << is an instance method of #games's class. Thus, we have:
#games.methods.include?(:<<) #=> true
#games.class.instance_methods.include?(:<<) #=> true
I expect #games is an instance of the class Array. Array's instance methods are listed here. For example:
#games = [1,2,3]
#games.class #=> [1,2,3].class => Array
#games << 4 #=> [1,2,3,4]
#games.<<(5) #=> [1,2,3,4,5]
On the other hand, suppose #games were an instance of Fixnum. For example:
#games = 7
#games.class #=> 7.class => Fixnum
which in binary looks like this:
#games.to_s(2) #=> "111"
Then:
#games << 2 #=> 28
28.to_s(2) #=> "11100"
because << is an instance method of the class Fixnum.
As a third example, suppose #games were a hash:
#games = { :a => 1 }
#games.class #=> { :a => 1 }.class => Hash
Then:
#games << { :b => 2 }
#=> NoMethodError: undefined method `<<' for {:a=>1}:Hash
The problem is clear from the error message. The class Hash has no instance method <<.
One last thing: consider the method Object#send. Recall that, at the outset, I said that methods are sent to receivers. Instead of writing:
#games = [1,2,3]
#games << 4 #=> [1,2,3,4]
you could write:
#games.send(:<<, 4) #=> [1, 2, 3, 4]
This is how you should think of methods and receivers. Because send is an instance method of the class Object, and all objects inherit Object's instance methods, we see that send is "sending" a method (:<<, which can alternatively be expressed as a string, "<<") and its arguments (here just 4) to the receiver.
There are times, incidentally, when you must use send to invoke a method. For one, send works with private and protected methods. For another, you can use send when you want to invoke a method dynamically, where the name of the method is the value of a variable.
In sum, to understand what method does in:
receiver.method(*args)
look for the doc for the instance method method in receiver's class. If you Google "ruby array", for example, the first hit will likely be the docs for the class Array.
It's not a bitwise left shift, it's "shovel" operator. You, probably, know that in Ruby operators are implemented as methods, i.e. when you write
1 + 1
what is actually going on:
1.+(1)
The same is true for "shovel" (<<) operator, it's just a method on Array class, that appends its arguments to the end of the array and returns the array itself, so it can be chained:
>> arr = []
=> []
>> arr << 1
=> [1]
>> arr
=> [1]
>> arr << 2 << 3
=> [1, 2, 3]
It looks like #games is an array. The shift operator for an array adds an element to the end of the array, similar to array#push.
ary << obj → ary
Append—Pushes the given object on to the end of this array. This expression returns the array itself, so several appends may be chained together.
Let's say that i have object my_obj
and i get this object from database, with some more calculation, it doesn't matter...
and then i need to iterate via such object, like:
my_obj.each do |m|
some_method(m.id)
end
this code is not good, becouse if my_obj is nil, i will get error like:
undefined method `each' for nil:NilClass
so i decide to write:
if my_obj.present?
my_obj.each do |m|
some_method(m.id)
end
end
but i think that there is one more way of doing this, without writting everywhere if construction.
so how could i iterate via object, only if it is not null?
I found the code a bit anti-pattern for normal OOP principle "Tell, Don't ask". I tried the question in console and found your worry unnecessary.
No matter what the result is, blank Array or blank ActiveRecord::Relation object, each all works and return a blank array [].
Article.count
# => 0
articles = Article.all # Return array in Rails 3
articles.each { |a| puts a.title }
# => []
articles = Article.scoped # Return ActiveRecord::Relation object in Rails 3
articles.each { |a| puts a.title }
# => []
I would suggest you to review the method and returned result of your query. If your query returns unusual things, make sure it returns at least a blank Array. Then you don't need to consider too much.
They easiest way to handle this is to surround your object with the 'Array' conversion function which will coerce your possibly nil input into an array while leaving an existing array untouched, e.g.
>> Array(nil)
=> []
and
>> Array([1])
=> [1]
So in your case:
Array(my_obj).each do |m|
some_method(m.id)
end
The collection query should always return iterable object, but there are several ways. The problem of nil checks led to a pattern named NullObjects which is often the best solution. Apart from that you can do:
my_object.to_a.each do |m|
some_method(m.id)
end
or
my_object.try(:each) do |m|
some_method(m.id)
end
or
(my_object || []).each do |m|
some_method(m.id)
end
In Rails, you can do Object#try method:
my_object.try(:each) do |m|
some_method(m.id)
end
It will call each (returning its result) with attached block if my_object is other than nil. Otherwise, it won't call each method and will return nil.
You can initialize like this;
def fields_each(fields)
if fields.present? && fields.keys.present?
fields.each do |key, value|
yield(key, value) if block_given?
end
end
end
and usage;
fields_each({a: 1, b: 2, c: 3}) do |key, value|
puts "Key: #{key} value: #{value}"
end
Everyone forgot the awesome [my_objects].flatten.compact!
in the newer ruby version, tested with 2.4.1, this can be written as
my_obj&.each do |m|
some_method(m.id)
end
when you initialize the my_obj do it like this
my_obj = Model.all || [] # Empty array will not iterate or throw error
Ex:
[].each do |x|
p "I will not execute"
end
you can try this above the loop:
if my_obj == nil then
redirect_to "something_else"
end
I wish I described this better, but it's the best I know how. I have two classes Cars and Colors. Each can have many of each other through a association class CarColors. The association is set up correctly I'm positive of this but I can't seem to get this to work:
#carlist = Cars.includes(:Colors).all
#carlist.colors
ERROR
#carlist[0].colors
WORKS
My question is how can I iterate over the #carlist without declaring a index as in the successful example? Below is a few things I have tried which also fail:
#carlist.each do |c|
c.colors
end
#carlist.each_with_index do |c,i|
c[i].colors
end
Your first example fails because Car.includes(:colors).all returns an array of cars, not a single car, so the following will fail, because #colors is not defined for the array
#cars = Car.includes(:colors).all
#cars.colors #=> NoMethodError, color is not defined for Array
The following will work, because the iterator will have an instance of car
#cars.each do |car|
puts car.colors # => Will print an array of color objects
end
each_with_index will work as well, but it is a bit different, as the first object
is the same as the each loop car object, the second object is the index
#cars.each_with_index do |car, index|
puts car.colors # => Will print an array of color objects
puts #cars[index].colors # => Will print an array of color objects
puts car == #cars[index] # => will print true
end
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]