Rails 3, HABTM how to query with conditions - ruby-on-rails

I have Awards and Categories, joined with Awards_Categories
I have 2 Categories.
I need to find all the Awards that have Category.id = 1, but not Category.id =2. Some categories have one or the other, some have just one. I want a list of the Awards that have category 1 but not category 2.
I have a scope:
scope :in_categories, lambda { |categories|
joins(:categories).
where(:awards_categories => { :category_id => categories } ).
select("DISTINCT awards.*")
}
And this works with a query like:
#awardsall = Award.in_categories([1 && 2]).order("name ASC")
I have tried
#awards_store = Award.in_categories([1]).order("name ASC")
With:
<% #awards_store.each do |store| %>
<li><%= link_to store.name, award_path(store), :title => store.info %> |
<% store.categories.each do |cat| %>
<%= cat.id%>
<% end %>
</li>
<% end %>
EDIT---
I know the block is not what I need. it is just my attempt at finding a way to make it work.
And while this lists all the awards, and all the award categories its still grabbing awards that have category.id = 2 because some awards have both
any ideas?

Sorry, didn't test it, but the main idea is to count the rows in the connecting table.
scope :in_categories, lambda { |*categories|
joins(:categories).
where(:awards_categories => { :category_id => categories } ).
where("(select count(distinct category_id) from awards_categories where category_id in (?)) = ?", categories, categories.size)
}
and use it this way:
#awardsall = Award.in_categories(1, 2).order("name ASC")
#awards_store = Award.in_categories(1).order("name ASC")
If you have a model for awards_categories, then it will look better:
scope :in_categories, lambda { |*categories|
joins(:categories).
where(:awards_categories => { :category_id => categories } ).
where("#{AwardCategory.where(:category_id => categories).count("distinct category_id").to_sql}=#{categories.size}")
}

Related

Rails fetch products based on minimum and maximum price

I have a rails app in which i have products and their variants.
product.rb
class Product < ActiveRecord::Base
has_many :variants
scope :with_min_range, lambda { |min|
where(" (variant_price) >= ?", "#{min.to_i}")
}
scope :with_max_range, lambda { |max|
where("(variant_price) <= ?", ["#{max.to_i}"])
}
def price_range
return #price_range if #price_range
return #price_range = ['N/A', 'N/A'] if active_variants.empty?
#price_range = active_variants.minmax {|a,b| a.price <=> b.price }.map(&:price)
end
def price_range?
!(price_range.first == price_range.last)
end
end
the way i fetch the price range of the product is
index.html.erb
<% #products.each do |product| %>
<figcaption>
<h4 class="aa-product-title"><%= link_to product.name, product_path(product) %></h4>
<span class="aa-product-price"><%= number_to_currency(product.price_range.first, :locale => :in ) %>
<% if product.price_range? %>
to
<%= number_to_currency(product.price_range.last, :locale => :in) %>
<% end %></span>
</figcaption>
<% end %>
Now you can see in the product.rb i want to fetch the product based on the price so that in the with_min_range the result will be the products whose variants' minimum price range will be greater than the value of min
and in the with_max_range the result will be the products whose variants' maximum price would be less than the max
P.S - I have given variant_price in the where query just to give you idea what I want
Please help me to figure out its solution
Anyways i figured it out myself i needed to make join in the Product model so that Product.joins(:variants) after that in the scope I written
scope :with_min_range, lambda { |min|
where(" variants.price >= ?", "#{min.to_i}")
}
scope :with_max_range, lambda { |max|
where("variants.price <= ?", "#{max.to_i}")
}
it now successfully fetch the record based on minimum and maximum price
If anyone can give a better answer then please !

Show categories/sub-categories in tree hierarchy inside a dropdown

I have a category table with fields id,name and parent_id. The root categories have parent_id 0. Now i want to show the list of categories in a drop down and a structure like this:
root_category
first_sub_category
sub_sub_category
another_sub_sub_category
second_sub_category
another_root_category
first_sub_category
second_sub_category
Here's my Controller:
def new
#category = Category.new
end
And here's the view:
<%= f.label :parent_category %>
<% categories = Category.all.map{|x| [x.name] + [x.id]} %>
<%= f.select(:parent_id, options_for_select(categories), {}, class: 'form-control') %>
Please Help.
Assuming you can get the children of a given category similar to:
has_many :children, :class_name => 'Category', :foreign_key => 'parent_id'
Create a method for categories to get all children and indent each by the level:
def all_children2(level=0)
children_array = []
level +=1
#must use "all" otherwise ActiveRecord returns a relationship, not the array itself
self.children.all.each do |child|
children_array << " " * level + category.name
children_array << child.all_children2(level)
end
#must flatten otherwise we get an array of arrays. Note last action is returned by default
children_array = children_array.flatten
end
Then in your view:
<select>
<option></option>
<% root_categories.each do |category| %>
<option><%=category.name%></option>
<% category.all_children2.each do |child| %>
<option><%=child.html_safe%></option>
<% end %>
<% end %>
</select>
I haven't 100% tested this but the bits I did suggest it should work...
Solved the problem by adding these functions in application_helper.rb
def subcat_prefix(depth)
(" " * 4 * depth).html_safe
end
def category_options_array(current_id = 0,categories=[], parent_id=0, depth=0)
Category.where('parent_id = ? AND id != ?', parent_id, current_id ).order(:id).each do |category|
categories << [subcat_prefix(depth) + category.name, category.id]
category_options_array(current_id,categories, category.id, depth+1)
end
categories
end
and using them in my view like this
<%= f.select(:parent_id, options_for_select(category_options_array), {}, class: 'form-control') %>

How to determine the order from a controller

I have a restaurant with many employees and each employee has many customer ratings.
I want to create a stats page that shows the employees ordered by their monthly ratings average.
In the employee model:
def avg_rating
date = Date.today
ratings_total = self.ratings.sum(:score, :conditions => {:created_at => (date.beginning_of_month..date.end_of_month)}).to_f
ratings_count = self.ratings.count(:conditions => {:created_at => (date.beginning_of_month..date.end_of_month)}).to_f
return (ratings_total/ ratings_count)
end
In the restaurant controller I have:
def restaurant_stats
#restaurant = Restaurant.find(params[:restaurant_id])
#employees = #restaurant.employees.all
end
In the restaurant stats view:
<table>
<% #employees.each do |employee| %>
<tr>
<td><%= employee.name %></td>
<td><%= employee.avg_rating %></td>
</tr>
<% end %>
</table>
I'm not sure how to get the employees in the correct order? I assume I would have to retrieve the values in the correct order in the restaurant_stats action instead of just #restaurant.employees.all but I'm not sure how to because of the functions used in the employees model
You could do, from the controller:
#employees = #restaurant.employees.all.sort_by {|employee| employee.avg_rating}
or more concisely
#employees = #restaurant.employees.all.sort_by(&:avg_rating)
Note that this will load all employees in memory for sorting.
Try in the restaurant controller:
#employees = #restaurant.employees.all.sort {|x,y| y.avg_rating <=> x.avg_rating }
or
#employees = #restaurant.employees.all.sort_by(:avg_rating)
you could create an array and sort it
I think below works but haven't checked it
#employees = #restaurant.employees.collect {|p| [ p.name, p.avg_rating ]}
#employees.sort!{ |a,b| (a[1] <=> b[1]) }

using sum and ifnull conditions inside named scope

I have the following named scope in the 'Book' model
named_scope :batch_book_status_55, lambda { |batch_id| {
:select => "ifnull( sum( if( BK_DB_STATUS_FK = 55, 1, 0 ) ), 0) as ASSIGNED",
:conditions => ["BK_BATCH = ?", batch_id]
}}
<% Book.batch_book_status_55(batch.BATCH_ID).each_with_index do |book, index| %>
<% if book.ASSIGNED.to_i != 0%>
Assigned : <%= book.ASSIGNED %>
<% end %>
<% end %
The code below display the number of books in a particular batch with status = 55 (BK_DB_STATUS_FK = 55).
I am a bit confused with the named scope code. I would appreciate it if someone can please explain the code to me.
You might be better off using a self method here instead of a named scope. Please refer: http://www.railway.at/2010/03/09/named-scopes-are-dead/

this query isn't efficient

i have a single search field that is querying three different columns from two different tables: "companies" and "industries" from a positions table and "schools" from an educations table. it is successfully returning all users that meet ALL fields entered into the search field (using select_tag). this is from my view:
<%= form_tag(search_path, :method => :get, :id => "people_search") do %>
<div class="row-fluid">
<div class="span4">
<table>
<tr>
<td>
<div class="search-table" style="padding-left:55px">
<%= select_tag "all", options_for_select((#visible_companies + #visible_industries + #visible_schools).uniq, params[:all]), { :placeholder => "Search by companies, industries and schools...", :multiple => true, :js => "if (evt.keyCode == 13) {form.submit();}" } %>
</div>
</td>
<td>
<%= submit_tag "Add", id: "send-button", style:"width:175px;" %>
</td>
</tr>
</table>
</div>
<% end %>
</div>
and controller:
#visible_positions = Position.where{ is_visible.eq('true') }
#visible_educations = Education.where{ is_visible.eq('true') }
#visible_companies = #visible_positions.order("LOWER(company)").map(&:company).uniq
#visible_industries = #visible_positions.order("LOWER(industry)").map(&:industry).uniq
#visible_schools = #visible_educations.order("LOWER(school)").map(&:school).uniq
#c = #visible_positions.where{company.in(my{params[:all]})}.map(&:user_id)
#i = #visible_positions.where{industry.in(my{params[:all]})}.map(&:user_id)
#s = #visible_educations.where{school.in(my{params[:all]})}.map(&:user_id)
#C = #visible_positions.where{company.in(my{params[:all]})}.map(&:company)
#I = #visible_positions.where{industry.in(my{params[:all]})}.map(&:industry)
#S = #visible_educations.where{school.in(my{params[:all]})}.map(&:school)
#blacklist = []
#cis = #c + #i + #s
#experiences = ([#C,#I,#S].reject(&:empty?).reduce(:&))
#cis.uniq.each do |user_id|
unless #C.empty?
#C.uniq.each do |company|
unless Position.find_all_by_company(company).map(&:user_id).include?(user_id) || Position.find_all_by_industry(company).map(&:user_id).include?(user_id) || Education.find_all_by_school(company).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
unless #I.empty?
#I.uniq.each do |industry|
unless Position.find_all_by_industry(industry).map(&:user_id).include?(user_id) || Position.find_all_by_company(industry).map(&:user_id).include?(user_id) || Education.find_all_by_school(industry).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
unless #S.empty?
#S.each do |school|
unless Education.find_all_by_school(school).map(&:user_id).include?(user_id) || Position.find_all_by_company(school).map(&:user_id).include?(user_id) || Position.find_all_by_industry(school).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
end
unless #c.empty? && #i.empty? && #s.empty?
#users = User.find(#cis - #blacklist)
end
the search looks like this (notice the single field), with a sample query included (notice the AND filter...i'm the only user in the database that fits all search terms ['dartmouth college' for school, 'world health organization' for company, 'internet' for industry]):
i realize this is not an efficient query and am thinking of ways to speed it up, but could use some ideas at this point.
happy turkey day :)
Based on your description rather then on understanding your code I figured out something like this
User.joins(:positions, :educations).where("lower(positions.company) like lower(?) and lower(positions.industry) like lower(?) and lower(educations.school) like lower(?) and positions.is_visible and educations.is_visible", "%#{company}%", "%#{industry}%", "%#{school}%")
or if there is only one company or industry in column
User.joins(:positions, :educations).where("(lower(positions.company) = lower(?) or lower(positions.industry) = lower(?)) and lower(educations.school) = lower(?) and positions.is_visible and educations.is_visible", company,industry, school)
But to put many industries, companies, schools as params will be more complicated
and create indexes
create index positions_lower_company on positions (lower(company));
create index positions_lower_industry on positions (lower(industry));
create index educations_lower_school on educations (lower(school));
I hope it will help somehow.

Resources