Blank check causing extra count call - ruby-on-rails

I am hardly trying to find one comparison of result.blank? and result[0] so finally today when i was checking one query with these two methods.
Here the code, result variable is #categories, which is an ActiveRecord result
This blank check calling one extra db call like SELECT COUNT(*) AS count_all
if #categories.blank?
end
But here that extra query is not showing there.
if #categories[0]
end
Is there any logic behind that? I couldn't find that

It is important to note that assigning a ActiveRecord query to a variable does not return the result of the query. Something like this:
#categories = Category.where(public: true)
Does not return an array with all categories that are public. Instead it returns an Relation which defines an query. The query to the database is execute once you call a method in the relation that needs to return the actual record, for example each, load, count.
That said: When you call blank? on a relation Rails needs to know it the relation will not return an empty array. Therefore Rails executes an query like:
SELECT COUNT(*) FROM categories WHERE public = 1
Because that queries is much faster that fetching all records when the only thing you need to know if there are any matching records.
Whereas #categories[0] works differently. Here it need to load all records to have an array holding all macthing categories and than return the first record in that array.
At this point both version ran only on query to the database. But I guess your next step would be to iterate over the records if there were any. If you used the first version (blank?) then the objects were not loaded, they were only counted. Therefore Rails would need to query for the actual records, what would result in a second query. The second exmaple ([0]) has the records already loaded, therefore not seconds query in needed.

Related

Ordering a collection by instance method

I would like to order a collection first by priority and then due time like this:
#ods = Od.order(:priority, :due_date_time)
The problem is due_date_time is an instance method of Od, so I get
PG::UndefinedColumn: ERROR: column ods.due_date_time does not exist
I have tried the following, but it seems that by sorting and mapping ids, then finding them again with .where means the sort order is lost.
#ods = Od.where(id: (Od.all.sort {|a,b| a.due_date_time <=> b.due_date_time}.map(&:id))).order(:priority)
due_date_time calls a method from a child association:
def due_date_time
run.cut_off_time
end
run.cut_off_time is defined here:
def cut_off_time
(leave_date.beginning_of_day + route.cut_off_time_mins_since_midnight * 60)
end
I'm sure there is an easier way. Any help much appreciated! Thanks.
order from ActiveRecord similar to sort from ruby. So, Od.all.sort run iteration after the database query Od.all, run a new iteration map and then send a new database query. Also Od.all.sort has no sense because where select record when id included in ids but not searching a record for each id.
Easier do something like this:
Od.all.sort_by { |od| [od.priority, od.due_date_time] }
But that is a slow solution(ods table include 10k+ records). Prefer to save column to sort to the database. When that is not possible set logic to calculate due_date_time in a database query.

apply ordering on limit query of active record

I need to find 10 records first and then apply ordering on that.
Model.all.limit(10).order('cast(code as integer)')
result of above code is - first it applies order on model and then limit query. So, I get same codes in my listing for given model. But I want to apply limit query first and then order fetched result.
When you call .all on model, it executes the query on DB and returns all records, to apply limit you have to write it before .all - Model.limit(10).all, but after that you can't use SQL function to operate data. So to get first 10 records and apply order to it, try this:
records = Model.limit(10).all.sort_by{|o| o.code.to_i}
or
records = Model.first(10).sort_by{|o| o.code.to_i}
Try this:
Model.limit(10).sort{|p, q| q.cost <=> p.cost}
All you need to do is to remove the .all method:
Model.limit(10).order('cast(code as integer)')
If you want to get 10 random records and then sort them without getting all records from the database then you could use
Model.limit(10).order("RANDOM()")
This uses the PostgreSQL rand() function to randomise the records.
Now you have an array of 10 random records and you can use .sort_by as described by BitOfUniverse.

Using .select or .map to get an array of names from an activerecord model

I am trying to get an array of names from an activerecord model based on a search query.
I have this method in my item model.
def self.search(search)
if search
where(['lower(name) LIKE ?', "%#{search}%"])
else
Item.all
end
end
I am trying to figure out the difference between using these two lines they are both returning the same thing.
Item.search('ex').select('name').map(&:name) vs
Item.search('ex').map(&:name)
Item.search('ex').select('name').map(&:name)
In the above statement, you are selecting only the name column from the result of Item.search('ex') and then getting the names for all of them using .map(&:name).
But, in the follwing statement:
Item.search('ex').map(&:name)
You are not selecting the name column, just getting the names using .map(&:name) from the result of Item.search('ex').
And Yes, they return the exact same result.
So, if you want the array of names only from the search result, then selecting the name column is redundant. Just go with this:
Item.search('ex').map(&:name)
Or, even better, do it using pluck:
Item.search('ex').pluck(:name)
It bypasses the need for instantiating every ActiveRecord object, and instead just returns the queried values in an Array directly, which improves performance both in terms of execution time and memory consumption.
Basically what .select does is what we call a projection, which is filtering the fields that are returned by the query. However, if you do not call .select at all, Rails default to selecting all the fields from the table.
So the diference between Item.search('ex').select('name') and Item.search('ex') is that the former selects only the column name and the latter selects all the columns from the items table.
Given that, when you map all the items to grab only the name, it doesn't make any diference, since both ways of doing have name selected.
1st Query: Item.search('ex').select('name').map(&:name)
Let's take the entire statement piece by piece.
Item.search('ex') # it trigger one SQL query and return `ActiveRecord::Relation` object in Array
SELECT `items`.* FROM `items` WHERE (lower(name) LIKE '%ex%')
Next
Item.search('ex').select('name')
SELECT `items`.`name` FROM `items` WHERE (lower(name) LIKE '%ex%')
As we might can see it return ActiveRecord::Relation object in Array with selecting name fields.
Now
Item.search('ex').select('name').map(&:name)
SELECT `items`.`name` FROM `items` WHERE (lower(name) LIKE '%ex%')
Further here as you are calling #map method on ActiveRecord::Relation object which itself defined in Enumerable Module.
#map Returns a new array with the results of running block once for
every element in enum.
2nd Query: Item.search('ex').map(&:name)
SELECT `items`.* FROM `items` WHERE (lower(name) LIKE '%ex%')
Here you are calling#map method on ActiveRecord::Relation object. Further you are saying, " Hey ActiveRecord::Relation I need only name field from search object, and ActiveRecord::Relation replay back to give all name in form of array. "
For more information The clever hack that makes items.map(&:name) work.
Hope this help !!!

Rails and Arel's where function: Can I call where on objects instead of making a call to the database?

Consider the following:
budget has many objects in its another_collection.
obj has many objects of the same type as object in its another_collection.
budget and some_collection are already declared before the following loop
they've been previous saved in the database and have primary keys set.
some_collection is a collections of objs.
-
some_collection.each do |obj|
another_obj = obj.another_collection.build
budget.another_collection << another_obj
end
budget.another_collection.collect {|another_obj| another_obj.another_obj_id}
=> [1, 2, 3, 4]
some_obj_with_pk_1 = some_collection.where(obj_id: obj.id)
some_obj_with_pl_1.id
=> 1
budget.another_collection.where(another_obj_id: some_obj_with_pk_1.id)
=> []
This shouldn't happen. What is happening is that rails queries the database for any items in another_collection with another_obj_id = 1. Since this collection hasn't been saved to the database yet, none of these items are showing up in the results.
Is there a function or something I can pass to Arel's where method that says to use local results only? I know I can just iterate over the items and find it but it would be great if I didn't have to do that and just use a method that does this already.
You could always use Enumeable#select which takes a block and returns only the elements that the block returns true for. You'd want to make sure that you had ActiveRecord retrieve the result set first (by calling to_a on your query).
records = Model.where(some_attribute: value).to_a
filtered_records = records.select { |record| record.something? }
Depending on your result set and your needs, it is possible that a second database query would be faster, as your SQL store is better suited to do these comparisons than Ruby. But if your records have yet to be saved, you would need to do something like the above, since the records aren't persisted yet

Why does a query in rails with a limit not work unless .all is put on the end

I have a query like this:
locations = Location.order('id ASC').limit(10)
which returns an array of 500 or so records - all the records in the table - i.e. the limit clause is being ignored.
Yet if I put a .all on the end:
locations = Location.order('id ASC').limit(10).all
it works and returns 10 records.
This code is being run in a rake task and I am using PostgreSQL if that makes any difference.
Why is it doing that? Surely the .all should not be required. What am I missing?
I think the behaviour depends on how you are handling the locations variable after setting it. This is because Location.order('id ASC').limit(10) isn't querying records but is returning an object of type ActiveRecord::Relation. The query will only occur once you call all, first, each, map, etc. on that object.
In my testing,
Location.order('id ASC').limit(10).map { |l| l.id }
returns an array of 10 ids as you would expect. But
Location.order('id ASC').limit(10).count
returns the total number of locations in the database, because it executes the SQL
SELECT COUNT(*) FROM "locations" LIMIT 10
which returns the full count of location rows (the limit is on the number of rows returned, not the count itself).
So if you are treating the result of Location.order('id ASC').limit(10) as an array by iterating through it, you should get the same result as if you had added all. If you are calling count, you will not. Kind of unfortunate, as I think ideally they should behave the same and you shouldn't have to know that you are dealing with an ActiveRecord::Relation instead of an array.
Ok here is my explanation
First of all if you do Location.order('id ASC').limit(10).class you'll see ActiveRecord::Relation next on the site with rails API ActiveRecord::Relation doesn't have a method all however it includes ActiveRecord::FinderMethods and if you look there you'll find next
# File activerecord/lib/active_record/relation/finder_methods.rb, line 142
def all(*args)
args.any? ? apply_finder_options(args.first).to_a : to_a
end
so it calls to_a method
As was mentioned in the railscasts this method is defined as
def to_a
...
#records = eager_loading? ? find_with_associations : #klass.find_by_sql(arel.to_sql)
...
#records
end
so it does SQL query on a third line with #records = eager_loading? ? find_with_associations : #klass.find_by_sql(arel.to_sql)

Resources