Search functionality rails 4 - ruby-on-rails

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

Rails 'includes' and 'where' with belongs_to and has_many association

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)

How to create ActionController to get items with list of tags

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).

Rails create object has_many through

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

How to set a value to the additional column of the join table in the Rails 4?

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.

Association not working

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)

Resources