What is map(&:id) in Rails? - ruby-on-rails

My question is not an error, it is for understanding. As I'm new to Rails, I can't read all the code yet.
what does (&:id) do after .map
#user_cnae_classifications = user.cnae_classifications.map(&:id)
what is the difference of .map with it and without it?
in this method call:
UserCnaeClassification.create(
user: #user,
cnae_classification_id: id
)
How do I read that part of the code...
user: #user,
cnae_classification_id: id
are they keys and values?

1 )
You should read some tutorials on map to get acquainted.
https://www.rubyguides.com/2018/10/ruby-map-method
But the short answer is that running user.cnae_classifications.map(&:id) will loop over all cnae_classifications and extract the id from them and put them all into an array. The & character allows for map shorthand to avoid passing an entire block.
From the link above:
2 )
The #create method can accept a key-value hash of known attributes (known to the class in question, in this case that is UserCnaeClassification) to assign upon creation. So you're basically right, they are key-value pairs but they are specific to this class/object. Those same keys might not work on another class/object.
Additional reading: https://guides.rubyonrails.org/active_record_basics.html#create

what does (&:id) do after .map
The syntax map(&:method) is equivalent to:
object.map do |i|
i.method
end
The complete explanation is that the & operator is used to convert any Ruby object that responds to to_proc into a Proc, which encapsulates a block of code. In this case, the Symbol object (:id) is converted into the block of code above.
If you're interested in learning more about it, notice this is pure Ruby, not Rails-specific. Check the documentation for Proc.
In this method call:
How do I read that part of the code...
are they keys and values?
These are keyword arguments. It's a way to name the parameters of a method to explicitly tell the reader what each value should be. Just be aware that the behavior of methods accepting hashes as keyword arguments is deprecated, as seen in this official post.

The .map(&:id) is a shorthand for the longer form of .map { |x| x.id }.

Some interesting things to say: if you're using database (ORM - ActiveRecord), you will see that writing map(&:id) could be helpful. There also exists method called pluck, which does similiar things, but it's a little faster.
Usage:
Also pluck doesn't work with regular Arrays.

Related

Some questions about symbol and instance method in Ruby

I'm currently try to learn ruby on 'Learn Ruby The Hard Way'
Here's my question...
The following code are from exercise 40:
cities = {'CA'=> 'San Francisco', 'MI'=> 'Detroit', 'FL'=> 'Jacksonville'}
cities['NY'] = 'New York'
cities['OR'] = 'Portland'
def find_city(map, state)
if map.include? state
return map[state]
else
return 'Not found.'
end
end
cities[:find] = method(:find_city)
while true
print 'State? (ENTER to quit) '
state = gets.chomp
break if state.empty?
puts cities[:find].call(cities, state)
end
I played around with the code, and finally understand how it works.
But I still don't understand about two things:
first...
In about middle of the code,
it defined a variable
cities[:find] = method(:find_city)
As what I know for now, the :(colon) declare a symbol.
I want to know is it a better practice to name a variable as cities[:find]
instead of using cities_find in this case?
I'm not quite sure what's the differences, or maybe it's much readable for most rubyist?
And the second one is also about the same line.
method(:find_city)
I know it allows me to call the find_city method.
But again, why I have to put a colon before find_city?
Does this code means parse the arguments I put in to symbols?
I have to say that Learn Ruby The Hard Way gives us a really Really REALLY GOOD example of what we should NOT do. No rubyist will ever type such code in his/her projects. This piece of code is confusing, unreadable and is an abuse of metaprogramming.
Anyway, I dissect that code for you.
The confusing part starts with this line:
cities[:find] = method(:find_city)
Let's look at the right side of the =. It calls a method whose name is method, as you may guess, the return value of the method call is the method find_city, more precisely, a Method object that wraps the method find_city with its scope. Then that method is stored in the hash cities, with a symbol :find as the key. So the value of cities now become
{
'CA'=> 'San Francisco',
'MI'=> 'Detroit',
'FL'=> 'Jacksonville',
'NY' => 'New York',
'OR' => 'Portland',
:find => #<Method:main.find_city>
}
You can see that the last key-value pair is really really weird, and it shouldn't be there because the hash cities should only store states and their capitals. Heck!
Then here comes this even weirder expression cities[:find].call(cities, state). Let's see how this work.
cities[:find] simply retrieve the Method object from the hash (still remember what method it wraps?)
cities[:find].call(cities, state) invokes the method it wraps, which is find_city, in the scope that the Method object wraps, which is the top level object (a.k.a. main), Method#call returns whatever the method wrapped in returns. So this expression is just find_city(cities, state), written in an alien style.
cities[:find] = method(:find_city)
Here, cities is a hash and the method object returned by method(:find_city) is assigned to the hash key find which is a symbol.
I think it depends upon you and the context of program where you are writing this.
A simple method_var = method(:find_city) would work here as well.
method(:find_city)
I know it allows me to call the find_city method. But again, why I have to put a colon before find_city? Does this code means parse the arguments I put in to symbols?
Here, you are passing the method name as an argument, you have to either pass it as a symbol or string.
In Ruby, the method method creates a Method Object. This allow you to pass it around in your code and call it later using the .call method on your Method object.
Since calling method(my_method) would evaluate my_method and pass the result to method(...), you need a way to tell the method method which method to use. That's why you basically pass in the method name as a Symbol into the method method :D
So it actually defined a proc, and make :find and find_city sort like key and value...Probably...

If ActiveRecord::Base#create is deprecated in 3.2.13, how am I using it still?

I'm running 3.2.13 and following the standard "railstutorial.org" I've run into a question. I'm still getting the hang of switching from a C++/Java programming mindset, and trying to understand normal calling conventions.
I can use my class (User < ActiveRecord::Base) like:
User.create(email: "email", password: "password", password_confirmation: "password")
The doc for the method indicates that it is deprecated. I also see no reference to a def create in my ActiveRecord gem source.
Since the call is not using a Hash, what is the name of that kind of invocation? Is it the same if the arguments were wrapped in curly brackets?
Two questions are being asked here.
First question: method deprecation
Changes were made in this commit.
The create method was removed from ActiveRecord::Base and inserted into a separate module called ActiveRecord::Persistence.
Regarding what APIDock told you... the method was "deprecated or moved." If you look halfway down the list of suggestions, you will see ActiveRecord::Persistence#create. This is where the method moved to.
Note that ActiveRecord::Persistence#create is used internally. When calling create on an ActiveRecord model (not object) in your application code, you are invoking ActiveRecord::Relation#create.
ActiveResource::Base#create was introduced in Rails 2.0 and is unrelated to ActiveRecord.
Second question: hash as arguments
Examining the source for ActiveRecord::Relation#create shows the following:
# File activerecord/lib/active_record/relation.rb, line 85
def create(*args, &block)
scoping { #klass.create(*args, &block) }
end
See the splat argument *args? This tells Ruby to pass all remaining arguments into args, no matter how many there are. Ruby/Rails' ducktyping magic sees a bunch of key-value pairs and assumes it to be a hash.
Rails is a bit loose with requiring curly brackets for hashes in arguments, just as it is loose about wrapping all arguments in parentheses. Generally, Rails will infer a hash when you pass in a series of key-value pairs. Sometimes this causes trouble when passing in multiple hashes, like in a complex form_for method.
If you have erratic behavior with curly brackets absent, insert them. Clearly defining hashes will allow you to ensure proper behavior.
Read more about Ruby splats here.
"Deprecated" doesn't mean "no longer available". It means it will be no longer available soon.

Rails - trying to understand a piece of code

I am working with some code I found online:
def person_path(options)
# This is where the path of the query is constructed.
path = "/people/" # For this section of the LinkedIn API
if id = options.delete(:id)
path += "id=#{id}"
elsif url = options.delete(:url)
path += "url=#{CGI.escape(url)}"
else
path += "~"
end
end
I am not completely certain what it does. what I am trying to do is have it construct a string something like this: http://api.linkedin.com/v1/people/~:(current-status) which I got from the LinkedIn developer docs here: https://developer.linkedin.com/documents/profile-api
Any thoughts on what I should pass this functions and how exactly it accomplishes what it does?
Thanks!
Whilst it's not stated what 'options' is, it's extremely common to pass in options to a method as a Hash of key-value pairs in Ruby, so I'd say that options is just that (with 99% certainty). This is the part that's key to understanding the rest of the code.
I believe that the #delete method on hash is being used in order to pull out the key-value pair and assign the value in one move, whilst taking advantage of the returned object's "truthiness".
http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-delete
And by "truthiness", I mean that in Ruby, all objects evaluate to 'true' except 'nil' and 'false'.
The rest is simple if-else control flow logic that you will have seen in any other language, so I hope this makes sense.
This just creates a path of the form "/people/id=foo" or "/people/url=foo_with_%_escapes" if it finds id or url in the options. As a side effect, it deletes the one it finds from the options. If it doesn't find either one, it gives "/people/~"

Accessing object values that have reserved keywords as names in Rails

I'm accessing the Amazon AWS API using the ruby-aaws gem, but without going to much into details of the API or the gem, I think my problem is more of a general nature.
When I query the API I will end up with "object array", let's call it item, containing the API response.
I can easily access the data in the array, e.g. puts item.item_attributes.artist.to_s
Now the API returns attributes whose identifier are reserved words in Rails, e.g. format or binding.
So doing this:
puts item.item_attributes.format.to_s will return method not found
while
puts item.item_attributes.binding.to_s will return some object hash like #<Binding:0xb70478e4>.
I can see that there are values under that name when doing
puts item.item_attributes.to_yaml
Snippet from the resulting yaml show artist and binding:
--- !seq:Amazon::AWS::AWSArray
- !ruby/object:Amazon::AWS::AWSObject::ItemAttributes
__val__:
artist: !seq:Amazon::AWS::AWSArray
- !ruby/object:Amazon::AWS::AWSObject::Artist
__val__: Summerbirds in the Cellar
binding: !seq:Amazon::AWS::AWSArray
- !ruby/object:Amazon::AWS::AWSObject::Binding
__val__: Vinyl
This was probably a very detailed explanation with a very simple solution, but I can't seem to find the solution.
edit
Finally found it. I guess it is because it is an array of objects, duh...
puts item.item_attributes[0].binding.to_s
You may be able to access the individual attributes by using [] instead of the method name (which is probably provided using method_missing anyway).
So, item.item_attributes[:artist].to_s may return what you want. If it doesn't it would be worth trying 'artist' as the key instead.
Finally found it. I guess it is because it is an array of objects, duh...
puts item.item_attributes[0].binding.to_s

options.fetch . I don't get it

Look at the discussion on this thread . I am not able to follow how having a block to a fetch is a better solution.
In the first patch on Rails ticket #4558:
options.fetch(:alt, File.basename(src, '.*').capitalize)
This line executes the basename and capitalize functions and then passes the result into Hash#fetch regardless of if a value for :alt already exists in the options hash.
In the updated patch:
options.fetch(:alt) { File.basename(src, '.*').capitalize }
The same basename/capitalize code is only executed when Hash#fetch needs the default value (i.e. when the :alt key does not exist in the options hash). This means the (possibly expensive) calculation of the default value can be skipped if it's not needed.
See the documentation on Hash#fetch for more details.
I don't know what duck-punching Rails has been doing to Hash#fetch, but with Plain Old Ruby Objects, I use it rather than Hash#[] because when I ask for something and it's not available, I want to know about it. This is called "Failing early" (or "Crashing early" in The Pragmatic Programmer's List of Tips)

Resources