Best way to reindex objects in Ruby after jQuery.sortable() - ruby-on-rails

I have objects with a 'position' defined. I 'm moving one object (using jQuery Sortable), then I need to reindex the position of each object in that list (I want to keep them in order 0,1,2...n)
Basically we have a ProductLine model that contains Products (using act_as_list for convinience).
First, swap the position of the previous object and the new object (the one we just dropped here):
def sort
#product = Product.find(params[:id])
if(params.has_key?(:next_id))
#next_product = Product.find(params[:next_id])
end
if(params.has_key?(:prev_id))
#prev_product = Product.find(params[:prev_id])
end
if #prev_product.blank?
#product.move_to_top
elsif #next_product.blank?
#product.move_to_bottom
else
#product.insert_at(#prev_product.position)
end
#product.save
reindex #product.product_line_id
render json: #products
end
This is the reindex function that ensures they are sequential:
def reindex(product_line_id)
#products = Product.where(product_line_id: product_line_id).order(position: :asc)
#products.each_with_index { |p, index|
p.position = index
p.save
}
end
I'm quite new to the Ruby language and I'm sure there are several cleaner, more efficent ways of doing this?

Related

Problem with selecting elements with the same params

What i do wrong? I want to return every products which pass condition:
def show
products = Product.select{|x| x[:category_id] == params[:id]}
render json: products
end
When i write
def show
products = Product.select{|x| x[:category_id] == 1}
render json: products
end
it works why the first example doesn't work?
I am pretty sure that there is mismatch in data type.
1=='1' #will be always false
1==1 #will be true
'1'=='1' #will be true as well
And also check for nil value from params[:id]
Please make sure to change as follows
def show
products = Product.select{|x| x.category_id == params[:id].to_i}
render json: products
end
OR
The best solution as suggested by #Eyeslandic is to use .where as it will not check for mismatch in data type. And also you don't have to take care of nil value from params[:id].
You should really be using a where to stop sql from loading all your products.
#products = Product.where('category_is = ?', params[:id])
The being said, if you are sticking to rails restful conventions, the fact you have a param called :id that is the category_id suggests you are on the category controller. So maybe consider changing your logic to:
#category = Category.includes(:products).find(params[:id])
you can then access products via
#category.products
or if your not interested in the category too much maybe
#products = Category.includes(:products).find(params[:id])&.products

Rails saving arrays to separate rows in the DB

Could someone take a look at my code and let me know if there is a better way to do this, or even correct where I'm going wrong please? I am trying to create a new row for each venue and variant.
Example:
venue_ids => ["1","2"], variant_ids=>["10"]
So, I would want to add in a row which has a venue_id of 1, with variant_id of 10. And a venue_id of 2, with variant_id of 10
I got this working, and it's now passing in my two arrays. I think I am almost there I'm not sure the .each is the right way to do it, but I think that I'm on the right track haha. I have it submitting, however, where would I put my #back_bar.save? because this might cause issues as it won't redirect
Thanks in advance.
def create
#back_bar = BackBar.new
#venues = params[:venue_ids]
#productid = params[:product_id]
#variants = params[:variant_ids]
# For each venue we have in the array, grab the ID.
#venues.each do |v|
#back_bar.venue_id = v
# Then for each variant we associate the variant ID with that venue.
#variants.each do |pv|
#back_bar.product_variant_id = pv
# Add in our product_id
#back_bar.product_id = #productid
# Save the venue and variant to the DB.
if #back_bar.save
flash[:success] = "#{#back_bar.product.name} has been added to #{#back_bar.venue.name}'s back bar."
# Redirect to the back bar page
redirect_to back_bars_path
else
flash[:alert] = "A selected variant for #{#back_bar.product.name} is already in #{#back_bar.venue.name}'s back bar."
# Redirect to the product page
redirect_to discoveries_product_path(#back_bar.product_id)
end
end # Variants end
end # Venues end
end
private
def back_bar_params
params.require(:back_bar).permit(:venue_id,
:product_id,
:product_variant_id)
end
as i said in comments
this is untested code and just showing you how it's possible to do with ease.
class BackBar
def self.add_set(vanue_ids, variant_ids)
values = vanue_ids.map{|ven|
variant_ids.map{|var|
"(#{ven},#{var})"
}
}.flatten.join(",")
ActiveRecord::Base.connection.execute("INSERT INTO back_bars VALUES #{values}")
end
end
def create
# use in controller
BackBar.add_set(params[:venue_ids], params[:variant_ids])
# ...
end

Rails saving array with the duplicated elements

I'm populating an array with multiple elements (some of then are equals, thats what I want). My code is something like this:
def create
#order = Order.create()
#order.table = Table.find_by(:number => params['table'][0])
#products ||= []
#qtd = []
Product.all.each do |product|
params['order'].each_pair do |ordered|
if(product.id.to_s == ordered.first)
for i in 0..ordered.second[0].to_i
#order.products << product
#order.save
end
end
end
end
binding.pry #here the #order.products is the way I want to
if #order.save
flash[:success] = "Pedido criado com sucesso."
redirect_to tables_path
else
flash[:danger] = "Erro ao criar pedido."
render :new
end
end
But When I go to rails console and do Order.last.products he dosen't show me de duplicated elements like I saved on my controller. Whats happening?
Well in your case you should be sending order information from client to server like
"order"=>{"line_items_attributes"=>{"0"=>{"quantity"=>"4", "id"=>"127"}}}
Instead of repeating the product id in the list, your should implement a concept of line items in your system. Line Items are the objects representing items that are added to your shopping cart and instead of repeating the product_id you can use term quantity.
Now you can have separate model called LineItem. Order can have many LineItems. LineItem has many products.
For more info see What is a "order line"?
For current Implementation:
<< method does not allow duplicate entries. It filters out the duplicates. Its basically relates products with order since order can have multiple products via some_table. It cannot relate same product to the order twice.
My suggestion would be, create a string field(column) called products and add the serialized array of product ids.
order.products = [1, 1, 3, 4,4,4].to_s
while accessing you de-serialize.

Rails 3: Search method returns all models instead of specified

What I'm trying to do: I have a model "Recipe" in which I defined a method "search" that takes an array of strings from checkboxes (I call them tags), and a single string. The idea is to search the db for recipes that has anything in it's 'name' or 'instructions' that contains the string, AND also has any of the tags matching it's 'tags' property.
Problem: The search method return all the recipes in my db, and doesn't seem to work at all at finding by the specific parameters.
The action method in the controller:
def index
#recipes = Recipe.search(params[:search], params[:tag])
if !#recipes
#recipes = Recipe.all
end
respond_to do |format|
format.html
format.json { render json: #recipe }
end
end
The search method in my model:
def self.search(search, tags)
conditions = ""
search.present? do
# Condition 1: recipe.name OR instruction same as search?
conditions = "name LIKE ? OR instructions LIKE ?, '%#{search[0].strip}%', '%#{search[0].strip}%'"
# Condition 2: if tags included, any matching?
if !tags.empty?
tags.each do |tag|
conditions += "'AND tags LIKE ?', '%#{tag}%'"
end
end
end
# Hämtar och returnerar alla recipes där codition 1 och/eller 2 stämmer.
Recipe.find(:all, :conditions => [conditions]) unless conditions.length < 1
end
Any ideas why it return all records?
if you are using rails 3, then it is easy to chain find conditions
def self.search(string, tags)
klass = scoped
if string.present?
klass = klass.where('name LIKE ? OR instructions LIKE ?', "%#{string}%", "%#{string}%")
end
if tags.present?
tags.each do |tag|
klass = klass.where('tags LIKE ?', "%#{tag}%")
end
end
klass
end
When you do
search.present? do
...
end
The contents of that block are ignored - it's perfectly legal to pass a block to a function that doesn't expect one, however the block won't get called unless the functions decides to. As a result, none of your condition building code is executed. You probably meant
if search.present?
...
end
As jvnill points out, it is in general much nicer (and safer) to manipulate scopes than to build up SQL fragments by hand

Intercepting creation of new object

I'm adding a categorization functionality to my app and struggling with it. Objects have many categories through categorizations. I'm trying to intercept the creation of a new categorization, check if theres a similar one, if so, increment it's count, if not, create a new object. Here's what I have so far.
validate :check_unique
protected
def check_unique
categorization = Categorization.where(:category_id => self.category_id, :categorizable_id => self.categorizable_id, :categorizable_type => self.categorizable_type)
if categorization.first
categorization.first.increment(:count)
end
end
This kind of logic should not exist in the controller. This is really business domain and should be in the model. Here's how you should go about it:
categorization = Categorization.find_or_create_by_category_id_and_categorizable_id_and_categorizable_type(self.category_id, self.categorizable_id, self.categorizable_type)
categorization.increment!(:count)
find_or_create will try to find the category in the DB, and if it doesn't exist, it'll create it. Now just make sure that count defaults to zero, and this code will do what you want. (when initially created the count would be 1, then later it'll increment)
PS: I'm not sure if find_or_create has changed in rails 3. But this is the main idea
I decided to move it out of the model object and put it into the controller method creating the categorization. It now works (Yay!) and here's the code if anyone is interested.
def add_tag
object = params[:controller].classify.constantize
#item = object.find(params[:id])
#categories = Category.find(params[:category_ids])
#categories.each do |c|
categorization = #item.categorizations.find(:first, :conditions => "category_id = #{c.id}")
if categorization
categorization.increment!(:count)
else
#item.categorizations.create(:category_id => c.id, :user_id => current_user.id)
end
end
if #item.save
current_user.update_attribute(:points, current_user.points + 15) unless #item.categorizations.exists?(:user_id => current_user.id)
flash[:notice] = "Categories added"
redirect_to #item
else
flash[:notice] = "Error"
redirect_to 'categorize'
end
end

Resources