Rails 'includes' and 'where' with belongs_to and has_many association - ruby-on-rails

I have models:
class Order < ApplicationRecord
acts_as_paranoid
has_paper_trail
enum status: %i[created in_process]
has_many :order_containers
has_many :line_items
end
class LineItem < ApplicationRecord
acts_as_paranoid
has_paper_trail
enum status: %i[in_process collected]
belongs_to :order
belongs_to :variant
end
class Variant < ApplicationRecord
acts_as_paranoid
has_paper_trail
has_many :line_items
belongs_to :product
validates :barcode, presence: true
end
class Product < ApplicationRecord
acts_as_paranoid
has_paper_trail
belongs_to :isles, required: false
has_many :variants
validates :name, :volume, :sku, :price, presence: true
end
class Isle < ApplicationRecord
acts_as_paranoid
has_paper_trail
has_many :products
validates :name, presence: true
end
I need to output only those orders in which the products belong to a specific island. For example, if there are no products in the order that belong to the island I need, then this order and its products do not need to be displayed. And if there are products in the order that belong to a specific island for example (isle.id 1), then such an order needs to be withdrawn and those products that belong to this department
I try this:
#products = Order.includes([:line_items, :variants, :products, :isles]).where('products.isle_id = isle.id').references(:orders)
but i got error:
ailure/Error: return { "#{root_name}": [] } if records.blank?
ActiveRecord::StatementInvalid:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "products"
LINE 1: ..."orders" WHERE "orders"."deleted_at" IS NULL AND (products.i...
I'm sorry if I didn't design well, I'm a novice developer, and here's my first assignment)

This will return all products in Order#1 from Isle#1. If order has multiple variants from the same product it will return duplicate products, if this is not what you need add .distinct to these queries.
>> order = Order.first
>> isle = Isle.first
>> Product.joins(variants: { line_items: :order }).where(isle_id: isle, line_items: { order_id: order })
=> [#<Product:0x00007f1551fc4820 id: 1, isle_id: 1>,
#<Product:0x00007f1551fc4258 id: 2, isle_id: 1>]
You can add a few associations to Order to simplify this:
class Order < ApplicationRecord
has_many :line_items
has_many :variants, through: :line_items
has_many :products, through: :variants
end
>> Order.first.products.where(isle_id: Isle.first)
=> [#<Product:0x00007f154babcb30 id: 1, isle_id: 1>,
#<Product:0x00007f154babca18 id: 2, isle_id: 1>]
Update
Make sure you're creating the associations correctly. Use create! and save! methods in the console to raise any validation errors.
# NOTE: create a new order with two products in two different isles
# just add the required attributes from your models.
order = Order.create!(line_items: [
LineItem.new(variant: Variant.new(product: Product.new(isle: (isle = Isle.create!)))),
LineItem.new(variant: Variant.new(product: Product.new(isle: Isle.new)))
])
# NOTE: verify that you have two products
>> order.products
=> [#<Product:0x00007f6f1cb964e0 id: 1, isle_id: 1>,
#<Product:0x00007f6f1cb963f0 id: 2, isle_id: 2>]
# NOTE: filter by isle
>> order.products.where(isle_id: isle)
=> [#<Product:0x00007f6f1ccda630 id: 1, isle_id: 1>]
>> order.products.where(isle_id: 2)
=> [#<Product:0x00007f6f1d140cd8 id: 2, isle_id: 2>]
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

You have quite a few issues with the structure of that. If you truly just want the products for a specific Isle then you should be able to just use:
#products = Product.where(isle_id: my_isle_variable)
You also probably need to update the models so that Product belongs_to :isle (singular not plural)

Related

How do i select an association record that has yet to be saved in Rails?

I have the following models and relationships. I'm building a form and am wanting to initialize terms of the proposal for the form. How can I select a specific ProposalTerm by it's term_type_id to pass on to my fields_for block?
Proposal
class Proposal < ApplicationRecord
after_initialize :add_terms
has_many :terms, class_name: "ProposalTerm", dependent: :destroy
accepts_nested_attributes_for :terms
def add_terms
terms << ProposalTerm.first_or_initialize(type: TermType.signing_bonus)
end
end
ProposalTerm
class ProposalTerm < ApplicationRecord
include DisableInheritance
belongs_to :proposal
belongs_to :type, class_name: "TermType", foreign_key: "term_type_id"
def self.signing_bonus
find_by(type: TermType.signing_bonus)
end
end
My Attempt
>> #proposal.terms
=> #<ActiveRecord::Associations::CollectionProxy [#<ProposalTerm id: nil, season: nil, value: nil, is_guaranteed: false, term_type_id: 2, proposal_id: nil, created_at: nil, updated_at: nil>]>
>> #proposal.terms.where(term_type_id: 2)
=> #<ActiveRecord::AssociationRelation []>
I was able to figure out an answer. I had tried "select" but I was doing it incorrectly.
I had tried the following,
#proposal.terms.select(term_type_id: 2)
but that wasn't returning anything. I then did the following...
#proposal.terms.select { |t| t.term_type_id = 2 }
If you want to return just the first instance use "detect" ...
#proposal.terms.detect { |t| t.term_type_id = 2 } }

Search functionality rails 4

I have a current implementation that allows me to filter my search results by category name
class Category < ActiveRecord::Base
has_many :bike_categories
has_many :bikes, through: :bike_categories
end
class BikeCategory < ActiveRecord::Base
# Holds bike_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
belongs_to :bike
belongs_to :category
end
class Bike < ActiveRecord::Base
has_many :bike_categories, dependent: :destroy
has_many :categories, through: :bike_categories
end
Model
def self.search(params)
includes_categories(params[:name])
end
def self.includes_categories(category_names)
joins(:categories)
.where(categories: { name: category_names })
.group('bikes.id')
.having("count(*) = ?", category_names.size)
end
So if i have the following data
Bike
id: 1 title: 'Bike 1'
id: 2 title: 'Bike 2'
category
id: 1 name: 'Mens'
id: 2 name: 'Womens'
id: 3 name: 'Mountain Bike'
id: 4 name: 'Hybrid'
bike_categories
id: 1 bike_id: 1 :category_id: 2 # Womens, Mountain Bike
id: 2 bike_id: 1 :category_id: 3
id: 3 bike_id: 2 :category_id: 2 # Womens, Hybrid
id: 4 bike_id: 2 :category_id: 4
In my filter if i choose Womens and Mountain Bikes i get all bikes with the categories Womens, Mountain Bikes, so in this example just the one result.
However I then would like to go one step further and select another category, hybrid (so i would select filters Womens, Mountain Bikes, Hybrid) and would like all bikes that have either Womens, Mountain Bikes and Womens, Hybrid, so in this instance I should get the 2 results returned
How could i modify this query to allow for this ?
Thanks
I have used something along the following lines to allow filtering of search results by multiple attributes
# style for Mountian Bike etc.
class Style < ActiveRecord::Base
has_many :bike_styles
has_many :bikes, through: :bike_styles
end
class BikeStyle < ActiveRecord::Base
belongs_to :bike
belongs_to :style
end
# gender for Womens, Mens etc.
# (or maybe there is a better name if you have girls / boys as well)
class Gender < ActiveRecord::Base
has_many :bike_genders
has_many :bikes, through: :bike_genders
end
class BikeGender < ActiveRecord::Base
belongs_to :bike
belongs_to :gender
end
class Bike < ActiveRecord::Base
has_many :bike_styles, dependent: :destroy
has_many :styles, through: :bike_styles
has_many :bike_genders, dependent: :destroy
has_many :genders, through: :bike_genders
# repeat for other searchable attributes ( e.g. frame size )
# has_many :bike_sizes, dependent: :destroy
# has_many :sizes, through: :bike_sizes
# returns filtered results based on the params, call as follows:
# Bikes.search style: "Mountian Bike", gender: "Mens"
# Bikes.search style: "Mountian Bike"
# Bikes.search gender: "Mens"
def self.search(params)
filtered = params[:collection] || self
filtered = style_filter(filtered, params[:style])
filtered = gender_filter(filtered, params[:gender])
filtered
end
# filters by style (if one is provided)
def self.style_filter(filtered, style)
filtered = filtered.joins(:styles).where(styles: {name: style}) unless style.blank?
filtered
end
# filters by gender (if one is provided)
def self.gender_filter(filtered, gender)
filtered = filtered.joins(:genders).where(genders: {name: gender}) unless gender.blank?
filtered
end
end

How to set up this belongs_to association in Rails / ActiveRecord?

I have User and Review models. A review can have an author and a subject, both pointing to a User:
class Review < ApplicationRecord
belongs_to :subject, class_name: 'User', optional: true
belongs_to :author, class_name: 'User', optional: true
end
class CreateReviews < ActiveRecord::Migration[5.0]
def change
create_table :reviews do |t|
t.references :subject
t.references :author
end
end
end
This works fine and now I can assign two separate User objects to the Review object to represent who wrote the review against whom.
The user though, doesn't "know" how many reviews he's associated with either as a subject or the author. I added has_and_belongs_to_many :users on reviews and vice-versa, and though doable, isn't exactly what I want.
How do I set up the associations to be able to do the following:
review.author = some_other_user
review.subject = user2
another_review.author = some_other_user
another_review.subject = user2
user2.a_subject_in.count
#=> 2
user2.a_subject_in
#=> [#<Review>, #<Review>]
some_other_user.an_author_in.count
#=> 2
In other words, how do I see how many times a User has been saved as an author or subject for a model with belongs_to?
IF you want to use has_many association on users side, you need to define two separate has_many relations like
class User < ApplicationRecord
has_many :reviews, foreign_key: :author_id
has_many :subject_reviews, class_name: 'Review', foreign_key: :subject_id
end
Now with this you can simply use
irb(main):033:0> s.reviews
Review Load (0.2ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."author_id" = ? [["author_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Review id: 1, comment: "random", subject_id: 2, author_id: 1, created_at: "2016-07-12 01:16:23", updated_at: "2016-07-12 01:16:23">]>
irb(main):034:0> s.subject_reviews
Review Load (0.2ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."subject_id" = ? [["subject_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
Comment: subject_reviews is not a good name :), change it to your requirements.
I think you're looking for this query:
class User
def referenced_in
# this fetches you all the reviews that a user was referenced
Review.where("reviews.author_id = :user_id OR reviews.subject_id = :user_id", user_id: id).distinct
end
end
User.first.referenced_in #should give you all records a user was referenced

Updating nested_attribute on a model, doesn't update the has_many through relationship

I have the following model relationship:
class Role < ActiveRecord::Base
has_many :permissions_roles, inverse_of: :role, dependent: :destroy
has_many :permissions, through: :permissions_roles
accepts_nested_attributes_for :permissions_roles, allow_destroy: true
end
class PermissionsRole < ActiveRecord::Base
belongs_to :permission
belongs_to :role
end
Permission class has id, name etc.
I am using Rails 4.2.4 and facing an error with update method of the role model. When I update the nested attribute permissions_roles, it doesn't update the has_many :through permissions attribute of the model. This is what I did to verify the error in rails console:
> role = Role.create(name: 'role', permissions_roles_attributes: [{permission_id: 1}])
# Checking permissions for the role
> role.permissions
[#<Permission:0x007ff3c3963160
id: 1,
name: "read">]
# Updating the nested attributes
> role.update(permissions_roles_attributes: [{permission_id: 10}])
# Checking nested attributed - Return as expected
> role.permissions_roles
=> [#<PermissionsRole:0x007ff3bbade740 id: 78, permission_id: 1, role_id: 11>, #<PermissionsRole:0x007ff3bc8fdee8 id: 79, permission_id: 10, role_id: 11>]
# Checking has_many through relationship. Stale :(
> role.permissions
=> [#<Permission:0x007ff3be1d29f0
id: 1,
name: "read">]
I have to manually call reload on the model or role.permissions attribute to make sure role.permissions is updated. Is there any way for permissions attribute to automatically update whenever I update permissions_roles ?
You have forgot to specify the id of the PermissionRole model, which you want to update:
role.update(permissions_roles_attributes: [{ id: 78, permission_id: 10}])

incorrect database records created for rails 3 has_many :through association

I have a has_many :through association. Players have many Teams and Teams have many Players. The join model, Affiliation, belongs to Players and Teams, and also has a year attribute to keep track of a player's team affiliation (or employment) from year to year.
I can't seem to figure out the right way to build an association based on the following rules:
Create a new player.
Associate a team that may be new or existing. So find it or create it, but only create it if the player is saved.
The association may or may not include a year, but the association should only be created if the player and team are saved.
The Player model looks like:
class Player < ActiveRecord::Base
attr_accessible :name
has_many :affiliations, :dependent => :destroy
has_many :teams, :through => :affiliations
end
The Team model looks like:
class Team < ActiveRecord::Base
attr_accessible :city
has_many :affiliations, :dependent => :destroy
has_many :players, :through => :affiliations
end
The Affiliation model looks like:
class Affiliation < ActiveRecord::Base
attr_accessible :player_id, :team_id, :year
belongs_to :player
belongs_to :team
end
I have been successful at creating the association records without the join model attribute using a create action in the PlayersController that looks like:
class PlayersController < ApplicationController
def create
#player = Player.new(params[:player].except(:teams))
unless params[:player][:teams].blank?
params[:player][:teams].each do |team|
team_to_associate = Team.find_or_initialize_by_id(team[:id], team.except(:year)
#player.teams << team_to_associate
end
end
#player.save
respond_with #player
end
end
After creating a new player with two teams using params like:
{"player"=>{"name"=>"George Baker", "teams"=>[{"city"=>"Buffalo"}, {"city"=>"Detroit"}]}}
the database looks like:
players
id: 1, name: George Baker
teams
id: 1, city: Buffalo
id: 2, city: Seattle
affiliations
id: 1, player_id: 1, team_id: 1, year: null
id: 2, player_id: 1, team_id: 2, year: null
When I try to introduce the year, things fall apart. My most recent attempt at the create action in the PlayersController looks like:
class PlayersController < ApplicationController
def create
#player = Player.new(params[:player].except(:teams))
unless params[:player][:teams].blank?
params[:player][:teams].each do |team|
team_to_associate = Team.find_or_initialize_by_id(team[:id], team.except(:year)
// only additional line...
team_to_associate.affiliations.build({:year => team[:year]})
#player.teams << team_to_associate
end
end
#player.save
respond_with #player
end
end
Now, when creating a new player with two teams using params like:
{"player"=>{"name"=>"Bill Johnson", "teams"=>[{"id"=>"1"}, {"city"=>"Detroit", "year"=>"1999"}]}}
the database looks like:
players
id: 1, name: George Baker
id: 2, name: Bill Johnson
teams
id: 1, city: Buffalo
id: 2, city: Seattle
id: 3, city: Detroit
affiliations
id: 1, player_id: 1, team_id: 1, year: null
id: 2, player_id: 1, team_id: 2, year: null
id: 3, player_id: 2, team_id: 1, year: null
id: 4, player_id: null, team_id: 3, year: 1999
id: 5, player_id: 2, team_id: 3, year: null
So three records were created when only two should have been. The affiliation record id: 3 is correct. For id: 4, the player_id is missing. And for id: 5, the year is missing.
Obviously this is incorrect. Where am I going wrong?
Thanks
Edit
Ok, i think i have a better solution. AFAIK, you can't use nested attributes on two levels of depth (though you could test it, maybe it works), but nothing prevents us to simulate this behavior :
class Player < ActiveRecord::Base
has_many :affiliations
has_many :teams, through: :affiliations
accespts_nested_attributes_for :affiliations, allow_destroy: true
end
class Affiliation < ActiveRecord::Base
belongs_to :player
belongs_to :team
validates :player, presence: true
validates :team, presence: true
attr_accessor :team_attributes
before_validation :link_team_for_nested_assignment
def link_team_for_nested_assignment
return true unless team.blank?
self.team = Team.find_or_create_by_id( team_attributes )
end
Now, doing this :
#player = Player.new(
name: 'Bill Johnson',
affiliations_attributes: [
{year: 1999, team_attributes: {id: 1, city: 'Detroit}},
{team_attributes: {city: 'Somewhere else'}}
]
)
#player.save
should create all the required records, and still rollback everything in case of problems (because the save itself is already wrapped in a transaction). As a bonus, all the errors will be associated to #player !
How about this ?
class PlayersController < ApplicationController
def create
ActiveRecord::Base.transaction do
#player = Player.new(params[:player].except(:teams))
raise ActiveRecord::Rollback unless #player.save # first check
unless params[:player][:teams].blank?
#teams = []
params[:player][:teams].each do |team|
team_to_associate = Team.find_or_initialize_by_id(team[:id], team.except(:year))
raise ActiveRecord::Rollback unless team_to_associate.save # second check
if team[:year]
affiliation = team_to_associate.affiliations.build(player: #player, year: team[:year])
raise ActiveRecord::Rollback unless affiliation.save # third check
end
#teams << team_to_associate # keep the object so we have access to errors
end
end
end
flash[:notice] = "ok"
rescue ActiveRecord::Rollback => e
flash[:alert] = "nope"
ensure
respond_with #group
end
end
This solution ended up working for me. If anyone uses this code for their own project, however, please know that I haven't tested any other actions besides create. I'm certain some of this will change once I deal with read, update and delete.
class Player < ActiveRecord::Base
attr_accessible :name
has_many :affiliations, :dependent => :destroy
has_many :teams, :through => :affiliations
accepts_nested_attributes_for :affiliations, :allow_destroy => true
attr_accessible :affiliations_attributes
end
class Team < ActiveRecord::Base
attr_accessible :city
has_many :affiliations, :dependent => :destroy
has_many :players, :through => :affiliations
end
class Affiliation < ActiveRecord::Base
attr_accessible :player_id, :team_id, :team_attributes, :year
belongs_to :player
belongs_to :team
accepts_nested_attributes_for :team
def team_attributes=(team_attributes)
self.team = Team.find_by_id(team_attributes[:id])
self.team = Team.new(team_attributes.except(:id)) if self.team.blank?
end
end
class PlayersController < ApplicationController
def create
player_params = params[:player].except(:teams)
affiliation_params = []
unless params[:player][:teams].blank?
params[:player][:teams].each do |team|
affiliation = {}
affiliation[:year] = team[:year] unless team[:year].blank?
affiliation[:team_attributes] = team.except(:year)
affiliation_params << affiliation
end
end
player_params[:affiliation_attributes] = affiliation_params unless affiliation_params.blank?
#player = Player.new(player_params)
#player.save
respond_with #player
end
end

Resources