Rails: Scope parent model by attribute of child - sql

I'm having a tough time figuring something out in Rails. It probably has to do with my very limited knowledge of SQL, since I know Rails pretty well. I'm using Rails 5.
I have two models: Applicant and Application.
class Applicant < ApplicationRecord
has_one :application
has_many :skills
accepts_nested_attributes_for :application
accepts_nested_attributes_for :skills,
reject_if: ->(skill) { skill[:name].empty? || skill[:experience].empty? }
validates_with ApplicantValidator
end
class Application < ApplicationRecord
belongs_to :applicant
has_many :notes
VALID_STATUSES = ["in review", "accepted", "declined", "closed"]
validates_length_of :why_interested, minimum: 25
validates :accept_terms, acceptance: true
validates :status, inclusion: { in: VALID_STATUSES }
before_validation :set_status
private
def set_status
self.status ||= "in review"
end
end
I'd like to add a scope, :active, to the Applicant model that returns only applicants who have an application whose status is "in review". However, I can't find a way to access the application within a scope proc.
I've seen other suggestions for cases where there is a has_many relationship with the child, but they didn't work in my case.
I doubt it makes a difference, but I'm using Postgres. The closest I've come to a solution is to add this, but when I run RSpec it says there needs to be a FROM-clause for the applications table. I don't know how to effect that.
scope :active, -> { joins(:application).where('"application"."status" = "in review"') }

scope :in_review_applicants, -> { joins(:application).where('application.status = ?', :in_review) }
I think is something like that..

Related

How to solve N + 1 issue in select box?

In my Rails app I have people which can have many projects and vice versa. The two tables are linked by a join table jobs.
class Project < ActiveRecord::Base
belongs_to :user
has_many :people, :through => :jobs
def self.names_as_options
order(:name).map{ |p| [ p.name, p.id, :'data-people_count' => p.people.count ] }
end
end
In one of my forms I have this select box:
<%= f.select :project_id, Project.names_as_options %>
The problem is that the count on people gives me an N + 1 query for each project.
What is the best way to overcome this?
Thanks for any help.
Try use scope whit lambda, is for that, here is one example how this works:
scope :top, lambda { order('views DESC').limit(20) }
in controller just call
Project.top
this is the best way to filter results in Ruby on Rails.
If you use counts, you might better use also counter caches, they are automatically used when needed. http://guides.rubyonrails.org/association_basics.html
You could add a "counter cache" of the people count for each project. Ordinarily you would add a field to the projects table via a migration
add_column :projects, :people_count, :integer, :default => 0
and then declare to use :counter_cache in the Person model
class Person < ActiveRecord::Base
belongs_to :projects, :counter_cache => true
end
This probably won't do what you want as it stands, as you are going through a Job join. So, the Person#projects declaration is just a convenient finder, and not used in any callback. But, you get the idea.
You could add a column as suggested above, and then make use of some callback methods in the Job class.
class Job
def update_project_counter
project.update_people_counter
end
after_create :update_project_counter
after_destroy :update_project_counter
end
class Project
def update_people_counter
self.update_attribute :people_count, people.count
end
end
Or something similar thats appropriate. You should then only need the one query.
class Project < ActiveRecord::Base
def self.names_as_options
order(:name).map do |p|
[p.name, p.id, :'data-people_count' => p.people_count]
end
end
end
Eager loading will solve this issue, use 'includes' as follows.
Example,
class LineItem < ActiveRecord::Base
belongs_to :order, -> { includes :customer }
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class Customer < ActiveRecord::Base
has_many :orders
end
Ref: http://guides.rubyonrails.org/association_basics.html

Rails Association (belongs_to) dilemma

I have a User model:
class User < ActiveRecord::Base
has_many :cards
end
and a Card model:
class Card< ActiveRecord::Base
belongs_to :user, :foreign_key => "owner_id"
end
the card model also has an attribute called "owner_id", which I'd like to use in way like this:
Card.first.owner which will retrieve the User which owns that card
my problem as that, I know that rails will automagically connect the id's in the association but that doesnt happen.
in the CardController, rails get stuck in the create action on the line
#card=current_user.cards.new(params[:card])
and says unknown attribute: user_id
I've done db:migrate and it still won't work.
must I do as follows for it to work?
#card = Card.new(params[:card])
#card.owner_id=current_user.id
or am I missing something?
First of all, you don't need a owner_id column for this. All you need is
class User
has_many :cards
end
This will give you #user.cards
class Card
belongs_to :owner, :class_name => "User", :foreign_key => "user_id"
end
This will give you #card.owner

How to call a scope across a has_many / through relationship

I have the following (on RoR 3.1 and MySQL 5.1):
class Menu < ActiveRecord::Base
has_many :menu_headers
has_many :menu_items, :through => :menu_headers
belongs_to :location
end
class MenuHeader < ActiveRecord::Base
acts_as_tree :parent_id
has_many :menu_items
belongs_to :menu
end
class MenuItem < ActiveRecord::Base
scope :is_enabled, where(:is_enabled => true)
belongs_to :menu_header
end
I'd like to be able to call the scope across the relationship; something like this:
# call the scope :is_enabled here
Menu.find(12).(where menu_items.is_enabled)
but not sure how to do this.
I'd like the behavior for:
Menu.find(12)
to continue to pull menu_items where is_enabled=false
Any ideas on how to do this?
thx
edit #1
added the act_as_tree and location associations as these also need to be working.
Something like this Scope with join on :has_many :through association might work but seems a little bit ugly
This should do the trick:
Menu.find(12).menu_items.is_enabled
It will return all enabled menuitem associated with the menu with id 12.

How to validate uniqueness of nested models in the scope of their parent model in Rails 3.2?

Here is an example of my problem.
I have a 'Room' model:
class Room < ActiveRecord::Base
has_many :items, :inverse_of => :room
accepts_nested_attributes_for :items
end
And I have an 'Item' model:
class Item < ActiveRecord::Base
belongs_to :room, :inverse_of => :items
validates :some_attr, :uniqueness => { :scope => :room}
end
I want to validate the uniqueness of the :some_attr attribute of all the Items which belongs to a certain room.
When I try to validate the items, I get this error:
TypeError (Cannot visit Room)
I cannot set the scope of the validation to be :room_id since the items are not saved yet so the id is nil. I also want to prevent using custom validators in the 'Room' model.
Is there any clean way to do it in Rails? I also wonder if I set the :inverse_of option correctly...
I don't see anything wrong with how you're using inverse_of.
As for the problem, in a similar situation I ended up forcing a uniqueness constraint in a migration, like so
add_index :items, [ :room_id, :some_attr ], :unique => true
This is in addition to the AR-level validation
validates_uniqueness_of :some_attr, :scope => :room_id
(I'm not sure if it's valid to use the association name as a scope, won't the DB adapter raise an exception when trying to refer to the non-existent room column in a query?)

Rails relation overload

Let's say I have the following models:
Class Wishlist
belongs_to :user # User class is irrelevant here
has_many :inclusions
has_many :products, :through => :inclusions
end
Class Product
has_many :inclusions
has_many :wishlists, :through => :inclusions
end
Class Inclusion
belongs_to :product
belongs_to :wishlist
end
inclusions/index.html.erb
<%= render #inclusions %>
inclusions/_inclusion.html.erb
<%= "#{ inclusion.quantity } #{ inclusion.product.name } #{ inclusion.wishlist.user.name }"%>
This example is trivial, but the point is: the amount of database queries is overwhelming. For every instance of _inclusion.html.erb, at least three new queries seem to be created.
Is there a way to prefetch this information beforehand, maybe through a JOIN command?
Maybe this can help:
#inclusions = Inclusions.includes(:product, { :wishlist => :user })
Read about eager loading.
have you thinking about creating sql view, called for example ProductsWishlist and (if the performance will be bad) materializing it?
http://ss64.com/ora/syntax-views.html