I have 2 models:
class User < ActiveRecord::Base
has_one :employee, dependent: :destroy
end
class Employee < ActiveRecord::Base
belongs_to :user
end
and I noticed that when I call User.last from the console, I get the following:
> User.last
User Load (3.9ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
User Load (3.9ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
Employee Load (9.3ms) SELECT "employees".* FROM "employees" WHERE "employees"."user_id" = $1 LIMIT 1 [["user_id", 21198]]
Employee Load (9.3ms) SELECT "employees".* FROM "employees" WHERE "employees"."user_id" = $1 LIMIT 1 [["user_id", 21198]]
#<User:0x0055bd0ae9af30> { ... }
Why is the employee record being loaded?
Related
I have 3 models : User, Conversation and ConversationUser
class User < ApplicationRecord
has_many :conversation_users, dependent: :destroy
has_many :conversations, through: :conversation_users
end
class Conversation < ApplicationRecord
has_many :conversation_users, dependent: :destroy
has_many :users, through: :conversation_users
end
class ConversationUser < ApplicationRecord
belongs_to :conversation
belongs_to :user
end
My purpose
I would like to find a Conversation when 2 users have the same conversation (a conversation must only have 2 conversation_users with the id of these 2 users)
For the test
I only created ONE conversation with TWO conversation_users
conversation = Conversation.create
conversation.conversation_users.create(user: User.first)
conversation.conversation_users.create(user: User.second)
My Problem
I want to find the conversation with JUST the first User AND second User.
If I do the following query, Active Record will still show me a conversation because it's doing a OR clause but I need a AND.
The correct result should show me an empty array because there is not a conversation with User.first AND User.third
Conversation.joins(:conversation_users).where(conversation_users: {user_id: [User.first.id, User.third.id]})
User Load (3.3ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 [["LIMIT", 1]]
User Load (2.5ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 OFFSET $2 [["LIMIT", 1], ["OFFSET", 2]]
Conversation Load (3.9ms) SELECT "conversations".* FROM "conversations" INNER JOIN "conversation_users" ON "conversation_users"."conversation_id" = "conversations"."id" WHERE "conversation_users"."user_id" IN ($1, $2) LIMIT $3 [["user_id", "274d2f54-2418-4dec-a696-a5f62196ee85"], ["user_id", "0ef2f797-3518-4096-951b-1837ca28e4e4"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Conversation id: "35556705-a162-4ee4-9776-963ae87bcfa8", created_at: "2020-04-20 09:34:05", updated_at: "2020-04-20 09:34:05">]>
Expectations
Should find a Conversation with User.first and User.second
Should NOT find a Conversation with User.first and User.third
I tried this query for the first expectation but it is returning an empty array
Conversation.joins(:conversation_users).where(conversation_users: {user_id: User.first.id}).where(conversation_users: {user_id: User.second.id})
User Load (3.8ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 [["LIMIT", 1]]
User Load (4.0ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 OFFSET $2 [["LIMIT", 1], ["OFFSET", 1]]
Conversation Load (3.3ms) SELECT "conversations".* FROM "conversations" INNER JOIN "conversation_users" ON "conversation_users"."conversation_id" = "conversations"."id" WHERE "conversation_users"."user_id" = $1 AND "conversation_users"."user_id" = $2 LIMIT $3 [["user_id", "274d2f54-2418-4dec-a696-a5f62196ee85"], ["user_id", "44a0c35a-b5c1-4bb6-a8a3-30ae3d4b3345"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
class Conversation < ApplicationRecord
has_many :user_conversations
has_many :users, through: :user_conversations
def self.between(*users)
users.map do |u|
where("EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = ?)", u)
end.reduce(&:merge)
.joins(:user_conversations)
.group(:id)
.having('count(*) = ?', users.length)
end
end
Not the cleanest solution ever but what is does is check for the existance of a join record for each user and then .having('count(*) = ?', users.length) ensures that there are not more join records then users.
Example usage:
Conversation.between(User.first, User.second)
Conversation.between(1,2,3)
Conversation.between(*User.all) # splat it like a pro
This results in the following SQL query:
SELECT "conversations".* FROM "conversations"
INNER JOIN "user_conversations" ON "user_conversations"."conversation_id" = "conversations"."id"
WHERE
(EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = 1))
AND
(EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = 2))
GROUP BY "conversations"."id"
HAVING (count(*) = 2)
LIMIT $1
You can do a query with AND condition as follows
Conversation.joins(:conversation_users).where(conversation_users: {user_id: User.first.id}).where(conversation_users: {user_id: User.second.id})
Using Rails 6 and CanCanCan. Here are my models:
class Shop < ApplicationRecord
has_many :shop_reviews, dependent: :destroy
end
class ShopReview < ApplicationRecord
belongs_to :shop, counter_cache: true
belongs_to :user_profile, counter_cache: true
end
class User < ApplicationRecord
has_one :user_profile, dependent: :destroy
has_many :shop_reviews, through: :user_profile
end
class UserProfile < ApplicationRecord
belongs_to :user
has_many :shop_reviews
end
ShopReview table:
id, shop_id, user_profile_id, review
Association:
1. A shop can have many reviews
2. Only one User Profile can have one review on a shop
Abilities I want to define:
1. User Profile can edit, update, destroy his own review of a shop
2. User Profile cannot create a new review if a review of his exist on that shop
What I tried:
class Ability
include CanCan::Ability
def initialize(user)
can :read, :all
if user.present?
can [:update, :destroy], ShopReview, user_profile_id: user.user_profile.id
can :create, ShopReview do
!ShopReview.exists?(user_profile_id: user.user_profile.id, shop_id: :shop_id)
end
end
end
end
But I can't seem to pass in the shop_id on visiting /shops/11/shop_reviews/new. Here's the log:
Started GET "/shops/11/shop_reviews/new" for 127.0.0.1 at 2020-01-04 00:23:10 +0800
Processing by ShopReviewsController#new as HTML
Parameters: {"shop_id"=>"11"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 9], ["LIMIT", 1]]
UserProfile Load (0.2ms) SELECT "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = $1 LIMIT $2 [["user_id", 9], ["LIMIT", 1]]
↳ app/models/ability.rb:9:in `initialize'
ShopReview Exists? (0.3ms) SELECT 1 AS one FROM "shop_reviews" WHERE "shop_reviews"."user_profile_id" = $1 AND "shop_reviews"."shop_id" = $2 LIMIT $3 [["user_profile_id", 8], ["shop_id", nil], ["LIMIT", 1]]
↳ app/models/ability.rb:11:in `block in initialize'
Shop Load (0.2ms) SELECT "shops".* FROM "shops" WHERE "shops"."id" = $1 ORDER BY "shops"."created_at" DESC LIMIT $2 [["id", 11], ["LIMIT", 1]]
You should use gem pundit to create authorizations in your app. Pundit is easy to scale than gem cancancan. Because pundit allows your app to create authorization on each model. You can search more about two gems and choose what is most suitable for you.
In my controllers/common/roles_controller.rb I would like to check if particular role (ID) belongs to current_user company users and if not, redirect to errors_path:
def correct_role
role_user = Role.where(:id => params[:id]).select('user_id').first
company_user = current_user.companies.includes(:users)
redirect_to(errors_path) unless company_user.include? role_user.id
end
Definitions:
role_user - finds user ID for particular role ID ("user_id" is column of roles table)
company_user - finds all user ID who belong to companies, which belong to current_user
models/role.rb
belongs_to :user, optional: true, inverse_of: :roles
accepts_nested_attributes_for :user
enum general: { seller: 1, buyer: 2, seller_buyer: 3}, _suffix: true
enum dashboard: { denied: 0, viewer: 1, editer: 2, creater: 3, deleter: 4}, _suffix: true
models/user.rb
#User has roles
has_many :roles
accepts_nested_attributes_for :roles, reject_if: proc { |attributes| attributes[:name].blank? }
# User has many companies
has_many :accounts, dependent: :destroy
has_many :companies, through: :accounts
models/account.rb
class Account < ApplicationRecord
belongs_to :company
belongs_to :user
accepts_nested_attributes_for :company, :user
end
models/company.rb
has_many :accounts, dependent: :destroy
has_many :users, through: :accounts
At the moment with role_user and company_user I can find both ID, however I cannot do the checking part. How do I do that correctly, please? Thank you for any help!
Update
#sajan code give this in console when I open /common/roles/1/edit (current_user ID=1 and should be allowed to edit):
Parameters: {"id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.6ms) SELECT COUNT(*) FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.3ms) SELECT "roles".id FROM "roles" WHERE "roles"."user_id" = ? [["user_id", 1]]
#Abhishek Kumar code in console gives:
Parameters: {"id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Company Load (0.5ms) SELECT "companies".* FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.4ms) SELECT "users".id FROM "users" INNER JOIN "accounts" ON "users"."id" = "accounts"."user_id" WHERE "accounts"."company_id" = ? [["company_id", 13]]
Role Load (0.2ms) SELECT "roles"."user_id" FROM "roles" WHERE "roles"."id" = ? ORDER BY "roles"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
Update v2
So I'm trying to use this code:
def correct_role
company_user_ids = current_user.companies.map(&:user_ids)
role_user = Role.where(:id => params[:id]).select('user_id').first
unless role_user.user_id.in?(company_user_ids)
redirect_to(errors_path)
end
end
however it redirects to errors_path in any case, this is what I have in console:
Parameters: {"id"=>"1"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Company Load (0.5ms) SELECT "companies".* FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.4ms) SELECT "users".id FROM "users" INNER JOIN "accounts" ON "users"."id" = "accounts"."user_id" WHERE "accounts"."company_id" = ? [["company_id", 13]]
Role Load (0.3ms) SELECT "roles"."user_id" FROM "roles" WHERE "roles"."id" = ? ORDER BY "roles"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
It seems that a better logic would be to define on the model for the current_user an association through companies and users to roles.
Then you can check:
def correct_role
redirect_to(errors_path) unless current_user.company_user_roles.where(id: params[:id]).exists?
end
It would be a single SQL statement that would return zero or one rows, and execute very quickly with the appropriate indexes in place.
Edit:
To company.rb, add:
has_many :user_roles, through: :users, source: :roles
To user.rb (assuming that current_user is an instance of this model) add:
has_many :company_user_roles, through: :companies, source: :user_roles
Maybe you could do like this:
def correct_role
unless current_user.companies.count > 0 && current_user.role_ids.include?(params[:id])
redirect_to(errors_path)
end
end
My model is
class TelecomOperator < ActiveRecord::Base
has_many :users
class User < ActiveRecord::Base
belongs_to :telecom_operator
When I do TelecomOperator.first.users it gives me all users but When I do
User.first.telecom_operator it always give me nil
SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 LIMIT 1 [["id", 2]]
=> nil
When I do
User.includes(:telecom_operator).find(2)
It works fine
I'm trying to make will_paginate's :order to work with this array:
#posts = current_user.subscribed_tags.map(&:posts).flatten.paginate(:page => params[:page],
per_page => 5,
:order => "created_at DESC")
Right now, it doesn't matter what value I give to :order, posts are ordered by tag (I think the date of creation of the tag). I want them to appear according to the posts creation date. I think the problem is that will_paginate is using the tags as reference and not the posts.
How to solve this issue (Maybe I have to define #posts in another way)?
Additional Information:
Users can subscribe to tags (So in the index page the user only sees posts with tags he or she is subscribe to).
Models:
User model:
class User < ActiveRecord::Base
(Devise)
has_many :posts, :dependent => :destroy
has_many :subscriptions
has_many :subscribed_tags, :source => :tag, :through => :subscriptions
attr_writer :subscribed_tag_names
after_save :assign_subscribed_tags
def subscribed_tag_names
#subscribed_tag_names || subscribed_tags.map(&:name).join(' ')
end
private
def assign_subscribed_tags
#self.subscribed_tags = []
return if #subscribed_tag_names.blank?
#subscribed_tag_names.split(" ").each do |name|
subscribed_tag = Tag.find_or_create_by_name(name)
self.subscribed_tags << subscribed_tag unless subscribed_tags.include?(subscribed_tag)
end
end
end
Tag model:
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :posts, :through => :taggings
has_many :subscriptions
has_many :subscribed_users, :source => :user, :through => :subscriptions
def tag_posts_count
"#{self.name} (#{self.posts.count})"
end
end
Generated SQL:
Processing by PostsController#index as HTML
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Tag Load (0.5ms) SELECT "tags".* FROM "tags" INNER JOIN "subscriptions" ON "tags"."id" = "subscriptions"."tag_id" WHERE "subscriptions"."user_id" = 2
Post Load (0.7ms) SELECT "posts".* FROM "posts" INNER JOIN "taggings" ON "posts"."id" = "taggings"."post_id" WHERE "taggings"."tag_id" = 6
Post Load (0.6ms) SELECT "posts".* FROM "posts" INNER JOIN "taggings" ON "posts"."id" = "taggings"."post_id" WHERE "taggings"."tag_id" = 10
Post Load (0.7ms) SELECT "posts".* FROM "posts" INNER JOIN "taggings" ON "posts"."id" = "taggings"."post_id" WHERE "taggings"."tag_id" = 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Tag Load (0.5ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."post_id" = 42
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 4 LIMIT 1
Tag Load (0.5ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."post_id" = 38
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Comment Load (0.6ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 52 ORDER BY "comments"."id" DESC LIMIT 1
CACHE (0.0ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 52 ORDER BY "comments"."id" DESC LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
CACHE (0.0ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 52 ORDER BY "comments"."id" DESC LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Tag Load (0.5ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."post_id" = 52
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5 LIMIT 1
Tag Load (0.5ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."post_id" = 55
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5 LIMIT 1
Comment Load (0.7ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 57 ORDER BY "comments"."id" DESC LIMIT 1
CACHE (0.0ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 57 ORDER BY "comments"."id" DESC LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
CACHE (0.0ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 57 ORDER BY "comments"."id" DESC LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Tag Load (0.5ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."post_id" = 57
Rendered layouts/_sidebar.html.erb (12.6ms)
Rendered posts/index.html.erb within layouts/application (314.3ms)
Rendered layouts/_header.html.erb (5.2ms)
Rendered layouts/_footer.html.erb (0.1ms)
Completed 200 OK in 1242ms (Views: 392.8ms | ActiveRecord: 12.5ms)
You are calling paginate on a regular array rather than on a sql result set.
[1,2,3,4].paginate(:page => 1, :per_page => 2) # [1, 2]
current_user.subscribed_tags.map(&:posts) # returns an array
If you are on Rails 3.1 and above:
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
has_many :subscriptions
has_many :subscribed_tags, :source => :tag, :through => :subscriptions
has_many :subscribed_posts, :source => :posts, :through => :subscribed_tags
end
class Subscription
belongs_to :user
belongs_to :tag
end
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :posts, :through => :taggings
has_many :subscriptions
end
Now you can
current_user.subscribed_posts.paginate(:page => params[:page],
per_page => 5,
:order => "created_at DESC")