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
Related
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)
Database tables, first table contain tags (id, name) the second table contain relation between items and tags.
tags
id name
1 TagA
2 TagB
3 TagC
tags_items
item_id tag_id
1 1
1 2
1 3
2 1
2 3
Active reocrds :
class Tag < ActiveRecord::Base
has_many :tags_itemses
validates_presence_of :name
validates_length_of :name, :maximum => 15
end
class TagsItems < ActiveRecord::Base
has_many :tags
end
In my controller i have index method:
def index
items = TagItems.all.includes(:tags)
render json: items,
status: 200
end
How the controller should looks like to get following json ?
[{item_id :1, tags: [{id:1, name: TagA}, {id:2, name: TagB}, {id:3, name: TagC}]},
{item_id :2, tags: [{id:1, name: TagA}, {id:3, name: TagC}]}]
You can customize the JSON output with the include option:
class TagsController
def index
items = TagItems.all.includes(:tags)
render json: items, includes: {
tags: {
only: [:id, :name]
}
}, status: 200
end
end
But this can get very repetitive though and bloats your controllers - active_model_serializers can help here.
However this will still not work since your modeling is way off! Models names should always be in singular! tags_items would be appropriate if it was a has_and_belongs_to_many relationship but that is a very special case since that is a join table without an associated model.
What you want here is to use a has_many :through relationship to setup a many to many between tags and items:
class Item < ActiveRecord::Base
has_many :tag_items # you're not Gollum!
has_many :tags, through: :tag_items
end
class Tag < ActiveRecord::Base
has_many :tag_items
has_many :items, through: :tag_items
end
class TagItem < ActiveRecord::Base
belongs_to :tag
belongs_to :item
end
You also need to correct the name of the table! Create a migration with rails g migration RenameTagsItems and modify the contents:
class RenameTagsItemsMigration < ActiveRecord::Migration
def change
rename_table :tags_items, :tag_items
end
end
Then run the migration (rake db:migrate).
My models
class Collection < ActiveRecord::Base
has_many :outfits
has_many :products, through: :outfits
end
class Outfit < ActiveRecord::Base
belongs_to :product
belongs_to :collection
end
class Product < ActiveRecord::Base
has_many :outfits
has_many :collections, through: :outfits
end
I want to save product in collection model
so one collection can have few product in it
How can i do it? i'm a bit struggle with it
it have tried something like this
p = Product.find_by_code('0339').id
p.collections.create(product_id:p1)
but i guess i'm wrong
When you're chaining through a through collection you don't need to reference the parent's id since that is known.
Instead of:
p = Product.find_by_code('0339').id # NOTE that you have an 'id' not an object here
p.collections.create(product_id:p1) # you can't call an association on the id
Build the association between two existing models (I'm assuming you have other fields in your models; I'm using name as an example).
p = Product.find_by(code: '0339')
c = Collection.find_by(name: 'Spring 2016 Clothing')
o = Outfit.new(name: 'Spring 2016 Outfit', product: p, collection: c)
o.save!
Assuming p and c exist and assuming o passes validation then you now have an assocaition between one product and one collection using a new outfit as the join table.
p.reload
p.collections.count # => 1
c.reload
c.products.count # => 1
I have a three tables:
product (id, name)
image (id, src)
product_images (product_id, image_id, position)
Position field is ordinal number of the product's image.
Product_images is the join table.
Also there are three models in Rails 4:
class Product < ActiveRecord::Base
has_many :product_images, class_name: "ProductImage"
has_many :images, through: :product_images
end
class Image < ActiveRecord::Base
has_many :product_images
has_many :products, through: :product_images, foreign_key: :product_id
accepts_nested_attributes_for :product_images, allow_destroy: true
end
class ProductImage < ActiveRecord::Base
self.table_name = "product_images"
belongs_to :product
belongs_to :image
#has_many :images
#accepts_nested_attributes_for :image, :allow_destroy => true
#attr_accessible :position
end
In controller:
def test
#pr = Product.new name: "Super Sofa"
#imgs = Image.find(19, 20)
#imgs[0].position = 1
#imgs[1].position = 2
#pr.images = #imgs
#pr.save
end
Rails returns this error:
undefined method `position=' for # Image:0x68696e8
How can I set position field to the product_images table through the Image model? Is it possible? Perhaps I have wrong understanding of accepts_nested_attributes_of. May be the pure SQL is the better way in this case?
Thank you.
product_images (product_id, image_id, position)
You have the attribute of position under product images, which belong to the image.
def test
#pr = Product.new name: "Super Sofa"
#imgs = Image.find(19, 20)
#imgs[0].product_image.first.position = 1
#imgs[1].product_image.first.position = 2
#pr.images = #imgs
#pr.save
end
You need to specify which product image that you want to change, because your image has may product images. If you call position , it has to be on product image, unless you do something like this.
class Image < ActiveRecord::Base
def position
self.product_images.first.position
end
end
But that would make no sense because then the position attribute should be under the image table not the product-images table. Have a think.
I have three models:
Department
class Department < ActiveRecord::Base
has_many :patients, :dependent => :destroy
has_many :waitingrooms, :dependent => :destroy
end
Waitingroom with fields patient_id:integer and department_id:integer
class Waitingroom < ActiveRecord::Base
belongs_to :patient
end
Patient with department_id:integer
class Patient < ActiveRecord::Base
belongs_to :department
has_many :waitingrooms
end
I save a waitingroom after a patient was in the waitingroom! So now i tried to retrieve the patients who where in the the waitingroom of the department:
def index
#waited = #current_department.waitingrooms.patients
end
Somehow it didnt worked it returned this error:
undefined method `patients' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Waitingroom:0x374c658>
But this worked: What did i wrong? Thanks!
def index
#waited = #current_department.waitingrooms
end
You can't invoke an association on a collection. You need to invoke it on a specific record. If you want to get all the patients for a set of waiting rooms, you need to do this:
def index
rooms = #current_department.waitingrooms
#waited = rooms.map { |r| r.patients }
end
If you want a flat array, you could (as a naive first pass) use rooms.map { |r| r.patients }.flatten.uniq. A better attempt would just build a list of patient ids and fetch patients once:
#waited = Patient.where(id: rooms.pluck(:patient_id).uniq)