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
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})
i'm using rails 4 and was wondering if anyone could find what's wrong in my code.
I have project model and I created a team model that has a belongs_to - has_one relation with project.
project model:
class CrmProject < ActiveRecord::Base
has_one :crm_team
team model
class CrmTeam < ActiveRecord::Base
belongs_to :crm_project
accepts_nested_attributes_for :crm_project
belongs_to :crm_section
belongs_to :manager, class_name: "User"
has_many :users
accepts_nested_attributes_for :users
end
When submitting the form to create a new team i get this error:
ActiveModel::MissingAttributeError in CrmTeamsController#create
can't write unknown attribute `crm_team_id`
and log from server :
Parameters: {"crm_team"=>{"crm_project"=>"5", "manager"=>"3", "user_ids"=>["", "2"]}, "co
mmit"=>"Create Crm team"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1
[["id", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
(0.1ms) begin transaction
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE ("users"."email" = 'email#gmail.com' AND "users"."
id" != 2) LIMIT 1
SQL (0.2ms) INSERT INTO "crm_teams" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2020-03-1
1 07:29:19.735115"], ["updated_at", "2020-03-11 07:29:19.735115"]]
(0.2ms) rollback transaction
Completed 500 Internal Server Error in 9ms (ActiveRecord: 1.0ms)
When you use a has_one relation like this
has_one :crm_team
Rails expects you to add crm_team_id to your crm_project, in order to understand which crm_team is related with crm_project object. Adding it going to solve your problem.
In my opinion defining opposite like this much better in logic. Because in future these teams can have multiple projects.
class CrmProject < ActiveRecord::Base
belongs_to :crm_team
class CrmTeam < ActiveRecord::Base
***has_one/has_many(pick one)*** :crm_project
accepts_nested_attributes_for :crm_project
belongs_to :crm_section
belongs_to :manager, class_name: "User"
has_many :users
accepts_nested_attributes_for :users
end
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.
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?
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