Which one is faster between map, collect, select and pluck? - ruby-on-rails

I have been using different methods to get specific fields from active record, But which one is faster and preferred to use and how are they different from one another?
User.all.collect(&:name)
User.all.pluck(:name)
User.all.select(:name)
User.all.map(&:name)
Thanks for your help in advance.

Usage of any of these methods requires different use cases:
Both select and pluck make SQL's SELECT of specified columns (SELECT "users"."name" FROM "users"). Hence, if you don't have users already fetched and not going to, these methods will be more performant than map/collect.
The difference between select and pluck:
Performance: negligible when using on a reasonable number of records
Usage: select returns the list of models with the column specified, pluck returns the list of values of the column specified. Thus, again, the choice depends on the use case.
collect/map methods are actually aliases, so there's no difference between them. But to iterate over models they fetch the whole model (not the specific column), they make SELECT "users".* FROM "users" request, convert the relation to an array and map over it.
This might be useful, when the relation has already been fetched. If so, it won't make additional requests, what may end up more performant than using pluck or select. But, again, must be measured for a specific use case.

pluck: retrieve just names from users, put them in an array as strings (in this case) and give it to you.
select: retrieve all the users from db with just the 'name' column and returns a relation.
collect/map (alias): retrieve all the users from db with all columns, put them in an array of User objects with all the fields, then transform every object in just the name and give this names array to you.
I put this in order of performance to me.

Related

Finding distinct values in array column

I'm using Postgresql with ActiveRecord in Rails 4.
I have a customer model and one of the columns is called "tags" and it is an array column (["sports", "broadcasting"]).
How can I select all the distinct values from this column? I would like to avoid doing anything where I would have to instantiate AR objects due to the amount of customer records we have. So I don't want something like:
Customer.select(:tags).map(&:tags).flatten.uniq
which works but uses too much memory.
I need the values to provide suggestions when someone is adding a tag to a customer. Hopefully it will help prevent variations of words or misspellings.
Thanks in advance!
You can use pluck like below
Customer.pluck(:tags).flatten.uniq

Rails scope complexity

I have a model to which I need to create a default scope. I am unsure of the best way to write this scope but I will explain how it needs to work.
Basically I need to get all items of the model and if two items have the same "order" value then it should look to the "version" field (which will contain, 1, 2, 3 etc) and pick the one with the highest value.
Is there a way of achieving this with just a scope?
Try this code:
scope :group_by_order, -> { order('order ASC').group('order') }
default_scope, { (group_by_order.map{ |key,values| values.order('version DESC') }.map{|key, values| values - values[1..-1]}).values.flatten }
Explanation Code:
order by "order" field.
group by "order" field.
map on the result hash, and order each values by "version" field
map again on values, and remove from index "1" to the end.
get all values, and flatten them
A word of caution using default scopes with order. When you performs updated on the collection such as update_all it will use the default scope to fetch the records, and what you think would be a quick operation will bring your database to its knees as it copies the rows to a temporary table before updating.
I would recommend just using a normal scope instead of a default scope.
Have a look at Select the 3 most recent records where the values of one column are distinct on how to construct the sql query you want and then put that into a find_by_sql statemate mentioned in How to chain or combine scopes with subqueries or find_by_sql
The ActiveRecord order method simply uses the SQL ORDER function which can have several arguments. Let's say you have some model with the attributes order and version then the correct way order the records as you describe it, is order(:order, :version). If you want this as the default scope would you end up with:
default_scope { order(:order, :version) }
First, default_scopes are dangerous. They get used whenever you use the model, unless you specifically force 'unscoped'. IME, it is rare to need a scope to every usage of a model. Not impossible, but rare. And rarer yet when you have such a big computation.
Instead of making a complex query, can you simplify the problem? Here's one approach:
In order to make the version field work, you probably have some code that is already comparing the order fields (otherwise you would not have unique rows with the two order fields the same, but the version field differing). So you can create a new field, that is higher in value than the last field that indicated the right entity to return. That is, in order to create a new unique version, you know that you last had a most-important-row. Take the most-important-rows' sort order, and increment by one. That's your new most-important-rows' sort order.
Now you can query for qualifying data with the highest sort order (order_by(sort_order, 'DESC').first).
Rather than focus on the query, focus on whether you are storing the right data, that can the query you want to achieve, easier. In this case, it appears that you're already doing an operation that would help identify a winning case. So use that code and the existing database operation, to reduce future database operations.
In sql you can easily order on two things, which will first order on the first and then order on the second if the first thing is equal. So in your case that would be something like
select * from posts order by order_field_1, version desc
You cannot name a column order since it is a sql reserved word, and since you did not give the real column-name, I just named it order_field_1.
This is easily translated to rails:
Post.order(:order_field_1, version: :desc)
I would generally advice against using default_scope since once set it is really hard to avoid (it is prepended always), but if you really need it and know the risks, it is really to apply as well:
class Post < ActiveRecord::Base
default_scope { order(:order_field_1, version: :desc) }
end
This is all actually documented very well in the rails guides.

What's the best way to create a model associated to a query instead of a table

I'm trying to create a reporting app with Rails 4.
As a reporting system, it has a lot of SQL queries where the result is not like any table schema. I mean, a select query where I have some joins, unions and etc and the result will be something like a row with it's columns being result of subqueries, sums and etc.
Would it be possible to have a Model with no table associated, but I can use the "find_by_sql" on it, to instanciate an array of that model with the results of my query?
Something like:
Use "select table1.field1, sum(if(...,table2.field,...) as field2, as field3 from...." as query, and return a array of a model "Result", where I can call a
array_of_result.first.field3?
Sorry if I'm not writing clearly enough.
EDIT: until now, sparky's anwser(http://railscasts.com/episodes/193-tableless-model) was the closest one, beacuse I want to use some of the ActiveRecord features, like specify a connection in the class(or even in a super class).
For pure reporting, especially when the result column names span multiple models, one alternative is to just pass the query directly back and deal with the result set:
ActiveRecord::Base.connection.execute([raw SQL query])
You'll get back a result set, which is typically an enumerable set of row results, but check the documentation for your DB adapter to find out for sure what it's returning.
For example, if you're using PostgreSQL as your database with the pg gem, you'll get back an instance of PG::Result which you can then operate on in the following way:
> results = ActiveRecord::Base.connection.execute("SELECT COUNT(*) FROM customers")
=> <PG:Result >
> results.count
=> 63 # the number of customers I have in this contrived example
> results.first
=> { "count": "63" }
> results[0]
=> { "count": "63" }
> results[0]["count"]
=> "63"
You'll need to cast your return values to something other than strings. ActiveRecord will typically do this for you in your models since it knows the column types, but by doing a raw query you'll probably just get back strings that you'll have to cast yourself. If you're just doing a query to display it on a page somewhere maybe the strings will be sufficient.
I'm sure you'll be doing more sophisticated reports, but you'll notice in my simple example that the key count wound up being created as the accessor to the result of the SELECT COUNT... query. If you specify column names, or alias them, the keys in the resulting hash set will match the column names or the aliases you've set.
You can certainly create a Reporting model.
You would want to start off by creating a tableless model. Essentially, this can be as simple as a file in your models directory with
class Reporting
end
in it, and a controller with some appropriate actions and views. However, have a look at
http://railscasts.com/episodes/193-tableless-model
http://railscasts.com/episodes/219-active-model
which cover tableless models and what you can do with active model with respect to validations etc.
In your case, you say that you have some complex joins etc. Sometimes it's easier in the short term to SQLize these, but if you can use activerecord you should. Apart from anything else, this will allow you to define custom methods in your model which you can chain and make your Reporting controller much cleaner

How to get ActiveRecord::Result from ActiveRecord::Relation on Rails?

I want to leverage querying capabilities of my model but I want to load arbitrary aggregated values. .pluck will give me an array and I'll lose the reference to columns that I have on ActiveRecord::Result.
So far I've been going through connection but that's cumbersome
query = where(...).group(...).select(...)
connection.exec_query(query.to_sql)

rails query serializable object

How do I query the database to find objects that contain one or more attributes that are stored as serializable?
For example, I have a concert which occurs only in certain cities. I want to make a Concert object with a column called cities and store an array of cities.
If I want to query my database to find all concerts that occur in 1 city (or all concerts that occur in an array of n cities), how do I do this?
The best way to do this isn't to store it in a serialized column, but a separate table called Cities. Then you can do this:
City.find_by_name('Cityname').concerts
One possible way to query would be to use SQL's LIKE condition. This would work for boolean conditions in serialized tables.
For example to find those Users with the 'notification' option on,
users=User.arel_table
User.where(users[:options].matches("%notification: true%"))
As for other type of variables this would not be as feasible.

Resources