I've been working on refactoring a piece of my code that allows a user to add items to their cart. Each product can also be added with different attributes. Thus if somebody adds the same product, with matching attributes it shouldn't be added again. It should just increase the quantity by += X
where X is the quantity they've chosen.
Now I feel like it can be refactored even more, but I'm not sure how to deal with that when it comes to for example making it have less queries or be faster. As it's one of the slowest processes on our application.
The add_product gets a parameter containing product_id
, line_item_attributes_attributes
and quantity
. What I do now is check initially if the cart has that product already in the cart. If it does, it checks if the attributes are a complete match if so adds quantity, if not adds it as a new product.
// Cart.rb
def add_product(parameters)
product = Product.find(parameters[:product_id])
quantity = parameters[:quantity].to_i
product_attributes = parameters[:line_item_attributes_attributes].try(:to_unsafe_h) || {}
values = product_attributes.values
collect_ids = values.map { |attribute| attribute[:product_attribute] }
.compact
.flatten
.reject(&:empty?)
attribute_values = collect_ids.map(&:to_i)
attach_store_to_cart(product.store) if line_items.empty?
current_item = line_items.where(product_id: product.id)
if current_item
existing_item = look_for_exact_item_match(current_item, attribute_values)
if existing_item
current_item = line_items.find(existing_item)
current_item.update_attribute(:quantity, current_item.quantity += quantity)
else
current_item = line_items.create(product: product, quantity: quantity)
add_attributes(current_item.id, attribute_values) if attribute_values.present?
end
else
current_item = line_items.create(product: product, quantity: quantity)
add_attributes(current_item.id, attribute_values) if attribute_values.present?
end
current_item
end
def look_for_exact_item_match(similar_items, attribute_values)
line_item_id = nil
similar_items.each do |item|
if item.line_item_attributes.map(&:product_attribute_id).sort == attribute_values.sort
line_item_id = item.id
return line_item_id
end
end
line_item_id
end
def add_attributes(current_item_id, attributes)
current_time = Time.current
columns = 'line_item_id, product_attribute_id, created_at, updated_at'
values = attributes.map { |attribute| "(#{current_item_id},#{attribute}, TIMESTAMP '#{current_time}', TIMESTAMP'#{current_time}')" }.join(',')
ActiveRecord::Base.connection.execute("INSERT INTO line_item_attributes (#{columns}) VALUES #{values}")
end
def attach_store_to_cart(store)
update_attribute(:store, store)
end
I feel like I might be able to improve the checking of attributes, and the collect_ids variable as it's currently doing 3 different things for the array. Which I predict is quite intensive.
I've tried to improve the process already by inserting all the attributes in one query.
I'd be interested to see how this can be improved in terms of efficiency and speed but also in an OOP way. Any feedback?