I'm having trouble setting up a scope that will check a value on a has_one relationship. I have a Document model. Each Document has_one Document::Response, and Document::Response has a status field (I generated the Document::Response model with rails g model documents/response, which made things a bit more confusing than I'd anticipated. The strong parameters method references it as :document_responses for example. Not sure if it's relevant though. )
scope :rejected, -> { joins(:response).where(status: 'rejected') }
I'll also need to check for something else once I get that working:
scope :rejected, -> { joins(:response).where(status: 'rejected').or(status: 'owed' }
But I can't seem to get the syntax right on this.
PG::UndefinedColumn: ERROR: column documents.status does not exist
LINE 1: ...ts"."id" WHERE "documents"."mortgage_id" = $1 AND "documents...
^
: SELECT COUNT(*) FROM "documents" INNER JOIN "document_responses" ON
"document_responses"."document_id" = "documents"."id" WHERE
"documents"."mortgage_id" = $1 AND "documents"."status" = $2
Routes:
resources :mortgages, shallow: true do
resources :documents do
collection do
post :create_templates
end
end
member do
post :archive
end
end
resources :responses, controller: 'document/responses' do
member do
post :accept
post :reject
end
end
It is required to explicitly tell AR to use status column from responses table instead of documents:
scope :rejected, -> {
joins(:response).where(responses: { status: 'rejected' })
}
scope :processed, -> {
joins(:response).where(responses: { status: ['rejected', 'owed'] })
}
Related
I am trying to unscope multiple model as below
User Model which has acts_as_paranoid
class User
acts_as_paranoid
has_one :category
has_one :brand
has_one :item
INDEXED_FIELDS = {
only: [:name],
include: {
category: { only: [:name] },
item: { only:[:name] },
brand: { only: [:name]},
}
}
def custom_json
Category.unscoped do
Item.unscoped do
Brand.unscoped do
self.as_json(INDEXED_FIELDS)
end
end
end
end
end
User model has following association which also has acts_as_paranoid
Sample Category model, Brand and Item model have same code
class Category
acts_as_paranoid
belongs_to :user
end
Can I do this dynamically with 'N' number of models, like iterating over array as below
def custom_json
[Category, Item, Brand].each do
# do unscoping
end
end
Association looks like
I think the approach you may have is to unscope the class manually, by setting default_scopes to [], and then putting it back.
classes_to_unscope = [Category, Item, Brand]
# remove default_scopes, saving them in previous_scopes
previous_scopes = classes_to_unscope.map do |klazz|
scopes = klazz.default_scopes
klazz.default_scopes = []
scopes
end
self.as_json(INDEXED_FIELDS)
# put default_scopes back
classes_to_unscope.each_with_index do |klazz, i|
klazz.default_scopes = previous_scopes[i]
end
As extra method:
def unscope_all(*models, &block)
# the order does not matter, but preserve it
blocks = [block] + models.reverse.map do |model|
proc do |inner_block|
model.unscoped { inner_block.call }
end
end
blocks.inject do |inner, outer|
proc { outer.call(inner) }
end.call
end
Then you would use it:
unscope_all(Category, Item, Brand) do
# do unscoping
end
unscoped pitfall: when leaving the block you loose the "unscopability", so make sure you don't return a relation (it won't be unscoped). Instead you have to resolve it in the block (e.g. by returning an array where(...).to_a.
In my controller I have the following:
def sort
params[:order].each do |key,value|
Question.find(value[:id]).update_attribute(:order,value[:order])
end
render :nothing => true
end
This works perfectly to update the order column for the 'Question' item.
However i've now moved the order column to a new table 'question_sections' which is associated to Questions.
class Question < ActiveRecord::Base
has_many :sections, :through => :question_sections
belongs_to :section
has_many :question_sections
default_scope { order(order: :asc) }
accepts_nested_attributes_for :section, :reject_if => :all_blank, allow_destroy: true
accepts_nested_attributes_for :question_sections, :reject_if => :all_blank, allow_destroy: true
end
I'm trying to adapt the sort function to update the 'order' column in 'question_sections' but am having trouble with it.
Any help on what the function should look like?
In case you are using nested attributes, you shoud call the includes method, and then iterate over each question_sections:
def sort
params[:order].each do |key,value|
questions = Question.includes(:question_sections).find(value[:id])
questions.question_sections.each { |q| q.update_attribute(:order,value[:order]) }
end
render :nothing => true
end
This breaks the problems into 2 parts, load all the question_sections needed:
1) Load all the question_sections of a question:
questions = Question.includes(:question_sections).find(value[:id])
Question Load
SELECT "questions".* FROM "questions" WHERE "questions"."id" = ? LIMIT 1 [["id", 1]]
QuestionSections Load
SELECT "question_sections".* FROM "question_sections" WHERE "question_sections"."question_id" IN (1)
2) update this question_sections
questions.question_sections.each { |q| q.update_attribute(:order,value[:order]) }
QuestionSections Update
UPDATE "question_sections" SET "order" = ?, "updated_at" = ? WHERE "question_sections"."id" = ? [["order", "different order now"], ["updated_at", "2017-03-09 13:24:42.452593"], ["id", 1]]
I think if you are using nested_attributes for Question model here then Rails should automatically update the nested params for QuestionSection
The controller should look something like this:
def sort
#question = Question.find_by(id: params[:id])
#question.update_attributes(question_params)
end
private
def question_params
params.require(:question).permit!
end
The parameters received to the controller should be like :
params = { question: {
abc: 'abc', question_sections_attributes: [
{ order_id: 1, ... },
{ order_id: 2, ... },
{ order_id: 3, ... }
]
}}
I hope this helps :)
I would like to filter stories on my index based on 2 different conditions where one is for the Current Country and the other one is for All Countries. Is it possible to create a scope where it could fetch stories for both this condition ?
All Countries is boolean field where in my Story table. The logic is if the Story is created for all countries the field, all_countries = 1
Featured Item model, is where the stories could be featured on the index page if the writer would like to do so.
This is how my model looks like for now with the scopes
class Country < ActiveRecord::Base
has_many :stories
end
class Story < ActiveRecord::Base
belongs_to :countries
has_many :featured_items, dependent: :destroy
scope :by_country, lambda { |id| where(:country_id => id)}
scope :for_all_countries, where(:all_countries => true)
end
class FeaturedItem < ActiveRecord::Base
belongs_to :story
scope :by_country, -> (country) {joins(:story).where('`stories`.country_id = ?', Country.find(country) )}
scope :for_all_countries, -> { joins(:story).where('`stories`.all_countries = ?',true) }
end
p/s the scope for all countries on the featured Items also returns an error.
You can do this sort of thing:
scope :by_country, -> (country) { country == :all ? where(:all_countries => true) : where(:country_id => country) }
You may need to add a little more logic to handle bad params.
And for the join table, you can join and filter on the stories.
class FeaturedItem < ActiveRecord::Base
scope :by_country, -> (country) { (country == :all ? where( :stories => { :all_countries => true } ) : where( :stories => { :country_id => country } ) ).joins(:story) }
end
Your scope syntax is currently wrong, as is your pluralization of the belongs_to association.
You'll need to use the following (#swards answer is right, this is just an addition):
#app/models/story.rb
class Story < ActiveRecord::Base
belongs_to :country
scope :countries, ->(ids = :all) { ids == :all ? where(all_countries: true) : find(ids) }
end
This will allow you to call Story.countries to return all countries, and Story.countries(1,2,4,5) to return individual ones.
filter stories on my index based on 2 different conditions where one is for the Current Country and the other one is for All Countries.
Have you considered using the following in your Country model:
#stories = #country ? #country.stories : Country.stories
#app/models/country.rb
class Country < ActiveRecord::Base
has_many :stories
scope :stories, -> { joins(:stories).where(story: {all_countries: true}) }
end
I am trying to get an array of records through a join table. I want to find All of one User's Favorites that are Blue, but that are "current". User_Favorites can expire. This is what I'm trying:
User.rb
has_many :user_favorites, dependent: :destroy
has_many :favorites, through: :user_favorites
Favorite.rb
has_many :user_favorites, dependent: :destroy
has_many :users, through: :user_favorites
UserFavorite.rb
belongs_to :user
belongs_to :favorite
scope :current_as_of, -> (date) do
where('start_date <= ?',date).
where('(end_date >= ? or end_date IS NULL)', date)
end
scope :blue, -> { where('self.favorite.color = ?','blue') }
class UsersController < ApplicationController
def profile
#user = User.find(params[:id])
#blue_favorites = #user.user_favorites.current_as_of(Date.today).blue.all
end
This is the error I get:
There is an Error: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "favorite"
LINE 1: ...d_date >= '2015-10-06' or end_date IS NULL)) AND (Favorite.co...
^
: SELECT "user_favorites".* FROM "user_favorites" WHERE "user_favorites"."user_id" = $1 AND (start_date <= '2015-10-06') AND ((end_date >= '2015-10-06' or end_date IS NULL)) AND (Favorite.color = 'blue')
in regards to this:
scope :blue, -> { where('self.favorite.color = ?','blue') }
It looks like you're mixing up database and ruby syntax. The other problem too is that the query, at this point, has no idea what favorite is because it hasn't been joined yet. Try something like this instead:
scope :blue, -> { joins(:favorite).where(favorites: {color: 'blue'}) }
If I understand it correctly your :blue scope should look something like this:
scope :blue, -> { joins(:favorite).where(favorites: { color: 'blue' }) }
In the joins you have to use the association name while in the where clause you have to use the table name.
I have 3 models, Categories, Products and Projects.
The goal is to get a list of all Categories that have products (category.products.count >0) and where the Project that that the Product belongs to is not marked as private.
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :project
belongs_to :user
belongs_to :category
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :products
end
class ApplicationController < ActionController::Base
protected
def set_categories
#categories = Category.joins(:products).where("products.is_product = true or
products.is_loving_this = true")
category = Category.find_by(title: "All")
#categories.unshift(category).uniq!
end
end
views/layouts/_title_bar.html.haml
%nav.navbar-custom1.navbar.navbar-custom1.navbar-default.navbar-static-top{role: "navigation"}
.container
.col-xs-4
%ul.nav.navbar-nav
%ul.dropdown#dropdown_title_bar
%a.dropdown-toggle{"data-toggle" => "dropdown", href: "#", type: "button"}
Products
%b.caret
%ul.dropdown-menu{"aria-labelledby" => "dropdown-menu", role: "menu"}
- #categories.each do |c|
- if c.products.count > 0 || c.title == "All"
%li= link_to c.title, category_path(c)
The code above gives me a dropdown list of all Categories where category.products.count > 0 but I can't figure out how to only get products where the product.project.private == false.
Can anyone guide me on how to add that?
I thought about doing 2 loops, but it seems messy.
- #categories.each do |c|
- if c.products.count > 0 || c.title == "All"
- c.products.each do |product|
- if product.project && product.project.private == false
%li= link_to c.title, category_path(c)
The last catch is that sometimes products don't have a project, which is why I added the first if statement
- if product.project && product.project.private == false
Thanks in advance guys.
I would suggest you come up with some scopes to help out with this query.
app/models/product.rb
# BTW, can you come up with a better name for this? When is a product not a product?
scope :is_product, -> { where(is_product: true )}
scope :is_loving_this, -> { where(is_loving_this: true) }
scope :is_public, -> { include(:project).where(projects: { private: false }) }
Then your query can look something like:
app/models/category.rb
def self.active_categories
category_ids = Products.is_public.is_product.is_loving_this.map(&:category_id)
Category.find(category_ids)
end
Note, this is all untested, so please write some tests to verify... :)
If you want to do it in a scope, you can do it with a subquery (Rails does it in 2 separate queries):
scope :wanted_categories, -> { Category.joins(:products).where("
(products.is_product = true or
products.is_loving_this = true) and products.id in (?)",
Product.joins(:projects).where("projects.private
= false").pluck(:id) }
Rails first runs the subquery and grabs the list of product ids who's project is not private, then uses the list to populate a sql set (note the parenthesis around the question mark) for the main query.
This approach works smoothly when you are looking for values that ARE IN a set, but if instead you need to use it with NOT IN, then there will be a caveat:
If the subquery returns no elements, rails will populate the sql set with NULL, which means nothing will match, products.id in (NULL) and products.id not in (NULL) both return nothing. In that case, you would want to turn the subquery into a scope and make your main scope conditional and make it run only if the subquery returns something. E.g:
class Product
scope :of_public_project, -> { joins(:projects).where("projects.private = false") }
class Category
scope :wanted_categories, -> { joins(:products).where("(products.is_product = true
or products.is_loving_this = true) and products.id not in
(?)", Product.of_public_project.pluck(:id) if Product.of_public_project.any?}
Thanks Jason,
Using your suggestions worked however, loving_this never belongs to a project so I can't chain all the methods together as you suggest.
I have to separate them like so:
def get_categories
category_ids = Product.is_public.product_type.map(&:category_id)
loving_this_ids = Product.loving_this_type.map(&:category_id)
#categories = Category.find(category_ids, loving_this_ids)
end
Do you think this is ok? Or is there a better way?