Rails 5 get specific field/value from table - ruby-on-rails

I have a table called group. I want this method to return just the content of the relevant record's ID field. At the moment it returns an active record object ID.
def get_group_name(group_id)
Group.select([:name]).where("id = ?", group_id)
end
Thanks in advance.

I think you can do easier with find
def get_group_name(group_id)
Group.find(group_id).name
end

This will get you only the name of the group.
def get_group_name(group_id)
Group.where(id: group_id).limit(1).pluck(:name).first
end
It will run this query:
SELECT name
FROM groups
WHERE id = ?
LIMIT 1;
A side note is, be careful of what you’re doing. Any time you have a method to get a single field’s value, while it can be more efficient at times, it can easily be misused. If you’re looping over a collection of group ids trying to grab all of the names, then you’d be better off 1 query up front for all of the names as opposed to 1 per group id on the page. So just keep and eye on your console and pay attention to the queries you’re running.
Also, if you are looking over a collection, you may want to look into includes for your ActiveRecord queries, to include the group data in the previous query. You can benchmark this all to figure out what’s fastest for your use case.

Related

Include vs Join

I have 3 models
User - has many debits and has many credits
Debit - belongs to User
Credit - belongs to User
Debit and credit are very similar. The fields are basically the same.
I'm trying to run a query on my models to return all fields from debit and credit where user is current_user
User.left_outer_joins(:debits, :credits).where("users.id = ?", #user.id)
As expected returned all fields from User as many times as there were records in credits and debits.
User.includes(:credits, :debits).order(created_at: :asc).where("users.id = ?", #user.id)
It ran 3 queries and I thought it should be done in one.
The second part of this question is. How I could I add the record type into the query?
as in records from credits would have an extra field to show credits and same for debits
I have looked into ActiveRecordUnion gem but I did not see how it would solve the problem here
includes can't magically retrieve everything you want it to in one query; it will run one query per model (typically) that you need to hit. Instead, it eliminates future unnecessary queries. Take the following examples:
Bad
users = User.first(5)
users.each do |user|
p user.debits.first
end
There will be 6 queries in total here, one to User retrieving all the users, then one for each .debits call in the loop.
Good!
users = User.includes(:debits).first(5)
users.each do |user|
p user.debits.first
end
You'll only make two queries here: one for the users and one for their associated debits. This is how includes speeds up your application, by eagerly loading things you know you'll need.
As for your comment, yes it seems to make sense to combine them into one table. Depending on your situation, I'd recommend looking into Single Table Inheritance (STI). If you don't go this route, be careful with adding a column called type, Rails won't like that!
First of all, in the first query, by calling the query on User class you are asking for records of type User and if you do not want user objects you are performing an extra join which could be costly. (COULD BE not will be)
If you want credit and debit records simply call queries on Credit and Debit models. If you load user object somewhere prior to this point, use includes preload eager_load to do load linked credit and debit record all at once.
There is two way of pre-loading records in Rails. In the first, Rails performs single query of each type of record and the second one Rails perform only a one query and load objects of different types using the data returned.
includes is a smart pre-loader that performs either one of the ways depending on which one it thinks would be faster.
If you want to force Rails to use one query no matter what, eager_load is what you are looking for.
Please read all about includes, eager_load and preload in the article here.

Rails: Order custom model using custom method

I have a custom model called product, and it has many reviews.
i have a method that calculates the review
def rating
total = 0
reviews_count = reviews.count
return 0 if reviews_count == 0
reviews.each do |review|
total += review.grade
end
total.to_f/reviews_count
end
i would like to know how could i use this method to Order my products.
At products_controller.rb, if i use:
#products = Product.all.order("price")
its easy, it gives me the products list ordered by price. But, if i use, for example:
#products = Product.all.sort_by{|p| p.rating}
it gives me an array and not a "ActiveRecord::Relation"
I would like to know how could i order my product using a custom method that returns a value.
In general, you can't. Ordering happens in your database, which has no knowledge about any method you typed in your application. What you need is a way of translating your method into a valid sql. In your case, you can do:
Product.joins(:reviews).group('products.id').order('AVG(reviews.grade)')
That will give you sorted results and the relation object. However, relations with join are not that nice to work with, especially if you try to add another join. Also this might get quite slow when your database grows.
What you're doing in your example is running the query then using sort_by to sort the result set.
If you want to get back an activerecord collection instead of an array, and potentially chain this with other scopes, you should move the logic from your method into SQL, and put it in a scope.

How to remove some items from a relation?

I am loading data from two models, and once the data are loaded in the variables, then I need to remove those items from the first relation, that are not in the second one.
A sample:
users = User.all
articles = Articles.order('created_at DESC').limit(100)
I have these two variables filled with relational data. Now I would need to remove from articles all items, where user_id value is not included in the users object. So in the articles would stay only items with user_id, that is in the variable users.
I tried it with a loop, but it was very slow. How do I do it effectively?
EDIT:
I know there's a way to avoid doing this by building a better query, but in my case, I cannot do that (although I agree that in the example above it's possible to do that). That thing is that I have in 2 variables loaded data from database and I would need to process them with Ruby. Is there a command for doing that?
Thank you
Assuming you have a belongs_to relation on the Article model:
articles.where.not(users: users)
This would give you at most 100, but probably less. If you want to return 100 with the condition (I haven't tested, but the idea is the same, put the conditions for users in the where statement):
Articles.includes(:users).where.not(users: true).order('created_at DESC').limit(100)
The best way to do this would probably be with a SQL join. Would this work?
Articles.joins(:user).order('created_at DESC').limit(100)

ActiveRecord return the newest record per user (unique)

I've got a User model and a Card model. User has many Cards, so card has a attribute user_id.
I want to fetch the newest single Card for each user. I've been able to do this:
Card.all.order(:user_id, :created_at)
# => gives me all the Cards, sorted by user_id then by created_at
This gets me half way there, and I could certainly iterate through these rows and grab the first one per user. But this smells really bad to me as I'd be doing a lot of this using Arrays in Ruby.
I can also do this:
Card.select('user_id, max(created_at)').group('user_id')
# => gives me user_id and created_at
...but I only get back user_ids and created_at timestamps. I can't select any other columns (including id) so what I'm getting back is worthless. I also don't understand why PG won't let me select more columns than above without putting them in the group_by or an aggregate function.
I'd prefer to find a way to get what I want using only ActiveRecord. I'm also willing to write this query in raw SQL but that's if I can't get it done with AR. BTW, I'm using a Postgres DB, which limits some of my options.
Thanks guys.
We join the cards table on itself, ON
a) first.id != second.id
b) first.user_id = second.user_id
c) first.created_at < second.created_at
Card.joins("LEFT JOIN cards AS c ON cards.id != c.id AND c.user_id = cards.user_id AND cards.created_at < c.created_at").where('c.id IS NULL')
This is a bit late, but I am working on the same matter, and i found this one works for me :
Card.all.group_by(&:user_id).map{|s| s.last.last}
What do you think ?
I've found one solution that is suboptimal performance-wise but will work for very small datasets, when time is short or it's a hobby project:
Card.all.order(:user_id, :created_at).to_a.uniq(&:user_id)
This takes the AR:Relation results, casts them into a Ruby Array, then performs a Array#uniq on the results with a Proc. After some brief testing it appears #uniq will preserve order, so as long as everything is in order before using uniq you should be good.
The feature is time sensitive so I'm going to use this for now, but I will be looking at something in raw SQL following #Gene's response and link.

What is the most efficient way to find the first matching record?

For instance, user = find the first Fred from a list of users
User.find_by(first_name: 'Fred') # it finds the first user http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find_by
If your want order by and pick the first record in the ordered result, following also should work:
user = User.where(:name => "Fred").order(:age).first
The most performant way is to use find_by:
User.find_by(name: 'Fred')
This method has been added on Rails 4 and it is defined like this:
def find_by(*args)
where(*args).take
end
In further details, take differs from first regarding the order. first will return the first record after reordering your records based on the primary key while take will return whatever it finds first.
So while using where().take is equivalent to find_by and choosing whether to use one of the other is a matter of taste, where().first differs from find_by in a subtle and not so obvious way.
source: https://github.com/rubocop-hq/rails-style-guide/issues/76
If you access User model with find or where commands. It will always give you the first entry sorted by created_at. You can always sort the data depending on parameters.
You can use find_by as follows
User.find_by_first_name('Fred')
Obviously, it give first user of Fred

Resources