Add attribute to Rails 5 model, which not exists in the database - ruby-on-rails

I have tables - users and articles. It is one to many relation or user can have many articles. What I want is to select all users from users table with LIMIT 40, but include count all articles for each user - add property for each user count_articles. I am trying in this way:
def self.get_users(limit, offset)
users = []
order('created_at').limit(limit).offset(offset).each do |user|
user.attributes[:count_articles] = user.articles.count
users << user
byebug
end
users
end
, but I am getting users without this attribute. If I use byebug and type in the
console - user.count_articles, I can see the result of count_articles for current user, but if I type only user, I see all the attributes without count_articles.

You could try with:
User.joins(:articles)
.select('users.id, users.name, COUNT(articles.id) AS count_articles')
.group('users.id')
.limit(40)

I'm doing s.th. similar with a sub-select. So no join is required.
Try putting this into your User Model:
def self.get_users(limit=40, offset=0)
order(:created_at)
.limit(limit)
.offset(offset)
.select('users.*, (SELECT COUNT(id) FROM articles WHERE articles.id = users.id) articles_count')
end
The resulting User instances in the returned Array will then have the attribute "articles_count".

Related

How to make a joint fetch?

I am building a Rails 5.2 app.
In this app I got three objects:
User
Id
Firstname
Lastname
Conversation
Id
Entity_type
Entity_subtype
Assignment
Id
User_id
Assignable_id (ID of Conversation)
Assignable_type (Class name of Conversation)
One or more Users are connected to a Conversation through Assignments.
I am trying to create a query that checks if two Users are having a direct message Conversation or I need to create a new one automatically.
I have this code and it "works" (I get no error) but it returns a Conversation even though only one of the Users are involved in a Conversation. But I need it to only return a Conversation if Both Users share the Same Conversation, ie they are talking to each other.
Conversation.where(entity_type: "conversation", entity_subtype: "direct").joins(:assignments).where(:assignments => {:user_id => ["cdd3c6be-ac78-46f2-a7ae-7f2299b6fedb", "32117e53-9b2f-49c6-8cc8-3a9eb9003a2e"] })
One way to do this is by using GROUP and using HAVING to set a condition on the group:
class Conversion < ActiveRecord::Base
def self.between(*users)
joins(:assignments)
.group(:id)
.where(assignments: { user_id: users })
.having(Assignment.arel_table[:id].count.eq(users.length))
end
end
The advantage is that this approach can be used with any number of users.
If you change out .eq to .gteq you can get conversions that include the two users but isn't a private conversation. On Postgres you can use Assignment.arel_table[Arel.star] as an optimization.
Join assignments twice and in resulting rows, pick a row with assignment user ids match.
user_ids = ["cdd3c6be-ac78-46f2-a7ae-7f2299b6fedb", "32117e53-9b2f-49c6-8cc8-3a9eb9003a2e"]
Conversation
.where(entity_type: "conversation", entity_subtype: "direct")
.joins("LEFT JOIN assignments as1 ON assignments.assignable_id = conversations.id AND assignments.assignable_type = 'Conversation'")
.joins("LEFT JOIN assignments as2 ON assignments.assignable_id = conversations.id AND assignments.assignable_type = 'Conversation'")
.where('as1.user_id = as2.user_id')
.where('as1.user_id = ? AND as2.user_id = ?', user_ids[0], user_ids[1])
Will give you list of convos, both are involved

Rails: Any way to get multiple first/last records of one-many models?

I have a User model, and User model has_many :roles.
Say I have 1000 Users' record in the database, is there a way to obtain the first/last record of the roles for each User's record? i.e what I would like to achieve is to retrieve user.roles.first or user.roles.last for each User's record.
I could do something like
User.all.each do |u|
puts u.roles.first # or u.roles.last
end
but in this case, the code would loop 1000 times. Is there any simpler (and elegant) way to achieve the same purpose?
You can improve your query like this way. Still have to loop over users.
User.includes(:roles).find_each do |user|
puts user.roles[0] #First Role
puts user.roles[-1] #Last Role
end
If you dont want to do a loop, you can create foreign keys for first and last Role in your "users" table and update them every time a new role is created. The query should be like
User.includes(:first_role, :last_role)
i assume that user-roles is a one-many relationship, then you can get first/last role each user with one query (postgres/mysql):
class Role < ApplicationRecord
belongs_to :user
scope :first_each_user, ->(sort) {
from(
<<~SQL
(
SELECT roles.*,
row_number() OVER (
PARTITION BY roles.user_id
ORDER BY roles.created_at #{sort.to_s}
) AS rn
FROM roles
) roles
SQL
).where("roles.rn = 1")
}
end
# first
Role.first_each_user(:asc)
# last
Role.first_each_user(:desc)

Filter table on attribute of first result from joined table

I have two tables users and task_lists, users has_many task_lists.
task_lists belongs to users and has an attribute tasks_counter.
users
|id|
task_lists
|id|user_id|tasks_counter|
I would like to find all the users whose first (MIN(id)) tasks_list has a tasks_counter < 5.
How would I achieve this in PostGreSQL? I'm using Rails, if somebody knows a solution using ActiveRecords.
This will set users_ids variable with an Array containing all User id's whose first TaskList has a tasks_counter < 5:
user_ids = TaskList.select("MIN(id) AS id, user_id, tasks_counter")
.group(:user_id) # Get first TaskList for each user
.select { |t| t.tasks_counter < 5 } # Keep users tha meet criteria
.pluck(:user_id) # Return users' id in array
If you would like to get an ActiveRecord_Relation object with User objects you can use the result from the previous query and filter User.
users = User.where(id: user_ids)
Or, everything in one line:
users = User.where(id: TaskList.select("MIN(id) AS id, user_id, tasks_counter")
.group(:user_id)
.select { |t| t.tasks_counter < 5 }
.pluck(:user_id))

How to write query in active record to select from two or more tables in rails 3

I don't want to use join
I want to manually compare any field with other table field
for example
SELECT u.user_id, t.task_id
FROM tasks t, users u
WHERE u.user_id = t.user_id
how can i write this query in Rails ??
Assuming you have associations in your models, you can simply do as follow
User.joins(:tasks).select('users.user_id, tasks.task_id')
you can also do as follow
User.includes(:tasks).where("user.id =tasks.user_id")
includes will do eager loading check the example below or read eager loading at here
users = User.limit(10)
users.each do |user|
puts user.address.postcode
end
This will run 11 queries, it is called N+1 query problem(first you query to get all the rows then you query on each row again to do something). with includes Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
Now when you do;
users = User.includes(:address).limit(10)
user.each do |user|
puts user.address.postcode
end
It will generate just 2 queries as follow
SELECT * FROM users LIMIT 10
SELECT addresses.* FROM addresses
WHERE (addresses.user_id IN (1,2,3,4,5,6,7,8,9,10))
Plus if you don't have associations then read below;
you should be have to look at http://guides.rubyonrails.org/association_basics.html
Assuming your are trying to do inner join, by default in rails when we associate two models and then query on them then we are doing inner join on those tables.
You have to create associations between the models example is given below
class User
has_many :reservations
...# your code
end
And in reservations
class Reservations
belongs_to :user
... #your code
end
Now when you do
User.joins(:reservations)
the generated query would look like as follow
"SELECT `users`.* FROM `users` INNER JOIN `reservations` ON `reservations`.`user_id` = `users`.`id`"
you can check the query by doing User.joins(:reservations).to_sql in terminal
Hopefully it would answer your question
User.find_by_sql("YOUR SQL QUERY HERE")
You can use as follows..
User.includes(:tasks).where("user.id =tasks.user_id").order(:user.id)

Could I add an association based on another association?

My User model looks like:
User
habtm :Roles
Role
habtm :Users
RoleExtension
belongs_to :Role
mysql tables:
users
id
..
roles
id
..
roles_users
user_id
role_id
role_extensions
id
role_id
feature_id
..
..
Now everything seems to be working fine so far.
Now I want the User model to have a collection of RoleExtensions, based on the habtm Roles collection.
example:
user = User.find(1)
user.Roles (returns roles with id's of 1,2,3)
So I want:
user.RoleExtensions
to return all Role extensions that have role_id in (1,2,3)
Normally you'd use a has_many, :through association, but that doesn't apply to has_and_belongs_to_many relations.
So, instead, in your User model:
def role_extensions
return roles.inject([]) do |array, role|
role.role_extensions do |re|
array.include?(re) ? array << re : array
end
end
end
Then my_user.role_extensions should return an array of all role extensions belonging to all the user's roles.
Note: I haven't tested this, but it should work
UPDATE: I like this better
def role_extensions
return roles.inject([]) { |array, role| array << role.role_extensions }.flatten!.uniq
end
user = User.find(1)
RoleExtension.find(:all, :conditions => ["role_id IN (?)", user.role_ids])
Otherwise you can use nested joins.
Try this -
# Fetch user object
user = User.first
# If you want roles of that user try this
roles = user.roles
# You can map all the role extensions of that user by
role_extensions = user.roles.map(&:role_extensions).uniq
Be aware that this will be extremely slow for large number of roles. In that case better write your own query method. Something like
role_extensions = RoleExtension.where("role_id in (?)", user.role_ids).all
#user.role_extensions.where(:joins => :roles)

Resources