I am building up an array of products in rails. Which is working fine, but my question is...
Is there any way to update an item if it exists in the array already? So as I am looping through products, and the model is "TV-32D300B" I need to check the array to see if it exists, but it may only be a partial number like "TV-32D300" (minus the last letter).
If the is the case I want to be able to update that product with the correct details.
product = {
name: product_name,
url: product_url,
modelnumber: product_modelnumber,
category_id: category.id,
group_id: category.group_id,
image_url: image_url
}
I'm using the include? to add products to the array if a product doesn't already exist, so I am guessing I need to add a like condition to find the number.
unless products.include?(product)
products << product
end
Assuming products is an array of hashes and what you call a model is a product_name held under name key in this hash, the following would do:
existing = products.find { |p| product[:name].include? p[:name] }
if existing
# update existing
else
products << product
end
More info on Enumerable#find.
Related
I'm inserting many records into a rails mongoid ds as such:
products = [{id: "123"},{id: "345"}]
products.each do |product|
product['product_id'] = product.delete 'id'
end
#store = current_user.store
# Clear the existing collection
#store.products.destroy_all
#store.products.collection.insert_many(products)
This works beautifully, however, the records entered into Owner are not associated with the Owner.
Examining a product, I can see that the field owner_id is nil.
I see that https://www.rubydoc.info/gems/mongo/Mongo%2FCollection:insert_many has the options hash. Is there a way to associate the records entered into the Owner when inserting them via the options. Would you do it before somehow? How do I associate each product entered into the Owner with the Owner?
This is a driver-level operation:
#store.products.collection.insert_many(products)
The driver only inserts the data you tell it to insert, i.e. only the keys/values in products. The driver does not have any knowledge of Mongoid associations or any other Mongoid features.
To associate products with their store, set store_id on each product accordingly:
products = [{id: "123", store_id: 1},{id: "345", store_id: 2}]
I got a Product model with has_many Types table and several scopes:
class Product < ActiveRecord::Base
has_many :product_types
has_many :types, through: :product_types
scope :type1, -> { joins(:types).where(types: { name: "Type1" }) }
scope :type2, -> { joins(:types).where(types: { name: "Type2" }) }
end
When I try to use one scope (Product.type1 for example) all goes well, but two scopes at a time (Product.type1.type2) returns an empty query. Yes, one product may have multiple types.
Final goal is to create a filter of products by type with checkboxes form. When I check type1 and type2 I want to see all my products that have Type1 and Type1 at the same time.
UPD 1
So I've tried to do several queries and then & them as #aaron.v suggested. I wanted to do the logic inside of the function so:
def products_by_type_name(types)
all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
...
end
My point was to iterate through each type, collect all products and then & them inside the function.
The problem is when I'm iterating, each loop returns string instead of array of hashes.
Product.joins(:types).where(types: { name: types }).distinct # returns array of hashes, it's okay.
types.each { |type| Product.joins(:types).where(types: { name: type }).distinct } # each loop returns string (Type name) instead of array of hashes.
What am I doing wrong?
SOLUTION 1
Suggested by #aaron.v, explained below
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
SOLUTION 2
Found on reddit.
In this function you are fetching all products with at least one required type and than grouping only ones that have required count of types. Thus, you get only those products that belongs to every type at the same time.
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct.group('products.id').having('count(*) = ?', types.each.count)
end
If you have a database background, this would be pretty obvious as to why you wouldn't be able to find products with multiple types based off how you are writing your scopes.
Database queries that are written from these scopes will multiply the rows to ensure that a product that has many types, will have a distinct row for each type. Your query when you combine both scopes is writing
where `types`.name = "Type1" AND `types`.name = "Type2"
This will never happen since columns aren't added with multiples of the same row on a join.
What you want to do is have a method that you can pass an array of type names to find it
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct
end
This method can accept one name by itself or an array of names for the types you want to find
If you pass an array of type names like ["type1", "type2"], this will result in a query like
where `types`.name in ("type1", "type2")
And then you should see the results you expect
UPDATE
To revise what you need in finding products that have all types given to your method, I decided to do this
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
This method requires an array of type names(even if it is one type, pass an array with one type in it). What it will do is find all products with one specific type name, only select the product id(which will be lighter on your DB) and map it into an array which will get dumped into the product_ids array. after it has looped over each type name, it will find all products that intersect in each of the arrays resulting with products that have all types passed in.
I have a product that has_many Variants. And a variant belongs to a product. I want to display the name of the product(which can be found in Product) and the price and quantity(which can be found in Variants).
Product table:
-id
-title
-description
Variants table:
- id
- is_active(boolean)
- price
- quantity
- product_id
This is how the table looks like. And this is my attempt
def index
#products =Product.all
#display = []
#products.each do |product|
#children = product.variants.where(is_active: true).order(:price).
select(:id,:price,:quantity).first
if #children.present?
#display << {
id: product.id,
title: product.title,
description: product.description,
price: #children.price,
quantity: #children.quantity,
variant_id: #children.id
}
end
end
#display = Kaminari.paginate_array(#display).page(params[:page])
end
I need to optimize this to the maximum. Which brings me to my first question. How can I optimize this better .
And my 2nd question why when I do #products = Product.all.includes(:variants) it will actually increase the loading time instead of lowering it since I do get the variants for every product in that iteration over the whole #products array(so it should count as an N+1, I get products for each product I get variants)?
Is spliting my array of #products in 4 and making 4 threads populate display a good idea?
Your code isn't using the eager loaded data which is why adding includes is slowing things down - it's just extra work.
In general if you call query methods ( where, order, etc) rails can't use the eager loaded data. Instead you can make a new association:
has_many :active_variants, -> { where(is_active: true).order(:price) }, class_name: "Variant"
Then eager load and use this association instead of the variants association.
You should write it as;
def index
#display = Varient.joins(:product).where(is_active: true).order(:price).select('products.id as id, products.title, products.description, price, quantity, variants.id as variant_id')
end
I have a category_aliases table, which has aliase column (array type), and category_id column, which points to categories table.
I have products table, which has category column.
I have categories table.
I want to loop through all products (a lot), which has old categories, and map these to new categories.
So I need to check if any of category_aliases.aliase (aliase is an array of old categories) includes this product's category, and if yes, I want to map it to new category from categories table.
What I tried is:
Product.all.each do |p|
CategoryAlias.all.each do |ca|
if ca.aliase.include? p.category
p.update_column(:category, Category.find(ca.category_id).name)
else
p.update_column(:category, 'undefined')
end
end
end
But I am surely missing something, because even though I know, that there should almost always be a match, it updates p.category to 'undefined' all the time.
You do not need to iterate through all categories ('each') in each product loop, you just need to find a matching category detect.
Furthermore: if you need to iterate over many Products, is might be better to use find_each:
category_aliases = CategoryAlias.pluck(:name, :aliase)
Product.find_each do |product|
category = category_aliases.detect { |ca| ca.aliase.include? product.category }
if category
p.update_column(:category, cateory.name)
else
p.update_column(:category, 'undefined')
end
end
I see number of issues in the code:
You updated category field on each iteration. It leads to update to 'undefined' if CategoryAlias record does not contain category. All other iterations do not make sense then.
You need to break inner loop right after category is updated.
You need to update category to 'undefined' in outer loop, if no alias category found in the whole category_aliases table.
On my site I got entries which have category. Site have only 5 categories, so I have dilemma:
Make relationship between category table and entries (category_id) table
OR
Make method which return category name via IF/CASE statement? Like:
case #entry.category.id
when 1
"Games"
when 2
"Movies"
when 3
"Fun"
[...]
end
(I remind that I must get 10 category name per page)
OR
Use array:
cat[1] = "Games"
cat[2] = "Movies"
cat[3] = "Fun"
[...]
<%= cat[#entry.category.id] %>
I think this relation definitely belongs into the database. (adding a category table)
it is the most sane and most scalable option.
It is also the cleanest, because you break the seperation of data, display and logic (MVC: model, view, controller) when hardcoding the categories in your application.
you can easily select the item AND its category with a single query:
SELECT item.*, category.name
FROM item
LEFT JOIN category ON category.id = item.category_id
WHERE some=condition
there are similar queries for INSERTs and UPDATEs (at least in MySQL), so you never need a second query.
If the only thing you care about category is "name", then you should just store the category_name in the entries table.
OR
Make a constant CATEGORY_NAME and wrapper method to get the name with id in the entries table (without using Category table/model at all). eg.,
class Entry
CATEGORY_NAME = [ "Games", "Movies", "Fun"]
def category_name
CATEGORY_NAME[cat_id] #cat_id being just 0,1,2 .. depends how you want to store
end
...
I am sure there are many ways to achieve this anyway.
Hope it helps.