Rails associations confusing - ruby-on-rails

I have 4 models, which are Company, Candidate, Job and Application
For Company
has_many :candidates
has_many :jobs
For Candidate
belongs_to :company
has_one :application
For Jobs
belongs_to :company
has_many :applications
For Application
belongs_to :candidate
belongs_to :job
I'm not sure whether the relationships between Candidate, Jobs and Application are correct or not. It would be great if someone can give some suggestions for improvement. Thank you.

You're on the right track. Adding indirect assocations as well will let you query up and down the heirarchy:
class Company < ApplicationRecord
has_many :jobs
has_many :applications, through: :jobs
has_many :candidates, through: :applications
end
class Job < ApplicationRecord
belongs_to :company
has_many :applications
has_many :candidates, through: :applications
end
class Application < ApplicationRecord
belongs_to :candidate
belongs_to :job
has_one :company, through: :job
end
class Candidate < ApplicationRecord
has_many :applications
has_many :jobs, through: :applications
has_many :companies, through: :jobs
end

I think the easiest way to build activerecord associations is to imagine your associations in real life. In this case, a company has several jobs, each job has several applications, and each application has one candidate.
Hence the relation would be
for Company
has_many :jobs
for Job
belongs_to :company
has_many :applications
for Application
belongs_to :job
has_one :candidate
for Candidate
belongs_to :application

Related

How to differentiate similar has_many :through associations in Rails?

I'll start off with my models:
class Project < ApplicationRecord
has_many :permissions
has_many :wallets, through: :permissions
has_many :follows
has_many :wallets, through: :follows
end
class Permission < ApplicationRecord
belongs_to :project
belongs_to :wallet
end
class Follow < ApplicationRecord
belongs_to :project
belongs_to :wallet
end
class Wallet < ApplicationRecord
has_many :permissions
has_many :projects, through: :permissions
has_many :follows
has_many :projects, through: :follows
end
As you can see, Permission and Follow are both through associations for Projects and Wallets.
They serve different purposes (Permission gives Wallets access to manage Projects while Follow lets Wallets "follow" projects for updates).
So how can I differentiate them? For example, if I do Wallet.find(1).projects, it defaults to using the "Follow" model...though in some scenarios I'd want it to actually use the "Permission" model.
Believe you'd find it will default to the has_many :projects that is defined last.
Need to give the associations different names, which will require something like ...
class Wallet < ApplicationRecord
has_many :permissions
has_many :projects, through: :permissions
has_many :follows
has_many :follow_projects, through: :follows, source: :project
end

build methods for has_one though has_one

Rails 5.1.2
Ruby 2.5.3
I understand there are multiple ways to impliment this relationship, however, this question is more about why the following doesn't work rather than solving a real world problem.
has_many setup
class Subscriber < ApplicationRecord
has_many :subscriptions, inverse_of: :subscriber
has_many :promotions, through: :subscriptions, inverse_of: :subscriptions
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :promotions
end
class Subscription < ApplicationRecord
belongs_to :subscriber, inverse_of: :subscriptions
belongs_to :promotion, inverse_of: :subscriptions
end
class Promotion < ApplicationRecord
has_many :subscriptions, inverse_of: :promotion
has_many :subscribers, through: :subscriptions, inverse_of: :subscriptions
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :subscribers
end
In the above Subscriber model which is setup to use has_many relationships following would work:
s = Subscriber.new
s.subscriptions.build
# OR
s.promotions.build
Following that, I would expect Subscriber to behave the same way with has_one relationships
has_one setup
class Subscriber < ApplicationRecord
has_one :subscription, inverse_of: :subscriber
has_one :promotion, through: :subscription, inverse_of: :subscriptions
accepts_nested_attributes_for :subscription
accepts_nested_attributes_for :promotion
end
class Subscription < ApplicationRecord
belongs_to :subscriber, inverse_of: :subscription
belongs_to :promotion, inverse_of: :subscriptions
end
class Promotion < ApplicationRecord
has_many :subscriptions, inverse_of: :promotion
has_many :subscribers, through: :subscriptions, inverse_of: :subscription
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :subscribers
end
However, attempting to build the nested promotion association with the equivalent has_one build methods results in a NoMethodError (undefined method 'build_promotion' for #<Subscriber:0x00007f9042cbd7c8>) error
s = Subscriber.new
s.build_promotion
However, this does work:
s = Subscriber.new
s.build_subscription
I feel it's logical that one should expect to build nested has_one relationships in the same way one builds has_many.
Is this a bug or by design?
Checking the code, when you call has_one, it creates the build_, create_ and create_..! methods ONLY if the reflection is "constructable"
https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activerecord/lib/active_record/associations/builder/singular_association.rb#L16
define_constructors(mixin, name) if reflection.constructable?
Now, checking the constructable? method, it returns the result of calculate_constructable https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L452
And for the HasOne class, it returns false if you use the :through option https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L723
def calculate_constructable(macro, options)
!options[:through]
end
So, I'd say it's not a bug, it's made like that by design. I don't know the reason though, maybe it feels logical but I guess there's some things to consider that are not that simple.

Rails: Check if Profile applied for job

I have an application where a Profile applies for jobs. There is a has_many through relationship between Jobs and Profiles where all the Relationships get saved on a table called "Relationships".
Let's say a Profile visits a job. How can I check if there's a relationship between that profile (current_profile) and the job he's viewing?
Models associations:
class Profile < ActiveRecord::Base
belongs_to :user
has_many :relationships , dependent: :destroy
has_many :jobs, through: :relationships
end
class Job < ActiveRecord::Base
belongs_to :employer
has_many :relationships, dependent: :destroy
has_many :profiles, through: :relationships
end
class Relationship < ActiveRecord::Base
belongs_to :profile
belongs_to :job
end
It is very simple just check if id of current_profile is exist in relationship
for e.g
if Relationship.all.include? current_profile.id
The answer given by #kunashir worked. I just had to check:
current_user.profile.jobs.where(id: current_job_id).any?

Ruby: specify has_many :through association when several has_many :through associations are given

i have a simple data set-up with a model for Users and a model for Tasks.
Between these two models i have two has_many :through associations: Fellowships and Assignements. In total i want to specify for a task several followers and several assignees.
I now want to display for a specific task all assignees and all followers.
If there would only be one association I simply could do #task.users. As i have two associations i want to specify by which association i want to get all users.
See my code:
class User < ActiveRecord::Base
has_many :assignments
has_many :tasks, through: :assignments
has_many :fellowships
has_many :tasks, through: :fellowships
end
class Task < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
has_many :fellowships
has_many :users, through: :fellowships
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Fellowship < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
Let's assume i have a task as
#task = Task.first
I now want to have all assignees and all followers with something like
#assignees = #task.users "over association assignment"
#followers = #task.users "over association followship"
but i don't know how to do this.
Thanks for the help!
You can write in following way.
has_many :assignment_tasks ,through: :assignments ,source: :task
has_many :fellowship_tasks, through: :fellowships, source: :task

Modeling "Likes" in rails HABTM vs HM/BT

What would be the best method to model "likes" in rails for my app. I could either to the following:
class User < ActiveRecord::Base
has_many :things
has_many :likes
has_many :liked_things, through: :likes, source: :thing
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :thing
end
class Thing < ActiveRecord::Base
belongs_to :user
has_many :likes
has_many :liking_users, through: :likes, source: :user
end
Or
class User < ActiveRecord::Base
has_many :things
has_and_belongs_to_many :things
end
class Thing < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :users
end
What approach would be best and why? I plan to have an activity feed in my app as well, if that helps determine the best approach.
The answer to this question depends on whether or not Like will ever have any attributes or methods.
If its only purpose of existence is to be the HABTM relationship between Users and Things, then using the has_and_belongs_to_many relationship would suffice. In your example, having has_many and belongs_to is redundant. All you would need in this case is:
class User < ActiveRecord::Base
has_and_belongs_to_many :things
end
class Thing < ActiveRecord::Base
has_and_belongs_to_many :users
end
On the other hand, if you anticipate that a Like will have an attribute (e.g. maybe someone will really like something, or love it, etc.) then you can do
class User < ActiveRecord::Base
has_many :likes
has_many :liked_things, through: :likes, source: :thing
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :thing
end
class Thing < ActiveRecord::Base
has_many :likes
has_many :liking_users, through: :likes, source: :user
end
Note that I removed has_many :things and belongs_to :user as they are redundant.

Resources