ActiveRecord select with predicate on childrens - sql

I have such db structure:
term.rb:
class Term < ActiveRecord::Base
has_many :tasks, through: :students
...
def accepted_tasks_count
tasks.where(status: Task.statuses[:accepted]).count
end
task.rb:
class Task < ActiveRecord::Base
has_many :notes, through: :submissions
...
def notes_count
self.notes.count
end
I need to add some method which will return accepted tasks without notes.
How can I do that?

Try this one:
class Task < ActiveRecord::Base
has_many :notes, through: :submissions
scope :accepted, -> { where(status: self.statuses[:accepted]) }
scope :without_notes, -> { includes(:notes).where(notes: { id: nil }) }
end
I've moved 'accepted tasks' query to the scope too, to make it reusable.
class Term < ActiveRecord::Base
has_many :tasks, through: :students
def accepted_tasks_count
tasks.accepted.count
end
end
To get all accepted tasks without notes, use this:
Task.accepted.without_notes
To get all accepted tasks without notes for the specific term, use that:
#term.tasks.accepted.without_notes

Related

Combining a scope on HABTM and belongs_to associations with OR

I have the following models:
class User < ApplicationRecord
has_and_belongs_to_many :lists
end
class Workspace < ApplicationRecord
has_many :lists, dependent: :nullify
end
class List < ApplicationRecord
has_and_belongs_to_many :users
belongs_to :workspace, optional: true
scope :by_workspace, ->(workspace) { where(workspace: workspace) }
scope :by_user, ->(user) { joins(:users).where(users: { id: user }) }
end
What I need is a by_workspace_or_user scope, which returns any list which belongs to a given workspace or to a given user. I've tried combining these using or, but no luck.
You can use plain SQL where to achieve this:
List
.left_outer_joins(:users)
.where("users.id = ? OR lists.workspace_id = ?", user.id, workspace.id)

Using similar scopes in ActiveRecord

I have a problem with using similar scopes in ActiveRecord. I have 3 models:
Person:
class Person < ActiveRecord::Base
has_many :survey_packs
def self.by_first_name(text)
where(first_name: text)
end
end
SurveyPack:
class SurveyPack < ActiveRecord::Base
has_many :survey_instances, inverse_of: :survey_pack
belongs_to :person
end
and SurveyInstance:
class SurveyInstance < ActiveRecord::Base
belongs_to :survey_pack, inverse_of: :survey_instances
belongs_to :student, class_name: 'Person', foreign_key: :person_id
scope :teacher_full_name, ->(text) { SurveyInstance.joins(:person).merge(Person.by_first_name(text)) }
scope :student_full_name, ->(text) { SurveyInstance.joins(:student).merge(Person.by_first_name(text)) }
end
The problem is that I cant join this scopes together:
scope :teacher_full_name, ->(text) { SurveyInstance.joins(:person).merge(Person.by_first_name(text)) }
scope :student_full_name, ->(text) { SurveyInstance.joins(:student).merge(Person.by_first_name(text)) }
For example I have a survey_instance that student name is: 'Student' and Teacher name is: 'Teacher':
SurveyInstance.teacher_full_name('Teacher') #this will work
SurveyInstance.student_full_name('Student') #this will also work
SurveyInstance.teacher_full_name('Teacher').student_full_name('Student') #this will not work...
I have no idea how to solve this problem...
Not sure whether this works or not, but the logic is similar to this:
class SurveyInstance < ActiveRecord::Base
belongs_to :survey_pack, inverse_of: :survey_instances
belongs_to :student, class_name: 'Person', foreign_key: :person_id
scope :full_name, ->(type, text) { SurveyInstance.joins(type).merge(Person.by_first_name(text)) }
end
Extract the association out so that the symbol is passed through the scope call.
To call the scope:
SurveyInstance.full_name(:person, 'Teacher')
SurveyInstance.full_name(:student, 'Student')

has_many :condition multiple models how to use join here?

I have the following model structure:
Model Visitor
class Visitor < ActiveRecord::Base
has_many: triggers
end
Model Trigger
class Trigger < ActiveRecord::Base
belongs_to :visitor, :inverse_of => :triggers
belongs_to :event, :inverse_of => :triggers
end
Model Event
class Event < ActiveRecord::Base
has_many: triggers
end
I am trying to setup a custom association in Visitor model like so:
has_many: triggers_that_pass_some_condition ,:class_name => "Trigger",
:conditions => ["triggers.some_column >= events.some_column"]
The problem is that it doesn't work .. I am guessing I have to do some kind of join to compare columns of two separate models (that are associated with each other)
I have tried
triggers.some_column >= triggers.event.some_column
That does not work either. Anyone has any suggestions? thanks!
Try the following code..
class Trigger < ActiveRecord::Base
belongs_to :event
belongs_to :visitor
end
# Visitors.rb
has_many :triggers_with_condition, -> { includes(:event).where(some_trigger_column >= event.some_event_column)}, class_name: "Trigger"
Make sure you first add the correct association between Visitor and Trigger in your model setup. From there, you can add a custom association as follows:
class Visitor < ActiveRecord::Base
has_many :approved_triggers, -> { includes(:events).where("events.something = ?", true).references(:events) }, class_name: 'Trigger', inverse_of: :visitor
end
class Trigger < ActiveRecord::Base
belongs_to :visitor, inverse_of :triggers
end
Right now your Trigger class holds no association to a Visitor.
Thanks to the clue from Darpa, I eventually settled on this:
has_many :custom_trigger, {:class_name => "Trigger", :include => :event,
:conditions => ["triggers.some_column >= events.another_column"]}

scope with condition on different model

i have following code:
class Item < ActiveRecord::Base
belongs_to :user
has_many :transactions
#scope :active, lambda??
end
class Transaction < ActiveRecord::Base
belongs_to :user
belongs_to :item
scope :active, where("status = 0")
end
class User < ActiveRecord::Base
has_many :items
has_many :transactions
end
i wish to build a scope in model Item to retrieve only records with active transactions, for example:
User.find(1).items.active
I found the answer. it shoud be this way:
scope :active, joins(:transactions)
Transaction.active
The answer was here: http://asciicasts.com/episodes/215-advanced-queries-in-rails-3

counter_cache has_many_through sql optimisation, reduce number of sql queries

How I can optimise my SQL queries, to ignore situations like this:
Meeting.find(5).users.size => SELECT COUNT(*) FROM ... WHERE ...
User.find(123).meetings.size => SELECT COUNT(*) FROm ... WHERE ...
I have no idea how to use counter_cache here.
Here is my model relation:
class Meeting < ActiveRecord::Base
has_many :meeting_users
has_many :users, :through => meeting_users
end
class User < ActiveRecord::Base
has_many :meeting_users
has_many :meetings, :through => meeting_users
end
class Meeting_user < ActiveRecord::Base
belongs_to :meeting
belongs_to :user
end
What are the most optimal solutions ?
And how implement counter_cache here ?
Starting from Rails3.0.5 and in newer versions, you are now able to set counter cache to the "linker" model, in your case it will be:
class MeetingUser < ActiveRecord::Base
belongs_to :meeting, :counter_cache => :users_count
belongs_to :user, :counter_cache => :meetings_count
end
It's important to explicitly specify count column names, otherwise the columns used will default to meeting_users_count.
As far as I know you can't use counter_cache with through associations, that's why you should manually increment it.
For example (untested):
class MeetingUser < ActiveRecord::Base
...
after_create { |record|
Meeting.increment_counter(:users_count, record.meeting.id)
}
after_destroy { |record|
Meeting.decrement_counter(:users_count, record.meeting.id)
}
end