I'm trying to define a scope on my Movie model in order to select all movies with an average rating higher then the provided value.
So far I have the following models:
class Movie < ActiveRecord::Base
# Callbacks & Plugins
# Associations
has_and_belongs_to_many :categories
has_many :ratings
# Validations
validates :name, presence: true, uniqueness: true
validates :description, presence: true
# Scopes
scope :category, -> (category) { joins(:categories).where("categories.id = ?", category) }
scope :searchable, -> (query) { where("name LIKE '%?%'", query) }
scope :rating, -> (rating) { joins(:ratings).average("ratings.value")) }
end
class Rating < ActiveRecord::Base
# Callback & plugins
# Associations
belongs_to :user
belongs_to :movie, counter_cache: true
# Validations
validates :value, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }
validates :user, presence: true, uniqueness: { scope: :movie_id }
end
Now I'm playing around with the query options in Rails.
What I want to do is have a scope that selects all ratings for the particular movie. Calculates the average using the value property of the rating. And if that value is equal or higher to the provided value, that movie is selected.
As in the code i've been playing with the joins and average query options, but I'm not sure how to combine them in getting what I want.
Think I found it...
scope :rating, -> (rating) { joins(:ratings).group("movies.id").having("AVG(ratings.value) > ? OR AVG(ratings.value) = ?", rating, rating) }
Generates the following query for me:
Movie Load (1.9ms) SELECT "movies".* FROM "movies" INNER JOIN "ratings" ON "ratings"."movie_id" = "movies"."id" GROUP BY movies.id HAVING AVG(ratings.value) > 1 OR AVG(ratings.value) = 1
Which is what I want I think. Will test it with some Rspec now to see if it works.
Related
Every day I need to send letters to users with today's tasks.
For do this I need to find all users who are allowed to send letters, and among these users to find all cards that have a deadline today. The result is three array elements with a nil value. How is this better done and right?
#users = User.all {|a| a.receive_emails true}
#user_cards = []
#users.each_with_index do |user, index|
#user_cards[index] = user.cards.where(start_date: Date.today).find_each do |card|
#user_cards[index] = card
end
end
My user model:
class Card < ApplicationRecord
belongs_to :user
# also has t.date "start_date"
end
My card model:
class User < ApplicationRecord
has_many :cards, dependent: :destroy
# also has t.boolean "receive_emails", default: false
end
Something like #cards_to_send = Card.joins(:users).where("users.receive_emails = true").where(start_date: Date.today)
Have a look at https://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-the-joined-tables for the docs on how to query on a joined table.
You could do this with a SQL join like this
User.joins(:cards).where(receive_emails: true, cards: { start_date: Date.today })
https://guides.rubyonrails.org/active_record_querying.html#joining-tables
I've been reading this, but can't make sense of writing it into a Rails scope :
find all parent records where all child records have a given value (but not just some child records)
I have a Course, Section, and Quiz, object :
class Course < ActiveRecord::Base
has_many :course_members
has_many :members, through: :course_members
has_many :sections
has_many :quizzes, through: :sections
end
class Quiz < ActiveRecord::Base
belongs_to :member
belongs_to :section
end
class Section < ActiveRecord::Base
belongs_to :course
has_many :quizzes
end
I'd like to find all courses of a member, where all quizzes related to that course have the attribute completed = true.
So in my Member class, I'd ideally like to write something like :
has_many :completed_courses, -> {
joins(:courses, :quizzes, :sections)
# .select( 'CASE WHEN quizzes.completed = true then 1 end') ??? maybe ???
}, class_name: 'Course'
Haha! But barring that being too complicated. I've been trying to write this simply in the Course would also be fine.
class Member < ActiveRecord::Base
has_many :courses, through: :course_members
has_many :course_members
has_many :completed_courses,
-> { joins(:quizzes).where.not(quizzes: {completed: [false, nil]}) },
through: :course_members,
source: :course
end
If your completed boolean column is NOT NULL, then change [false, nil] above to just simply false
Usage Example
irb(main):002:0> Member.first.completed_courses
Member Load (0.2ms) SELECT "members".* FROM "members" ORDER BY "members"."id" ASC LIMIT 1
Course Load (0.1ms) SELECT "courses".* FROM "courses" INNER JOIN "sections" ON "sections"."course_id" = "courses"."id" INNER JOIN "quizzes" ON "quizzes"."section_id" = "sections"."id" INNER JOIN "course_members" ON "courses"."id" = "course_members"."course_id" WHERE (NOT (("quizzes"."completed" = 'f' OR "quizzes"."completed" IS NULL))) AND "course_members"."member_id" = ? [["member_id", 1]]
I have a rails app with the models below. I have both assigned_tasks and executed_tasks for a given user. I would like to know which option is better for getting all the tasks (executed and assigned as well) for that given user.
task.rb
belongs_to :assigner, class_name: "User"
belongs_to :executor, class_name: "User"
user.rb
has_many :assigned_tasks, class_name: "Task", foreign_key: "assigner_id", dependent: :destroy
has_many :executed_tasks, class_name: "Task", foreign_key: "executor_id", dependent: :destroy
Solution 1:
task.rb
scope :completed, -> { where.not(completed_at: nil) }
scope :uncompleted, -> { where(completed_at: nil) }
user.rb
def tasks_uncompleted
tasks_uncompleted = assigned_tasks.uncompleted.order("deadline DESC")
tasks_uncompleted += executed_tasks.uncompleted.order("deadline DESC")
tasks_uncompleted.sort_by { |h| h[:deadline] }.reverse!
end
tasks_controller:
#tasks = current_user.tasks_uncompleted.paginate(page: params[:page], per_page: 12)
Solution 2:
task.rb
scope :completed, -> { where.not(completed_at: nil) }
scope :uncompleted, -> { where(completed_at: nil) }
scope :alltasks, -> (u) { where('executor_id = ? OR assigner_id = ?', u.id, u.id) }
tasks_controller
#tasks = Task.alltasks(current_user).uncompleted.order("deadline DESC").paginate(page: params[:page], per_page: 12)
You should define an association on User that will return all of the Tasks associated by either executor_id or assigner_id:
class User < ActiveRecord::Base
has_many :assigned_and_executed_tasks,
->(user) { where('executor_id = ? OR assigner_id = ?', user, user) },
class_name: 'Task',
source: :tasks
end
user = User.find(123)
user.assigned_and_executed_tasks
# => SELECT tasks.* FROM tasks WHERE executor_id = 123 OR assigner_id = 123;
Then you can do as you do in "Solution 2," but instead of the unfortunate Task.alltasks(current_user) you can just do current_user.assigned_and_executed_tasks (of course you could give it a shorter name, but descriptive names are better than short ones):
#tasks = current_user.assigned_and_executed_tasks
.uncompleted
.order("deadline DESC")
.paginate(page: params[:page], per_page: 12)
Solution 2 will be the more efficient way of retrieving the records from your database. In most Rails apps, calls to the database are a frequent cause of bottlenecks, and in solution 2 you make one call to the database to retrieve all the records, but in solution 1 you make two calls to the database to retrieve the same information.
Personally, I also think this solution is much more readable, easily testable, and maintainable, so solution 2 is better in many ways beyond speed!
I'm working in rails. My model is like this:
class Topic < ActiveRecord::Base
has_many :topics, dependent: :delete_all
belongs_to :parent, foreign_key: 'topic_id', class_name: 'Topic'
has_many :contents
validates :name, uniqueness: true, presence: true
end
So I have a topic that can have many "sub-topics". Every sub-topic can have many sub-topics, indefinitely. I'm trying to make a method that returns me all "leaf" topics. A leaf topic is a topic with no sub-topics.
def self.leafs
where(???)
end
I can't formulate this in active record logic, so actually I use this query:
Topic.find_by_sql("SELECT * FROM topics WHERE id NOT IN (SELECT t.topic_id FROM topics t WHERE topic_id IS NOT NULL)")
How can I write this in an active record way?
Try this:
child_ids_with_topic_id = where.not(topic_id: nil).pluck(:topic_id)
where.not(id: child_ids_with_topic_id)
def self.leafs
topics.where("topic_id IS NOT NULL")
end
ActiveRecord 4.0 and above adds where.not so you can do this:
scope :leafs, -> topics.where.not(topic_id: nil)
scope :without_topics, includes(:topics).where(:topics => { :id => nil })
Although i am not sure but i have tried using this Rails 3 finding parents which have no child
scope :leafs, joins("left join topics as sub_topics sub_topics.topic_id = topics.id").where("topics.topic_id is null")
I need help in doing the following join using ActiveRecord relation models:
select "access_urls"."id", "controller_urls"."controller", "action_urls"."action" from
"access_urls"
inner join "controller_urls"
on "controller_urls"."id" = "access_urls"."controller_url_id"
inner join "action_urls"
on "action_urls"."id" = "access_urls"."action_url_id"
Where I have AccessUrl, ActionUrl and ControllerUrl models
class AccessUrl < ActiveRecord::Base
belongs_to :controller_url
belongs_to :action_url
end
class ActionUrl < ActiveRecord::Base
has_many :access_urls
validates :action, presence: true, uniqueness: { message: "já encontra-se em uso." }
end
class ControllerUrl < ActiveRecord::Base
has_many :access_urls
validates :controller, presence: true, uniqueness: { message: "já encontra-se em uso." }
end
Can anyone help me?
Using joins:
result = AccessUrl.joins(
:controller_url, :action_url
).select(
'access_urls.id, controller_urls.control, action_urls.action'
)
This will give you a relation result containing objects of AccessUrl. You can loop through the result and access the selected columns as:
result.each do |r|
# r.id
# r.control
# r.action
end