Rails query object class are different? - ruby-on-rails

#category = Category.where(:category_name => 'cricket')
#category.class
Here the class of #category is "ActiveRecord::Relation"
But,
#category = Category.all(:conditions => { :category_name => 'cricket' })
#category.class
In this case the class of #category is "Array"
The result of both the queries are same, then also the class is different.
WHY?
One more thing...
In the first case, I can do #category.title or #category.body etc.
But in second case, It is not possible.
WHY?

In the first case you are actually using the default scope and attribute it with the where part. It means, when you want to use the items of this Relation, it will run the SQL query on demand. Think about it like it is prepared, but not yet ran query, which will yield walues when needed, and you can further specify the parameters, for example you can append another where clause, or something to it. And of course it is smarter than a simple array, because of the implementetion is more complex behind this.
In the second case you immediately fetch all record from the database, so the result is an Array, containing the results. It is pretty dumb compared to the other one.

Related

Chaining multiple .map calls together, more efficient way to query data?

I find myself sometimes needing to "find" an item that's buried deep within an association structure.
In this case, it starts with a task_group. And from that task_group, I need to get the parent_image's, which resides at the end of several associations.
Here is how I'm currently handling it:
task_group.tasks
.map(&:submissions).flatten
.map(&:taggables).flatten
.map(&:images).flatten
.map(&:parent_image)
Is there a more efficient way of going about this type of query?
Perhaps the query could somehow limit the content the amount of data returned each time? For example, rather than have &:submissions, &:taggables, &:images .map calls return the entire records each time, could I have them pull out only, say, the :id attribute, and let the final &:parent_image map call return the full record?
Thank you in advance for any guidance or insights!
UPDATE: Learning about how to use the .joins method as recommended by #Deepak . I'm almost there!
parent_images = Image.joins(crops: [tags: [:submission [task: :task_group]]])
This returns an Image::ActiveRecord_Relation that I can call .ids or .uniq on to get an array.
It seems .joins directly references field names. Which is well and good, except in this instance, taggables that I used in the original .map calls is a quick and dirty "catch-all" for several different associations.
has_many :tags
has_many :people
has_many :businesses
...
def taggables
tags + people + businesses ...
end
I wonder, then, is it possible to reference a method as part of .joins query (possibly with .where(), though I'm not sure how that would it in...)? Or will I need to explicitly state each of the taggables associations directly somehow?
Thanks to #Deepak's suggestion, I am able to combine .joins with .where in the following manner, in order to get all of the parent_images related to a given task_group:
tg = TaskGroup.find(2)
parent_images = Image.joins(crops: [tags: [submission: [task: :task_group]]]).where(task_groups: { id: tg })
Or say I'm to pull all of the images related to a given TaskGroup, one association 'removed' from the parent_images:
tg = TaskGroup.find(2)
images = Image.joins(tags: [submission: [task: :task_group]]).where(task_groups: { id: tg })
Now, my understanding of .joins is that the arguments need to relate to fields found in their respective tables. This means that, when using .map, I could call the taggables method, but when using .joins, this does not seem to be possible.
So the workaround I'm using at the moment is to call each of the taggables models separately, then combine the results.
tag_crops = Image.joins(people: [submission: [task: :task_group]]).where(task_groups: { id: tg })
business_crops = Image.joins(businesses: [submission: [task: :task_group]]).where(task_groups: { id: tg })
...
And then combine each of those together into one giant list
crop_image_ids = tag_crops.ids.uniq +
person_crops.ids.uniq +
business_crops.ids.uniq +
...
crop_image_ids = crop_image_ids.uniq

How to get active record's name after using find .where

Straight forward here:
<% #yyy = CityRace.where(city_race_id2: "3") %>
<% #xxx = #yyy.name %>
The #yyy is returning the proper record using the ID I have passed into it, but I'm trying to get the objects name. For some reason .name isn't working. Any idea what I'm going wrong here?
How do I find a record's name where id = a certain id?
where returns an ActiveRecord_Relationship, meaning an object containing CityRace objects for every row in the database with city_race_id2 equals to 3, no matter if there's only one, the result is an ActiveRecord_Relationship object, and that doesn't respond to name.
If you need the name of a particular object from that result, you can access to the specific element by its index and invoke name on it, e.g:
CityRace.where(city_race_id2: "3").first.name
Or to retrieve the name from every object:
CityRace.where(city_race_id2: "3").pluck(:name)
this one returns an array of strings, so, you must iterate over them to print them all or get a specific one.
In the other hand if you need a single row from the query, use find_by:
CityRace.find_by(city_race_id2: "3").name
.where returns an ActiveRecord::Relation which behaves like an array. You can think of it like a special kind of array that allows you to chain on more active record queries on it. When you call certain methods like each or to_a it evaluates the query into an actual array.
In any case, what you are looking for here is not something array-like. You want #yyy to refer to a single record.
Simple fix, just use find_by instead of where. Also take a look at https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find and find vs find_by vs where

Methods for ActiveRecord that not implies database or convert into Hash/Array

I have a model foo and in my index action I do:
#foos = foo.all
and now, on the view I want to know if this #foos contain a record whit foo.id = whatever
One method of do that is #foos.exists?(id: whatever), but this method implies a bd query and I want avoid this.
Other method will be #foos.collect(&:id).include?(whatever) but this implies the conversion of all #foos into array, another case that I would avoid.
There are another ways to accomplish it?
Thanks in advance
UPDATE
I want also get this object not only know if exists, sorry for bad explanation.
You can use the Enumerable#any? to check if the value exists in the array of results:
#foos.any? { |foo| foo.id == whatever }
This check will be performed on the result array without hitting the database again and without converting the array to another structure.
UPDATE
If you want the object returned, use Enumerable#find, like this:
#foos.find { |foo| foo.id == whatever }
This will return the first occurrence where foo.id == whatever.

Rails difference in object created from a .find(:id) and .where() methods

What is the difference in the objects created with these 2 methods:
tec = Technique.find(6)
tec2 = Technique.where(:korean => 'Jok Sul')
The data returned for each is exactly the same, yet the first object will respond perfectly to an inherited method like update_attributes while the second object will give an error of method not found.
When I do tec.class and tec2.class one is an ActiveRecord::Relation and the other doesn't give me a class at all, it just prints out the content of the object.
Maybe when you use the .where method you get an array, even if there is only one match and therefore you always have to issue the .each method to get at the contents? But that makes it hard to deal with when you want to update records, etc.
Can someone clarify this for me? Specifically, how to deal with matches found through the .where method.
Thanks.
Try:
tec2 = Technique.where(:korean => 'Jok Sul').first
Good question.
tec_scope = Technique.where(:korean => 'Jok Sul') # create an internal query
Remember, here only the query is created, it is not executed. You can programmatically build on top of this query if you so wished. The scope (or query if you so wish) will be executed in 2 ways. "Implicit" or "Explicit". Implicit way of running the query happens for example in the console, which invokes a method on the scope which automatically runs the query for you. This wont happen in your controllers unless you run it explicitly for .e.g
tec_scope.all # returns array
tec_scope.first # retuns one element
Scopes are just adding where clauses/predicates to your query. It's query building and delaying the execution till it is needed.
However,
tec_objects = Technique.find(6) # explicitly runs a query and returns one object (in this case)
This will explicitly run the query there and then. It is a question of the timing of execution of the query.
The difference is subtle but very important.
This hasnt got anything to do with whether you get one result or an array.
Technique.find([4,5]) # will return an array
Technique.find(4) # will return one object
Technique.where(:some_key => "some value").all # will return an array
Technique.where(:id => 5).first # will return one object
The difference is in timing of the execution of the query. Don't let the console fool you into believing there is no difference. Console is implicitly firing the query for you :)
The find(6) returns a single object, because you're specifying the object ID in the database, which is guaranteed to be unique by convention.
The where call returns a collection, which may be only 1 item long, but it still returns a collection, not a single object.
You can reveal this difference. Using your example code, if you call tec.class vs. tec2.class I think you'll find that they aren't the same class of object, as you expect.
That is, the methods available to a collection of objects is different than the methods available on an instance of that object.

Trouble on finding a class object in a array of classes

I am using Ruby on Rails 3.0.7 and I would like to understand how to handle the following code in order to retrieve a class objects with a specified id.
In my view file I have:
#records = Users.all # This returns an array (class)
In another file, a partial template, I would like to retrieve, for example, the user with id 1, but if I make this:
#records.find(1)
I get an enumerator (class) of all records:
<Enumerator: [<Users id: 1, ... ] >
How can I find the user with id 1 (or other ids) "a là Ruby on Rails Way"?
UPDATE
I use #records = Users.all in a view file because I aim to minimize calls to the database since I need to iterate almost over all records and check them existence. If I do for example:
some_hash.each { |key, value|
put User.find(value)
}
and I go in the log file, I will see a lot of database requests.
Even though this is probably quite slow, and I suspect there are some less than optimal designs in the app you're working on (not judging, we've all been there), Array#index seems to be what you're looking for:
#records[#records.index{|user| user.id == 1}]
Edit
Although if you need to do something for every user, and you need to access them by id quickly, I'd probably do something like this in your controller. Even if it's not really faster, it's much more readable (to me anyways):
#users_hash = {}
User.all.each{|user| #users_hash[user.id] = user}
Then in your views you can do:
#users_hash[id].username
Use User.scoped instead of User.all. #all will immediately query the database and return an array, whereas #scoped will return an ActiveRecord::Relation object which you can chain further queries. In this case, the database won't be hit until you try and somehow inspect or enumerate the result
Actually you're mistaken. #records.find(1) is returning an object of the class Enumerator (which is not the same as the class Enumerator itself).
The problem here is that, as you've noted, #records is an Array, not an ActiveRecord object, and Array#find (inherited from Enumerable#find--which, when not given a block, returns an object of class Enumerable) is not the same method as ActiveRecord::Base#find (i.e. User#find).
What you should do is, in your controller, pick out the one user record you want:
#user = User.find 1
...and then use #user directly in your template. Generally you should avoid doing ActiveRecord lookups (e.g. find) in your templates. That kind of logic should happen in your controller.
Last time for such case I ended up doing like this:
#assignments = Assignment.find_by_sql(' ... ')
#assignments.find(id: 1).first

Resources