Rails: has_many through multiple models - ruby-on-rails

I am trying to access all the projects for which there are expenses from or payments to a given supplier.
class Project < ApplicationRecord
has_many :payments #, dependent: :destroy
has_many :expenses, dependent: :restrict_with_error
has_many :suppliers,-> { distinct }, through: :expenses
end
class Supplier < ApplicationRecord
has_many :expenses, dependent: :destroy
has_many :payments, dependent: :destroy
has_many :projects, through: :expenses
end
class Expense < ApplicationRecord
belongs_to :project
belongs_to :supplier #, optional: true
end
class Payment < ApplicationRecord
belongs_to :project
belongs_to :supplier
scope :belongs_to_project, -> (project_id) { joins(:project).where("projects.id = ?", "#{project_id}")}
end
What would be ideal is if I could do
class Supplier < ApplicationRecord
has_many :projects, through: [:expenses, :payments]
end
but since that does not work, I have resorted to
def balances
project_ids = #supplier.projects.pluck(:id)
Project.all.each do |project|
#if there are no expenses (project not in #supplier.projects) but there are payments to supplier for the project, append the project.id to project_ids
if !project_ids.include? project.id and #supplier.payments.belongs_to_project(project.id).any?
project_ids << project.id
end
end
#projects = Project.find( project_ids )
end
Is there a more elegant way to do this?

Related

ActiveRecord grab shared model from polymorphic association

I'm looking for a better way to query Users from 2 different Models used in a polymorphic association. Here is the setup
class Schedule < ApplicationRecord
belongs_to :announcement
has_many :targets, dependent: :destroy
has_many :lists, through: :targets, source: :target, source_type: 'List'
has_many :accounts, through: :targets, source: :target, source_type: 'Account'
end
class Target < ApplicationRecord
# belongs_to :announcement
belongs_to :schedule
belongs_to :target, polymorphic: true
delegate :announcement, to: :schedule
end
class List < ApplicationRecord
belongs_to :account
has_many :targets, as: :target, dependent: :destroy
has_many :lists_users
has_many :users, through: :lists_users
end
class Account < ApplicationRecord
has_many :announcements, dependent: :destroy
has_many :targets, as: :target, dependent: :destroy
has_many :users, dependent: :destroy
end
At the moment I'm solving this by creating a method inside the Schedule model that grabs Users this way:
def subscribers
targets.map(&:target).map(&:users).flatten.uniq
end
I looked at something similar with this question, but didn't seem to solve it.
I would do that like this:
class Schedule < ApplicationRecord
def subscribers
# fetch all associated user IDs
lists_user_ids = lists.joins(:lists_users).distinct.pluck("lists_users.user_id")
accounts_user_ids = accounts.joins(:users).distinct.pluck("users.id")
user_ids = (lists_user_ids + accounts_user_ids).uniq
# fetch users by IDs
User.where(id: user_ids)
end
end

How to check if all lessons have been viewed

I want to define a method (complete) that returns true if a user has watched all lessons of a course. Right now the method only returns false even if all the lessons are viewed.
User model:
has_many :enrolments, dependent: :destroy
has_many :courses, through: :enrolments
has_many :views
has_many :lessons, through: :views
def enrol(course)
courses << course
end
def enrolled?(course)
courses.include?(course)
end
def view(lesson)
lessons << lesson
end
def viewed?(lesson)
lessons.include?(lesson)
end
def complete(course)
if self.viewed?(course.lessons.all)
return true
else
return false
end
end
Lesson Model:
belongs_to :course
has_one_attached :file
default_scope -> { order(created_at: :desc) }
has_many :views
has_many :users, through: :views
Course Model:
belongs_to :company
has_many :lessons, dependent: :destroy
default_scope -> { order(created_at: :desc) }
has_many :enrolments, dependent: :destroy
has_many :users, through: :enrolments
View Model:
belongs_to :user
belongs_to :lesson
Your viewed? checks only for a single lesson, but you're passing a relation there.
For this case it's sufficient to compare arrays with lessons ids:
def complete(course)
lessons.where(course: course).ids.sort == course.lessons.ids.sort
end

How to create only one game with a boolean field true for each provider

The models I have:
Category:
class Category < ApplicationRecord
has_many :categorizations
has_many :providers, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Provider:
class Provider < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorization:
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :provider
has_many :games, dependent: :destroy
accepts_nested_attributes_for :games
end
Game:
class Game < ApplicationRecord
belongs_to :categorization
end
The games table has most_popular field that is boolean. I need that only one game for each provider could have that boolean field set to true. If I had to make that only one game could have a boolean field = true, I would do smth. like this:
class Game < ApplicationRecord
belongs_to :categorization
validate :only_one_most_popular_game
scope :most_popular, -> { where(most_popular: true) }
protected
def only_one_most_popular_game
return unless most_popular?
matches = Game.most_popular
if persisted?
matches = matches.where('id != ?', id)
end
if matches.exists?
errors.add(:most_popular, 'Can\'t have another most popular game.')
end
end
end
So, what is the best way to solve this issue? Thanks ahead.
You don't want to look at all games, you only want to look at the ones for that categorization.
def only_one_most_popular_game
return unless most_popular?
if categorization.games.most_popular.where('id != ?', id).first
errors.add(:most_popular, 'Can\'t have another most popular game.')
end
end
EDIT
You can also specify
class Provider < ApplicationRecord
has_many :games, through: :categories
(above requires Rails 3.1 or later)
Which will let you do...
if categorization.provider.games.most_popular.where('games.id != ?', id).first

Update nested_attributes

A course has_many students and student has_many courses
Using a json API how would we update course to assign multiple students to a course
Model
class Course < ActiveRecord::Base
has_many :course_students
has_many :students, through: course_students
accepts_nested_attributes_for :course_students
end
class Student < ActiveRecord::Base
has_many :course_students
has_many :courses, through: course_students
end
class CourseStudent < ActiveRecord::Base
belongs_to :course
belongs_to :student
end
Controller
class CoursesController < SessionsController
def update
if #course.update_attributes(course_params)
puts "students should now be added to course"
end
end
def course_params
params.require(:course).permit(:description, :status, course_students_attributes: [:id], course_jobs_attributes: [:id])
end
end
Am I on the right path?
If your relationship is many to many, you are missing the keyword through in the association declaration:
class Course < ActiveRecord::Base
has_many :students, through: :course_students
accepts_nested_attributes_for :course_students
end
class Student < ActiveRecord::Base
has_many :courses, through: :course_students
end
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Also be careful with accepts_nested_attributes_for, specially with validations. Here you can read more: https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through

twitter model associations in rails

I am trying to build a twitter like data model in rails. This is what I have come up with.
class User < ActiveRecord::Base
has_many :microposts, :dependent => :destroy
end
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :mentions
has_many :hashtags
end
class Mention< ActiveRecord::Base
belongs_to :micropost
end
class Hashtag < ActiveRecord::Base
belongs_to :micropost
end
Should I be using a has_many through association somewhere or is this accurate?
Edit: The final twitter MVC model.
class User < ActiveRecord::Base
has_many :microposts, :dependent => :destroy
userID
end
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :link2mentions, :dependent => :destroy
has_many :mentions, through: :link2mentions
has_many :link2hashtags, :dependent => :destroy
has_many :hashtags, through: :link2hashtags
UserID
micropostID
content
end
class Link2mention < ActiveRecord::Base
belongs_to :micropost
belongs_to :mention
linkID
micropostID
mentionID
end
class Mention < ActiveRecord::Base
has_many :link2mentions, :dependent => :destroy
has_many :microposts, through: :link2mentions
mentionID
userID
end
Edit 2: A concise and accurate explanation
http://railscasts.com/episodes/382-tagging?view=asciicast
If two microposts use the same hashtag, you probably don't want to create two database records for that hashtag. In this case you would use has_many through:
class Hashtagging < ActiveRecord::Base
belongs_to :micropost
belongs_to :hashtag
end
class Hashtag < ActiveRecord::Base
has_many :hashtaggings
has_many :microposts, through: :hashtaggings
end
class Micropost < ActiveRecord::Base
...
has_many :hashtaggings
has_many :hashtags, through: :hashtaggings
end
When you create the Hashtagging migration, make sure it has the micropost_id and hashtag_id columns.

Resources