Using includes with AREL functions - ruby-on-rails-3

I have the following classes in my application:
class Prompt
has_many :entries
end
class Entry
belongs_to :prompt
belongs_to :user
def self.approved
where("is_approved")
end
end
class User
has_many :entries
end
And I want to display a table of all "approved" entries for a given prompt and the users that they belong_to. To generate this list I do the following query:
prompt = Prompt.find(prompt_id, :include => {:entries => :user})
But when I run the following loop, it makes a query for each user rather than using the prefetched users
prompt.entries.approved.each do |entry|
puts entry.user.id
end
How do I rewrite this so that it doesn't do a query for each iteration of the loop?

It does a query for each user because entries.approved is calling the query *where('is_approved')* for each entry. Your find statement is merely pulling all of the prompts and creating objects with access to their child attributes. I think what you need is a where statement that selects all of the entries that have the attribute 'is_approved' and then run through a loop printing their ids.
Maybe try something like #entries = Entry.where(is_approved: true).includes(entries: user) )
Then
entries.user.each do |user|
puts user.id
end
Hope thats helps.

Related

How to reduce queries on associated object with Rails ActiveRecord

I have the following problem.
I have an Organization class that returns and array of "authorized user" emails and their associated User ID.
class Organization < ApplicationRecord
...
has_many :authorized_users
def authorized_user_opts
self.authorized_users.map do |authorized_user|
[authorized_user.email, authorized_user.user.id]
end
end
end
Then the AuthorizedUser class - note that we lookup the user via a find_by:
class AuthorizedUser < ApplicationRecord
...
def user
User.find_by(email: email)
end
end
And the User model:
class User < ApplicationRecord
validates :email, presence: true
end
This creates an extra query for each user to get their ID. Is there a way I can improve this query?
I thought about migrating the AuthorizedUser class to add a user_id field, but I'm wondering if there's a way to improve this just SQL instead of adding another field.
I think the addition of a user_id to AuthorizedUser is a decent choice, but if you must do it without, you should be able to use joins and includes:
authorized_users.joins("inner join users on users.email = authorized_users.email").includes(:users)
joins here is doing what an ActiveRecord association would do under the hood, and then includes eager loads the user objects in one query so that you don't have N queries for N users.
You might also be able to mess with the options on belongs_to which lets you specify the key that it uses under the hood. Something like:
# authorized_user.rb
belongs_to :user, foreign_key: 'email', primary_key: 'email'

Add Arbitrary Attribute to SQL Query from Joins Record without WHERE clause (Active record)

I'm trying to create an attribute in my select statement that depends on whether or not an association exists. I'm not sure if it's possible with a single query, and the goal is to not have to iterate a list afterward.
Here is the structure.
class Project < ApplicationRecord
has_many :subscriptions
has_many :users, through: :subscriptions
end
class User < ApplicationRecord
has_many :subscriptions
has_many :projects, through: :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :project
belongs_to :user
end
Knowing a project, the goal of the query is to return ALL users and include on them a new attribute call subscribed - denoting whether or not they are subscribed.
non-working code (pseudo code):
project = Project.find_by(name: 'has_subscribers')
query = 'users.*, (subscriptions.project_id = ?) AS subscribed'
users = User.includes(:subscriptions).select(query, project.id)
user.first.subscribed
# => true or false
I'm open to whether or not there is a better way of going about this. However, the information is:
You know the project record.
You query a list of ALL users
Each user record has a subscribed attribute, denoting whether its
subscribed to the given project
Solution:
I was able to figure out a straight forward solution using the bool_or aggregate method. Coalesce ensures that the value returned is false instead of nil, should no subscriptions exists.
query = "users.*, COALESCE(bool_or(subscriptions.project_id = '#{project_id}'::uuid), false) as subscribed"
User.left_outer_joins(:subscriptions)
.select(query)
.group('users.id')
Yep, you can do this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id])
Which will result in a SQL query like this:
SELECT *, "subscriptions"."project_id" FROM "users" INNER JOIN "subscriptions" ON "subscriptions"."user_ud" = "users"."id";
If you want to specify a specific project (i.e. use an expression), you can do it with Arel like this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42))
Unfortunately, you won't have a column name alias, and you can't call as on an Arel::Nodes::Equality instance. I don't know enough about the internals of Arel to have a way out of that box. But you can do this if you want the composability of Arel (e.g. if this is going to be something that needs to work with multiple models or columns):
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42).to_sql + " as has_project")
This is a bit clunky, but it works and provides a user.has_project method that returns a boolean. You can pretty it up like so:
class User
scope :with_project_status, lambda do |project_id|
has_project =
Subscription.arel_table[:project_id].
eq(project_id).to_sql + " as has_project"
joins(:projects).select(Arel.star, has_project)
end
end
User.with_project_status(42).where(active: true)

Latest updates, but only one per polymorphic object using GROUP

I have three models:
class Update
attr_accessible :object_id, :object_type
belongs_to :object, :polymorphic
default_scope order('created_at DESC')
end
class Document
has_many :updates, as: :object
end
class Report
has_many :updates, as: :object
end
Now, using sql or the Active Record query interface I want to be able to grab pick out the lastest Update for each object.
I was thinking that I would do something like this:
Update.group(:object_id).group(:object_type)
That does indeed give me only one update per object, but it is not always the latest update and I am not sure why.
Does group just select a random record from the group? Is there a simple way to ensure that the latest update gets selected?

Simple has_many :through association

Pretty simple setup. I want to make sure my understanding of the ORM is correct.
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through => memberships
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through => memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
Now when a user creates a group I want the membership record in the link table to get populated. It should be an atomic(transaction).
class GroupsController < ApplicationController
def create
#group = current_user.groups.build(params[:group])
if #group.save
flash[:notice] = "Group has been created."
redirect_to #group
else
flash[:alert] = "Group has not been created."
render :action => "new"
end
end
end
This doesn't work. The group gets saved but no membership record created in the link table. However using a create vs build works. Is that how it's supposed to work?
What's the best approach here?
This behaviour is by design. As you mentioned, you can either do #group = current_user.groups.create(params[:group]).
Or you can add an additional statement to create a record in the join model's table as :
#group = current_user.groups.build(params[:group])
if #group.save
#group.memberships.create(:user_id => current_user)
# redirect and notify
Well, the reason being simply building #group and saving it does not add an additional record in the join table.
Infact, in this case, #group = current_user.groups.build(params[:group]) is somewhat similar to #group = Group.new(params[:group]). The difference being, in the former case, current_user.groups will contain #group (you can try that in Groups#create before redirect) but doing current_user.reload followed by current_user.groups will yield [].
The best way to do this is somewhat similar to your approach. Have a simple create action as :
def create
#group = Group.new(params[:group])
# if else for save and redirect
However, for this to work the params hash submitted to Groups#create should include user_ids as :
"group"=>{"name"=>"new group", "user_ids"=>["1", "2", "3"]}, "commit"=>"Create Group"
May be that was the reason why #bruno077 was asking you to paste your view's code, so as to get an idea on user_ids params being passed.
So, if the new group form contains fields to select multiple users, then its simple create action as shown right above (because of the user_ids params). But if have a new group form with no options to select users, then you are better off using the first option (one using create).

How to get a scope for all database rows in rails 3?

assume we the following setup:
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts
end
assume further the user has a boolean attribute 'admin', which indicates if he is a global admin or not.
I want to write a method (or a scope?) for the User class, called 'visible_posts'. If the user is no admin, it should return just its own posts. If he IS admin the method should return all posts in the system.
My first attempt was something like this:
class User < ActiveRecord::Base
[...]
def visible_posts
if admin?
Post.all
else
posts
end
end
end
Problem here is that Post.all returns an Array, but I would rather like to have an ActiveRecord::Relation like I get from posts to work with it later on.
Is it somehow possible to get an ActiveRecord::Relation that represents ALL posts ?
You can do Post.scoped i guess in Rails
And later on this you can call .all to fetch the results