How to check if all lessons have been viewed - ruby-on-rails

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

Related

Rails: has_many through multiple models

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?

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

ruby on rails filtering pg_search_scope v2.2

I'm very new to Ruby on Rails and I need help figuring out how to amend an existing db query. I'm using old versions, which I cannot update: Ruby 2.2.3p173, and Rails 4.0.2.
I want to filter the existing query results to remove records which do not have any videos. The model hierarchy I think is: Artist, AlbumGroup, Album, Track, Video.
To clarify: I want artists with at least 1 video, from the model association artist->AlbumGroup->albums->tracks->videos (not artist->videos).
The existing query is contained in model Artist:
require_dependency "tagging"
require_dependency "similar"
class Artist < ActiveRecord::Base
has_many :images, dependent: :destroy
has_many :taggings, dependent: :destroy
has_many :artforms, through: :taggings
has_many :similars, foreign_key: "similar_id", dependent: :destroy
has_many :similar_artists, through: :similars, source: :similar_to
has_many :reverse_similars, foreign_key: "similar_to_id", class_name: "Similar", dependent: :destroy
has_many :similar_to_artists, through: :reverse_similars, source: :similar
has_many :memberships, foreign_key: "member_id", dependent: :destroy
has_many :groups, through: :memberships, source: :membership
has_many :group_members, foreign_key: "membership_id", class_name: "Membership", dependent: :destroy
has_many :members, through: :group_members, source: :member
has_many :users, through: :followed_artists
has_many :videos, dependent: :destroy
has_many :audios, dependent: :destroy
has_many :metrics, through: :audios
has_many :releases, foreign_key: 'artist_id', class_name: "AlbumGroup"
has_many :albums
has_many :collaborations
has_many :album_groups, through: :collaborations
mount_uploader :mugshot, MugshotUploader
include PgSearch
pg_search_scope :for_name,
against: :name,
using: { tsearch: {threshold: '1', dictionary: 'simple', tsvector_column: 'tsv_name', prefix: true, normalization: 2}},
ranked_by: "(artists.popularity / 50 * :tsearch) + :tsearch"
end
I want to add something like the following to filter out the records which do not have any videos: (to the query):
if: artist.releases.albums.tracks.videos.count > 1
Or the Artist model maybe?:
scope :valid, -> {where("video_count > 1")}
The other code for the different models is below:
class AlbumGroup < ActiveRecord::Base
belongs_to :artist
has_many :collaborations
has_many :artists, through: :collaborations
has_many :albums
has_many :taggings, dependent: :destroy
has_many :artforms, through: :taggings
mount_uploader :artwork, MugshotUploader
def as_json options={}
{
id: id,
title: title
}
end
end
class Album < ActiveRecord::Base
belongs_to :album_group
belongs_to :artist
has_many :tracks
end
class Track < ActiveRecord::Base
has_many :playlist_tracks, dependent: :destroy
has_many :playlists, through: :playlist_tracks
belongs_to :audio
belongs_to :video
has_many :videos
has_many :audios
has_many :taggings, dependent: :destroy
has_many :artforms, through: :taggings
belongs_to :album
belongs_to :artist
default_scope order: "position ASC"
after_save :cache_columns
def cache_columns
if image_url.nil?
img = album.album_group.artwork_url(:tiny)
unless img.nil?
update_column(:image_url,img)
end
end
if artist_name.nil?
if artist_id
name = Artist.find(artist_id).name
update_column(:artist_name,name)
end
end
if album_name.nil?
if album_id
title = Album.find(album_id).title
update_column(:album_name,title)
end
end
end
end
class Video < ActiveRecord::Base
belongs_to :artist
belongs_to :event
belongs_to :track
has_many :tracks
has_many :playlists, through: :tracks, order: "tracks.position ASC"
scope :valid, -> {where("flag_count < 2").order("score DESC") }
scope :flagged, -> {where("flag_count > ?", 1) }
# validates :url, uniqueness: {scope: :artist_id}
end
ActiveRecord's has_many and belongs_to methods give you the ability to define scopes directly on the association. This way, you can customize the SQL that is generated when you access the association.
let’s say you wanted to define a scope on the Artist model which returns all artists who have zero videos. In Rails 5, to find all artists that have no videos, you can use left_outer_joins
class Artist < ActiveRecord::Base
has_many :videos
scope :with_zero_videos, -> { left_outer_joins(:videos).where(videos: {id: nil}) }
end
Another good solution is to use includes like this:
Artist.includes(:videos).where(videos: { videos_id: nil })
You can choose the one you like the most!
Hope it helped.
Thanks Gabriel, I've added a raw SQL query to artists_controller.rb, which looks like:
def index
#artists = Artist.for_name(params[:name])
artists = Artist.find_by_sql ["SELECT artists.*, ((artists.popularity / 50 * (ts_rank((artists.tsv_name), (to_tsquery('simple', ''' ' || ? || ' ''' || ':*')), 2))) + (ts_rank((artists.tsv_name), (to_tsquery('simple', ''' ' || ? || ' ''' || ':*')), 2))) AS pg_search_rank FROM artists " +
"JOIN album_groups on (artists.id = album_groups.artist_id) " +
"JOIN tracks on (tracks.album_group_id = album_groups.id) " +
"JOIN videos on (videos.track_id = tracks.id) " +
"WHERE (((artists.tsv_name) ## (to_tsquery('simple', ''' ' || ? || ' ''' || ':*')))) " +
"GROUP BY artists.id " +
"ORDER BY pg_search_rank DESC, artists.id ASC", params[:name], params[:name], params[:name]]
render json: artists, each_serializer: SimpleArtistSerializer
end

Creating a feed => NoMethodError: undefined method for ActiveRecord_Associations_CollectionProxy

I am implementing a feed of all cards belonging to the tags a user has subscribed to and I get the following error. It's probably something trivial but I can't pinpoint what needs work.
NoMethodError: undefined method `cards' Tag::ActiveRecord_Associations_CollectionProxy:0x007fbaa46239f8>
Here are my models:
class User < ActiveRecord::Base
has_many :cards, dependent: :destroy
has_many :tags, through: :cards
has_many :subscriptions, dependent: :destroy
has_many :subscribed_tags, through: :subscriptions, source: :tag
end
class Tag < ActiveRecord::Base
has_many :taggings, dependent: :destroy
has_many :cards, through: :taggings
has_many :subscriptions, dependent: :destroy
has_many :subscribers, through: :subscriptions, source: :user
end
class Card < ActiveRecord::Base
acts_as_votable
belongs_to :user
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
def self.tagged_with(name)
Tag.find_by_name!(name).cards
end
def self.tag_counts
Tag.select("tags.*, count(taggings.tag_id) as count").
joins(:taggings).group("taggings.tag_id")
end
def tag_list
tags.map(&:name).join(", ")
end
def tag_list=(names)
self.tags = names.split(",").map do |n|
Tag.where(name: n.strip).first_or_create!
end
end
end
What I am really trying to do is to run current_user.subscribed_tags.cards and retrieve an array of cards I can reorder and output as a timeline.
Thanks
subscribed_tags - it's a scope (where(user: self)), you can call where or join on them but not an item methods.
In your case you want to use a scope
class Card
scope :with_subscription, -> { joins(tags: :subscriptions) }
end
# In controller
current_user.cards.with_subscription.order('cards.created_at DESC')
You can imagine current_user.cards like another form of Cards.where(user: current_user). Once you tell that you will retrieve Card array - it cannot be changed. You cannot do user.cards.subscriptions or User.where(id: user).cards.tags the only you can do it's filter.
Next we filter with joins(:subscriptions). It will give us inner join, so we get cards belonged to user that have subscriptions. And it's a scope we can modify further passing for example an order.
activerecord join associations

How do I properly alias a has_many through a join in Rails ActiveRecord?

Rails/ActiveRecord newbie here. Consider the following models for a Classroom, User and ClassroomEnrollments (join between the two)
class Classroom < ActiveRecord::Base
has_many :fulltime_enrollments, -> { where(duration: 'full-time') }, class_name: "ClassroomEnrollments"
has_many :fulltimers, :through => :fulltime_enrollments, class_name: "User"
has_many :parttime_enrollments, -> { where(duration: 'part-time') }, class_name: "ClassroomEnrollments"
has_many :parttimers, :through => :parttime_enrollments, class_name: "User"
end
class ClassroomEnrollment < ActiveRecord::Base
# columns: user_id, classroom_id, duration
belongs_to :user
belongs_to :classroom
end
class User < ActiveRecord::Base
has_many :classroom_enrollments
has_many :classrooms, :through => :classroom_enrollments
end
The following model for a classroom and classroom_enrollments does not work. Specifically the :fulltimers and :parttimers aliases throw undefined method 'to_sym' for nil:NilClass errors when I try to access them via my_classroom.fulltimers or my_classroom.parttimers.
If I remove the :parttimers alias and rename :fulltimers to :users it works fine (and displays only the full time students), so it seems to me that it has something to do with it figuring out that :fulltimers is of type User, even though I've specified the classname: "User" in the has_many condition.
What am I doing wrong?
Since the source association cannot be inferred automatically, you need specify it using the :source option:
class Classroom < ActiveRecord::Base
has_many(
:fulltime_enrollments,
-> { where(duration: 'full-time') },
class_name: "ClassroomEnrollments"
)
has_many :fulltimers, :through => :fulltime_enrollments, :source => :user
has_many(
:parttime_enrollments,
-> { where(duration: 'part-time') },
class_name: "ClassroomEnrollments"
)
has_many :parttimers, :through => :parttime_enrollments, :source => :user
end
http://guides.rubyonrails.org/association_basics.html#options-for-has-many-source
How about trying a cleaner, more readable approach? Something like this:
class Classroom < ActiveRecord::Base
has_many :classroom_enrollments
has_many :users, through: :classroom_enrollments
def full_timers
users_by_duration("full-time")
end
def part_timers
users_by_duration("part-time")
end
private
def users_by_duration(duration)
users.where(classroom_enrollments: { duration: duration })
end
end
Then:
my_classroom = Classroom.find(1)
my_classroom.full_timers
I stumbled on this while working on something similar. This is will generate the same sql and is a bit easier to look at.
class Classroom < ActiveRecord::Base
has_many :classroom_enrollments
has_many :users, through: :classroom_enrollments
def fulltimers
users.merge(ClassroomEnrollment.full_time)
end
def parttimers
users.merge(ClassroomEnrollment.part_time)
end
end
class ClassroomEnrollment < ActiveRecord::Base
belongs_to :user
belongs_to :classroom
scope :fulltime, ->{ where(duration: 'full-time') }
scope :parttime, ->{ where(duration: 'part-time') }
end
class User < ActiveRecord::Base
has_many :classroom_enrollments
has_many :classrooms, :through => :classroom_enrollments
end

Resources