Active Record Third-Tier Relation Query - mysql

I have models User, Filter and Messages:
User has_many :filters and has_many :messages, as: :owner
Filter belongs_to :user, has_many :messages, as: :processor
Messages belongs_to :owner, polymorphic: true and :processor, polymorphic: true
What is the right way to query "Filter.find_by(user_id: [#whatever].id).messages"without returning undefined method "messages" for nil:NilClass?

Try this :
Filter.find_by(user_id: [#whatever].id).try(:messages)

Related

has_many :through multiple joins table

I'm trying to create a 4 way joins table.
class User < ApplicationRecord
has_many :user_configurations
has_many :teams, through: :user_configurations
has_many :companies, through: :user_configurations
scope :supervisors, -> { joins(:user_configurations).where(user_configurations: { supervisor: true }) }
scope :agents, -> { joins(:user_configurations).where(user_configurations: { supervisor: false }) }
end
class Team < ApplicationRecord
has_many :user_configurations
has_many :users, through: :user_configurations
belongs_to :company_unit
end
class CompanyUnit < ApplicationRecord
has_many :user_configurations
has_many :users, through: :user_configurations
has_many :teams
belongs_to :company
end
class Company < ApplicationRecord
has_many :user_configurations
has_many :users, through: :user_configurations
has_many :company_units
has_many :teams, through: :company_units
end
class UserConfiguration < ApplicationRecord
belongs_to :user, optional: true
belongs_to :team, optional: true
belongs_to :company, optional: true
#supervisor:boolean in this table
end
When I create I get 2 separate entries into the UserConfiguration Table.
Company.first.users.create(team_ids: [1])
id: 1, user_id: 1, team_id: nil, company_id: 1
id: 2, user_id: 1, team_id: 1, company_id: nil
I don't know if it's good practice to attempt something like this any suggestions will be really helpful thanks. Every search results in trying to do a sql join to query data.
EDIT: Decided not to do this and will try and figure out a different approach.
I would set it up with indirect relationships instead:
class User
has_many :employments
has_many :companies, through: :employments
has_many :positions
has_many :teams, through: :positions
end
class Company
has_many :employments
has_many :users, through: :employments
has_many :teams, through: :users
end
class Team
has_many :positions
has_many :users, through: :positions
has_many :companies, through: :users
end
# join model between User and Company
class Employment
belongs_to :user
belongs_to :company
end
# join model between User and Team
class Position
belongs_to :user
belongs_to :team
end
While you could potentially use a single 3 way join model this violates the Single Responsibility Principle and does not map the domain very well.
3 way joins introduce quite a bit of complexity as you cannot simply unlink two records by deleting a row in the join table. And ActiveRecord does not automatically keep track of the foreign key columns.
If you want to add roles to this data model there are a few ways to do it:
1 add an enum to the join table:
# join model between User and Team
class Position
enum role: [:grunt, :supervisor]
belongs_to :user
belongs_to :team
end
2 create a reusable role system.
class User
has_many :user_roles
has_many :roles, through: :user_roles
def has_role?(name, resource = nil)
roles.exists?(name: name, resource: resource)
end
end
class Role
belongs_to :resource, polymorpic: true, optional: true
has_many :user_roles
end
class UserRole
belongs_to :user
belongs_to :role
end
This is a really flexible system lets you attach roles to anything - companies, teams etc. And lets you build systems where the roles can even by defined by end users. Check out the rolify gem for a full example.

Cant list conversations for current_user that is recipient of the conversation

I have a problem listing one of my models.. I am following the tutorial http://josephndungu.com/tutorials/gmail-like-chat-application-in-ruby-on-rails
In this tutorial you make a conversation that has a recipient_id and a sender_id these are both foreign keys to the conversation table. the current_user is set on either recipient or sender when the conversation is made.
-I can only list the current_user.conversations when the current_user is a sender.
-When i try with a current_user that is a recipient I get no result.
-Figured out that in my user model I have a has_many conversations with a foreign_key :sender_id. When I change this to recipient_id, then only the current_user that is a recipient can list conversations.
-With this I assume that I need to have two foreign keys referencing the same user? What can I do to get conversations to list for both current_users?
User.rb "model"
has_many :conversations, class_name: "Conversation", :foreign_key => :sender_id
listing conversation for current_user:
<% current_user.conversations.each do |conversation| %>
<p>hi</p>
<% end %>
Conversation.rb "model"
class Conversation < ActiveRecord::Base
belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'
has_many :messages, dependent: :destroy
validates_uniqueness_of :sender_id, :scope => :recipient_id
scope :involving, -> (user) do
where("conversations.sender_id =? OR conversations.recipient_id =?",user.id,user.id)
end
scope :between, -> (sender_id,recipient_id) do
where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND conversations.recipient_id =?)", sender_id,recipient_id, recipient_id, sender_id)
end
end
The problem is that in your User.rb model you are saying that The conversations of this user are those whose sender_id is this user. That is the reason why when you use current_user.conversations you only get those records where sender_id is the current user.
I would do the following in your User.rb model:
has_many :conversations_started, class_name: "Conversation", :foreign_key => :sender_id
has_many :conversations_continued, class_name: "Conversation", :foreign_key => :recipient_id
This way you would be able to invoke:
current_user.conversations_started
or
current_user.conversations_continued
However, if you want to be able to list them all, you will need to define an additional method in your User.rb model:
def conversations
conversations_started + conversations_continued
end
If a user can start a conversation with itself, I would add .uniq at the end of the conversations method

rails_admin has_many through with extra field

I have 2 models. User and Project. And there is a many_to_many relation with and extra position field between them. I can't see that extra field on rails_admin edit page.
How can add that field to form?
user.rb
has_many :projects, :through => :works_ons
project.rb
has_many :users, :through => :works_ons
works_on.rb
attr_accessible :position, :project_id, :user_id
belongs_to :user
belongs_to :project
Is it true that your user model has_many :users, :through => :works_ons?
I'm wondering if you need
user.rb
has_many :projects, :through => :works_ons
This is the closest I found. You should probably add a custom field below the association and enter the extra fields.
https://github.com/sferik/rails_admin/wiki/Has-many-%3Athrough-association

Associate foreign key through another foreign key

I've these 3 Models
class User < ActiveRecord::Base
has_many :answers, :as => :owner
end
class Answer < ActiveRecord::Base
belongs_to :owner, :polymorphic => true
has_one :test
end
class Test < ActiveRecord::Base
belongs_to :answer
end
so I want to associate the Test model with User model through Answer Model without need of creating new association between them, so I put the following into Test Model:
has_one :owner, :through => :answer
but it doesn't work and I got this error
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have a has_many :through association 'Test#owner' on the polymorphic object 'Owner#owner'.
any help?
In Test:
delegate :owner, :to => :answer
You have to specify source_type option as owner is a polymorphic association
class Test < ActiveRecord::Base
belongs_to :answer
has_one :owner, :through => :answer, :source_type => "User"
end

ActiveRecord Multi-association query

So I've got three models:
User
User Interest
Interest Tags
User Model
class User < ActiveRecord::Base
has_many :user_interests
end
InterestTag Model
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
UserInterest Model
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
I'd like to use ActiveRecord to include the name of the user's interests when loading their profile using the following query:
#user = User.find(current_user.id, :include => [{:user_interests => :interest_tags}])
Migrations for interest_tags + user_interests
create_table :interest_tags do |t|
t.string :name, :null => false, :size => 30
t.timestamp :created_at
end
create_table :user_interests do |t|
t.integer :user_id
t.integer :interest_tag_id
end
What am I doing wrong?
You have to add an has_many :through association on User model.
class User < ActiveRecord::Base
has_many :user_interests
has_many :interest_tags, :through => :user_interests
end
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
Now you can eager load the tags as follows:
User.find(current_user.id, :include => :interest_tags)
Note:
You might want to look at the acts_as_taggable_on gem for your requirement.
I assume you are building a tagging system, like in stackoverflow: every user kann have multiple tags that they are interested in. In that case the user_interests table is only a join table and does not need a model. Just use has_and_belong_to_many on the two real models.
See also this article on different ways to implement tagging with more or less normalized relational databases. You could also use a non-relational database like Mongodb, there you would need only one table to do tagging, see the cookbook.