How to add conditions for json model association - ruby-on-rails

Im trying to include an association but only ones based on its attribute's value
Joo Model:
def as_json(options = {})
super(include: [:foo, (:bar).where('bar.accepted = ?', true)])
end
undefined method `where' for :bar:Symbol
Doing super(include: [:foo, :bar]), I have no control of what I want. How to accomplish what Im trying to do? Only include where bar.accepted == true
I was looking at this to see if it was possible. Im using Rails 5 API.
Edited to show associations:
Joo:
has_many :bars, dependent: :destroy
belongs_to :foo
Foo:
has_many :bars, dependent: :destroy
Bar:
belongs_to :foo
belongs_to :joo

As per the doc I see there isn't way to include associations conditionally. I think you can make a conditional association on model Joo and call it in as_json
Class Joo < ApplicationRecord
belongs_to :foo
has_many :bars, dependent: :destroy
has_many :accepted_bars, -> { where accepted: true }, class_name: "Bar"
end
Then you call it like
#joo = Joo.all.reverse
#joo.as_json(include: [:foo, :accepted_bars])
Note: Not tested!

Related

Avoiding N+1 queries in Ruby on Rails with nested associations and conditions

I'm working on a podcast player and wish to display a list of recently updated feeds, along with details of the play time remaining for the most recently published entry.
So the view looks something like:
#feeds.each do |f|
puts #feed.rss_image.url
puts #feed.most_recent_entry.published_at
if play = #feed.most_recent_entry.most_recent_play_by(#user)
puts play.remaining
end
end
My models are as follows:
class Feed < ApplicationRecord
has_one :rss_image, as: :rss_imageable
has_many :entries, dependent: :destroy
has_one :most_recent_entry, -> { order(published_at: :desc) }, class_name: "Entry"
has_many :plays, dependent: :destroy
end
class Entry < ApplicationRecord
belongs_to :feed, touch: true
has_many :plays, dependent: :destroy
has_one :most_recent_play, -> { order(updated_at: :desc) }, class_name: "Play"
def most_recent_play_by(user)
plays.by(user).order(updated_at: :desc).first
end
end
class Play < ApplicationRecord
belongs_to :entry
belongs_to :feed
belongs_to :user
scope :by, ->(user) { where(user: user) }
def self.most_recent_by(user)
by(user).order(updated_at: :desc).first
end
end
My query is:
#feeds = Feed
.joins(:entries)
.includes(:rss_image, most_recent_entry: :most_recent_play)
.where(most_recent_entry: {plays: {user: #user}})
.group(:id)
.order("max(entries.published_at) DESC")
.limit(10)
But this errors with:
PG::GroupingError: ERROR: column "rss_images.id" must appear in the GROUP BY clause or be used in an aggregate function
Is it possible to achieve this without N+1 queries?
Thanks!
Take a look to Bullet gem, it helps to reduce the number of queries and eliminate n+1. In this case it should suggest you how to modify you query, eg. adding .includes(:entries) ....

Define the return of a getter depending on a condition

I'd like to change what the getter method returns based on a condition in Ruby on Rails 5.
I have:
class Foo < ApplicationRecord
# has an boolean attribute :my_boolean
has_many :bars, through: :FooBar, dependent: :destroy
end
class Bar < ApplicationRecord
has_many :foos, through: :FooBar, dependent: :destroy
scope :my_scope, -> {where(some_attribute: true)}
end
class FooBar < ApplicationRecord
belongs_to :Foo
belongs_to :Bar
end
I want that if Foo has :my_boolean to true, when I call foo.bars it returns his bars within the scope :my_scope, and otherwise it returns all his bars.
I tried to override Bar getter without success like:
class Foo < ApplicationRecord
...
def bars
bars = self.bars
return bars.my_scope if self.my_boolean
bars
end
end
Any idea to make that work please?
You can't name your has_many and your method the same way without having a stack level too deep exception (I suppose you've made a typo in your code, it should be has_many :bars, with a "s").
What you can do though is:
def my_boolean_bars
return bars unless my_boolean
bars.my_scope
end
Or use the same method as you've implemented, it seems ok to me.
EDIT:
If you want to keep the method name, you can do something like this:
class Foo < ApplicationRecord
has_many :bars, through: :FooBar, dependent: :destroy
alias_method :ori_bars, :bars
def bars
return ori_bars unless my_boolean
ori_bars.my_scope
end
end

Rails 4 scope through belongs_to association

I am trying to scope through an array of child records based on a value in one of the parent columns. I am trying to find all the ShoppingCartItems that belong to a Product with a category "Bundle."
I am trying to use acts_as_shopping_cart_gem
My Models.
User.rb
class User < ActiveRecord::Base
has_many :shopping_carts, dependent: :destroy
end
ShoppingCart.rb
class ShoppingCart < ActiveRecord::Base
acts_as_shopping_cart_using :shopping_cart_item
belongs_to :user
has_many :shopping_cart_items, dependent: :destroy
has_many :products, through: :shopping_cart_items
end
Product.rb
class Product < ActiveRecord::Base
has_many :shopping_cart_items, dependent: :destroy
end
ShoppingCartItem.rb
class ShoppingCartItem < ActiveRecord::Base
belongs_to :product, dependent: :destroy
scope :bundles, -> {
joins(:product).where('products.category = ?', 'Bundles') unless category.blank?
}
end
I am getting this error:
> undefined local variable or method `category' for
> #<Class:0x007fc0f40310d0>
Your problem is actually straight forward - there is nowhere you defined the category variable.
This is how I would do that (generalized scope):
scope :by_category, lambda { |category|
joins(:product).where(products: { category: category })
}
Note, there is no unless statement - if the category is not passed to scope, it will raise the ArgumentError.
Then use the scope for any category:
ShoppingCartItem.by_category('Bundles')
To prevent the blank category to be passed into scope, just make sure you pass the right string. You can create a dropdown of categories:
Product.pluck(:category)
or something similar, if it is a part of user interface.
The category field on your scope regards the ShoppingCartItem? If so, try self.category.blank?. If not, just remove the unless statement.
Maybe you need to add Category model and add this relation:
class Product < ActiveRecord::Base
has_many :shopping_cart_items, dependent: :destroy
belongs_to :category
end

Sorting as association before saving using callbacks

I recently migrated from Rails 3 to Rails 4 and in the process I noticed that sorting association does not work in Rails 4. Following are the sample models:
#box.rb
class Box < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items, :allow_destroy => true
before_validate
items.sort! { <some condition> }
end
end
#item.rb
class Item < ActiveRecord::Base
belongs_to :box
end
In Rails 3, sort! method on the association modified the items hash, but in Rails 4 it returns a new sorted instance but does not modify the actual instance. Is there a way to overcome this?
Try this:
#box.rb
class Box < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items, :allow_destroy => true
before_validate
self.items = items.sort { <some condition> }
end
end
#item.rb
class Item < ActiveRecord::Base
belongs_to :box
end
Sorting before storing won't really help. When you pull them from the database they might not be in that order. You wan't to specify the order on the has many (or in the item model).
If you have a more complicated ordering, then please post that logic.
class Box < ActiveRecord::Base
# Can replace position with hash like: { name: :asc }
has_many :item, -> { order(:position) }
accepts_nested_attributes_for :items, :allow_destroy => true
before_validate
items.sort! { <some condition> }
end
end

Rails Join Query with scopes

I'm working on my first rails project, I'm stuck trying to get two of my models to work together. Here are my models:
class Ecn < ActiveRecord::Base
has_many :revisions, :dependent => :destroy
has_many :drawings, through: :revisions
accepts_nested_attributes_for :revisions, :reject_if => lambda { |attrs| attrs.all? { |key, value| value.blank? }}, :allow_destroy => true
belongs_to :user
class Drawing < ActiveRecord::Base
has_many :revisions
class Revision < ActiveRecord::Base
belongs_to :drawing, foreign_key: :drawing_number
belongs_to :ecn
What I am trying to achieve is a search for Ecns that uses the revision foreign_key :drawing_number. When I create an Ecn, I add multiple revisions to the Ecn, which have a field for :drawing_number, which is a property of Drawings. I have an Ecn search form, which has multiple fields to search for, one of which is :drawing_number. I would like the query to find all Revisions that include the given :drawing_number, and find the Ecns that include those revisions.
The scope I have in my Ecn model looks like this:
scope :by_drawing_number, lambda { |drawing_number| Ecn.joins(:drawings).where("drawings.drawing_number LIKE ?", "%#{drawing_number}%") unless drawing_number.nil? }
This scope does not throw any errors, but the search comes up with zero results.
Following should be possible:
scope :by_drawing_number, lambda { |drawing_number| Revision.where("drawing_number LIKE ?", "%#{drawing_number}%").map(&:ecn)
This will return a collection of Ecns.

Resources