Rails ActiveRecord conditions - ruby-on-rails

Is there a way to create a condition like this?
#products = Product.find(:all,
:limit => 5,
:conditions => { :products => { :locale => 'en', :id NOT '1' }, :tags => { :name => ['a','b']})
I would like to list all products not including product 1.
Thx.

Rails 3
Use squeel gem.
Product.where(
:products => { :locale => 'en', :id.not_in => '1' },
:tags => { :name => ['a','b']}
).limit(5)
Rails 2
Use AR Extensions for this. It supports the following condition modifiers:
* _lt => less than
* _gt => greater than
* _lte => less than or equal to
* _gte => greater than or equal to
* _ne => not equal to
* _not => not equal to
Now you can rewrite your query as follows:
#products = Product.find(:all,
:limit => 5,
:joins => [:tags],
:conditions => { :locale => 'en', :id_not => '1', :tags => { :name => ['a','b']}
)

It should be something like this. The original query wasn't really clear, adapt it to your needs.
#products = Product.find(:all,
:limit => 5,
:conditions => ["locale = ? AND id <> ? AND tags.name IN (?)", "en", 1, ['a','b'],
:joins => "tags"
)

Another way is to use the merge_conditions which turns the hash conditions into a string. Then you can add on whatever you want or call merge_conditions again with other options.
hash_conditions = {:category => 'computers'}
conditions = Product.merge_conditions(hash_conditions) + ' AND products.id NOT IN(1139) '
products = Product.find(:all, :conditions => conditions)

Rails 3.2.9
Controller
#products = Product.english_but_not(1).with_tags('a','b').limit(5)
Model
class Product < ActiveRecord::Base
attr_accessible :locale
has_many :tags
scope :english, -> { where(:locale => 'en') }
scope :except_id, ->(id) { where(arel_table[:id].not_eq(id)) }
scope :english_but_not, ->(id) { english.except_id(id) }
scope :with_tags, ->(*names) { includes(:tags).where(:tags => {:name => names}) }
end

Related

Hash manipulation in Ruby

Quick ruby question regarding the manipulation of a Hash in Ruby.
I actually have the following hash:
[2] project(#<V1::UsersController>) » error.info
=> {
:id => "914a24888-5e71-4d12-b9b0-10e2d98f516b",
:game => "vampotron",
:data => {
"private" => {
"name" => "Jean",
"logins" => 2300,
"foo" => "bar"
}
},
:revision => 1
}
I want the hash to become:
[2] project(#<V1::UsersController>) » error.info
=> {
:id => "914a24888-5e71-4d12-b9b0-10e2d98f516b",
:game => "vampotron",
:data => {
"name" => "Jean",
"logins" => 2300,
"foo" => "bar"
},
:revision => 1
}
I would like to remove the 'private' key by keeping the existing k,v pairs in my 'data' hash.
Thanks for your help,
M
The easiest way
hash[:data] = hash[:data]['private']

ThinkingSphinx excerpt issue with associations with special chars

I'm having an issue with special characters (apostrophe, namely), only when present is a nested association, however.
I have a 'Vendor' model and an 'Event' model, where a Vendor has_many Events. Here are the index files:
vendor_index:
ThinkingSphinx::Index.define :vendor, :with => :active_record do
indexes :name
indexes city
set_property :min_prefix_len => 2
set_property :enable_star => true
end
event_index:
ThinkingSphinx::Index.define :event, :with => :active_record do
indexes title
indexes subtitle
indexes venue_name
indexes vendor.name, :as => :vendor_name
indexes vendor.city, :as => :vendor_city
indexes genre.name, :as => :genre_name
where "workflow_state = 'published'"
set_property :min_prefix_len => 2
set_property :enable_star => true
end
I'm using an ExcerptorPane, like so, in my search#index action :
class SearchController < ApplicationController
helper_method :format_autocomplete
def index
#events = Event.search params[:search], {:star => true , :per_page => 5, :page => params[:events_page]}
#events.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
#vendors = Vendor.search params[:search], { :star => true , :per_page => 5, :page => params[:vendors_page]}
#vendors.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
#users = User.search params[:search], { :star => true , :per_page => 5, :page => params[:users_page]}
#users.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
end
# methods used for ajax-y pagination
def vendor_results
#vendors = Vendor.search params[:search], { :star => true , :per_page => 5, :page => params[:vendors_page]}
#vendors.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
respond_to do |format|
format.js
end
end
def user_results
#users = User.search params[:search], { :star => true , :per_page => 5, :page => params[:users_page]}
#users.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
respond_to do |format|
format.js
end
end
def event_results
#events = Event.search params[:search], { :star => true , :per_page => 5, :page => params[:events_page]}
#events.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
respond_to do |format|
format.js
end
end
def get_terms
results = ThinkingSphinx.search(params[:search], {:star => true})
results.context[:panes] << ThinkingSphinx::Panes::ExcerptsPane
results_json = format_autocomplete(results)
respond_to do |format|
format.js { render :json => results_json }
end
end
private
def format_autocomplete(r)
bucket = [];
r.each do |result|
puts result.class
if result.class.name == "Event"
title = result.excerpts.title
name = result.excerpts.vendor_name
bucket << {
:label => title,
:value => title,
:category => "Events",
:subtitle => result.excerpts.subtitle,
:url => event_url(result),
:vendor_name => name,
:vendor_city => result.excerpts.vendor_city,
:genre_name => result.excerpts.genre_name,
:venue_name => result.excerpts.venue_name
}
elsif result.class.name == "Vendor"
name = result.excerpts.name
bucket << {
:label => name,
:value => name,
:category => "Vendors",
:subtitle => result.excerpts.city,
:url => vendor_url(result)
}
elsif result.class.name == "User"
name = result.excerpts.name
bucket << {
:label => name,
:value => name,
:category => "Users",
:subtitle => result.excerpts.city,
:url => user_url(result)
}
end
end
bucket
end
end
I have also included a charset_table and ignore_chars in my thinking_sphinx.yml file.
Now, when I search for a Vendor with an apostrophe in their name, everything goes fine if the Vendor has no events. If a Vendor has events though, I get an error trying to render the event's vendor_name: (the full vendor name is "VIFF's Vancity Theatre, and the search query is 'viff')
sphinxql: syntax error, unexpected IDENT, expecting ')' near 's Vancity Theatre', 'event_core', '*viff*', '<span class="match">' AS before_match, '</span>' AS after_match, ' … ' AS chunk_separator)'
raised at this line in my view:
<p><%= link_to ( raw event.excerpts.vendor_name ), vendor_path(event.vendor) %></p>
I've been searching for a while, but can't find anything of help...Any ideas as to what might be causing this?
Thanks!
UPDATE:
It gets weirder ... with vendor name "Viff's Vancity Theatre", (and all events and vendors have city = 'Vancouver' ) if I search "van" or "vanc", everything renders fine, with "Vancity" marked as a match. However if I search "vanco" it breaks again. This happens when I am performing a search on specific models. When I perform a global search however (for autocomplete), I get the opposite behaviour - 'vanco' will work, but anything shorter throws back the same error. I've updated the code above with the full search_controller.rb.
I just received a pull request for Riddle that may contain a fix for this. Try adding the following to your Gemfile:
gem 'riddle', '~> 1.5.6',
:git => 'git://github.com/pat/riddle.git',
:branch => 'master',
:ref => '50d410cda6'

Rails 3.1 deep nesting with RABL

I'm using the RABL gem to render JSON user data for users of comments which are children of annotations which are children of images. I'd like to do something similar to:
object #image
node :annotations do
#image.annotations.map { |a| {
:id => a.id,
:x1 => a.x1,
:x2 => a.x2,
:y1 => a.y1,
:y2 => a.y2,
node :comments do
#image.annotations.comments.map { |c| {
:body => c.body,
:user_id => c.user_id,
:name => User.find(c.user_id).name,
:user_icon => user_icon(User.find(c.user_id), 'square', 30)
}}
end
}}
end
I know this isn't valid in RABL, I also tried using child instead of node, but couldn't access the user data that way. How should I go about doing this and whats the proper syntax to make this happen?
I got this answer from #calvin-l. The trick was to just map the a.comments then grab the data from each comment that way:
node :annotations do
#image.annotations.map { |a| {
:id => a.id,
:x1 => a.x1,
:x2 => a.x2,
:y1 => a.y1,
:y2 => a.y2,
:comments => a.comments.map { |c| {
:body => c.body,
:created_at => c.created_at,
:user => {
:id => c.user.id,
:facebook_id => c.user.facebook_id,
:name => c.user.name,
:username => c.user.username
}
}}
}}
end

Minimizing number of Queries

I currently have this:
data = []
products.each do |product|
categories = product.shop_categories.select("shop_categories.id, shop_categories.name").map do |category|
{
:name => category.name,
:category_id => category.id.to_s
}
end
data << {
:name => product.name,
:product_id => product.productid,
:manufacturer => product.manufacturer,
:detail => product.description,
:categories => categories,
:sales_rank => product.sales_rank,
:sale_price => product.sale_price.to_f,
:price => product.price.to_f,
:images => product.images,
:url => product.url,
:is_rated => current_user.voted_for?(product),
:is_liked => current_user.voted_as_when_voted(product),
:is_in_wishlist => current_user.has_product_in_wishlist?(product)
}
end
This part where products are searched for its shop_categories are taking up a huge amount of time to query since every product (100 per run) when it searches for the products' shop_categories.
Is there a way to minimize the number of queries or at least minimize the CPU being used up by this process?
Use includes to eager-load the association:
data = Product.includes(:shop_categories).collect do |product|
{
:name => product.name,
:product_id => product.productid,
:manufacturer => product.manufacturer,
:detail => product.description,
:categories => product.categories.collect { |c| { :name => c.name, :category_id => c.id.to_s } },
:sales_rank => product.sales_rank,
:sale_price => product.sale_price.to_f,
:price => product.price.to_f,
:images => product.images,
:url => product.url,
:is_rated => current_user.voted_for?(product),
:is_liked => current_user.voted_as_when_voted(product),
:is_in_wishlist => current_user.has_product_in_wishlist?(product)
}
end

AJAX update of accepts_nested_attributes_for partials

My current working environment is Rails 2.3.8 (various reasons why my company hasn't moved to Rails 3).
I'm trying to update elements of a multi-model form via AJAX calls - the idea being to replace certain dropdowns depending on how the user selects or fills in other fields.
I have previously managed to get this working by using non-form based partials - the problem I have now is to reproduce the AJAX updating of the select dropdowns when the partials are based around form_for and fields_for.
Sorry for the following wall of text - i've tried to cut it down as much as possible (the code itself does work on my test site).
How do I generate the form builder elements in the Outbreak controller and then pass this to the category partial to take the place of incident_form?
Any pointers would be great :D
Models
class Outbreak < ActiveRecord::Base
has_many :incidents, :dependent => :destroy
has_many :locations, :through => :incidents
accepts_nested_attributes_for :locations, :allow_destroy => true, :reject_if => :all_blank
accepts_nested_attributes_for :incidents, :allow_destroy => true, :reject_if => :all_blank
end
class Incident < ActiveRecord::Base
belongs_to :outbreak
belongs_to :location
belongs_to :category
belongs_to :subcategory
belongs_to :subtype
end
class Location < ActiveRecord::Base
has_many :incidents, :dependent => :destroy
has_many :outbreaks, :thorugh => incidents
end
Views
_form
<% form_for(#outbreak, :html => {:multipart => true}) do |form| %>
<%= render :partial => 'outbreak_type_select', :locals => {:outbreak_types => #outbreak_types, :f => form } %>
<% form.fields_for :incidents do |incident_form| %>
<%= render :partial => 'category_select', :locals => {:categories => #categories, :incident_form => incident_form} %>
<%= render :partial => 'subcategory_select', :locals => { :subcategories => #subcategories, :incident_form => incident_form } %>
<% end %>
<% end %>
_outbreak_type_select
<% with_str = "'outbreak_type=' + value " %>
<% if #outbreak.id %>
<% with_str << "+ '&id=' + #{outbreak.id}" %>
<% end %>
<%= f.collection_select(:outbreak_type, #outbreak_types, :property_value, :property_value, {}, {:onchange => "#{remote_function(:url => { :action => "update_select_menus"}, :with => with_str)}"} ) %>
_category_select
After calling update_select_menus how to generate the incident_form
<%= incident_form.collection_select( :category_id, #categories, :id, :name, {:prompt => "Select a category"}, {:onchange => "#{remote_function(:url => { :action => "update_subcategory"}, :with => "'category_id='+value")}"}) %>
RJS
begin
page.replace_html 'outbreak_transmission_div', :partial => 'outbreaks/transmission_mode_select', :locals => {:transmission_modes => #transmission_modes }
rescue
page.insert_html :bottom, 'ajax_error', '<p>Error :: transmission modes update select</p>'
page.show 'ajax_error'
end
begin
page.replace_html 'incident_category_select', :partial => 'outbreaks/category_select', :locals => { :categories => #categories }
rescue
page.insert_html :bottom, 'ajax_error', '<p>Error :: incident category update select</p>'
page.show 'ajax_error'
end
Controllers
Outbreak
def new
#outbreak = Outbreak.new
#outbreak.incidents.build
#outbreak.locations.build
#just the contents for the dropdowns
#categories = Category.find(:all, :conditions => {:outbreak_type => "FOODBORNE"}, :order => "outbreak_type ASC")
#subcategories = Subcategory.find(:all, :order => "category_id ASC")
end
def update_select_menus
#outbreak_type = params[:outbreak_type].strip
if params[:id]
#outbreak = Outbreak.find(params[:id])
else
#outbreak = Outbreak.new
#outbreak.incidents.build
#outbreak.locations.build
end
if #outbreak_type == "FOODBORNE"
ob_type_query = "OUTBREAKS:TRANSMISSION_MODE:" << #outbreak_type
#transmission_modes = Property.find(:all, :conditions => {:field => ob_type_query})
ob_type_query = "INVESTIGATIONS:CATEGORY:" << #outbreak_type
#sample_types = Property.find(:all, :conditions => {:field => ob_type_query})
#categories = Category.find(:all, :conditions => { :outbreak_type => "FOODBORNE"})
#subcategories = Subcategory.find(:all, :conditions => { :category_id => #categories.first.id})
#subtypes = Subtype.find(:all, :conditions => { :subcategory_id => #subcategories.first.id})
elsif #outbreak_type == "NON-FOODBORNE"
ob_type_query = "OUTBREAKS:TRANSMISSION_MODE:" << #outbreak_type
#transmission_modes = Property.find(:all, :conditions => {:field => ob_type_query})
ob_type_query = "INVESTIGATIONS:CATEGORY:" << #outbreak_type
#sample_types = Property.find(:all, :conditions => {:field => ob_type_query})
#categories = Category.find(:all, :conditions => { :outbreak_type => "NON-FOODBORNE"})
#subcategories = Subcategory.find(:all, :conditions => { :category_id => #categories.first.id})
#subtypes = Subtype.find(:all, :conditions => { :subcategory_id => #subcategories.first.id})
end
respond_to do |format|
format.html
format.js
end
end
Found a work around based on http://www.treibstofff.de/2009/07/12/ruby-on-rails-23-nested-attributes-with-ajax-support/
This should probably go in Outbreak helper (in Outbreak controller atm)
def update_select_menus
#outbreak_type = params[:outbreak_type].strip
#next_child_index will only be used if
#next_child_index ? params[:next_child_index] : 0
if params[:id]
#outbreak = Outbreak.find(params[:id])
else
#outbreak = Outbreak.new
#outbreak.risks.build
#outbreak.incidents.build
#outbreak.locations.build
end
if #outbreak_type == "FOODBORNE"
ob_type_query = "OUTBREAKS:TRANSMISSION_MODE:" << #outbreak_type
#transmission_modes = Property.find(:all, :conditions => {:field => ob_type_query})
ob_type_query = "INVESTIGATIONS:CATEGORY:" << #outbreak_type
#sample_types = Property.find(:all, :conditions => {:field => ob_type_query})
#categories = Category.find(:all, :conditions => { :outbreak_type => "FOODBORNE"})
#subcategories = Subcategory.find(:all, :conditions => { :category_id => #categories.first.id})
#subtypes = Subtype.find(:all, :conditions => { :subcategory_id => #subcategories.first.id})
elsif #outbreak_type == "NON-FOODBORNE"
ob_type_query = "OUTBREAKS:TRANSMISSION_MODE:" << #outbreak_type
#transmission_modes = Property.find(:all, :conditions => {:field => ob_type_query})
ob_type_query = "INVESTIGATIONS:CATEGORY:" << #outbreak_type
#sample_types = Property.find(:all, :conditions => {:field => ob_type_query})
#categories = Category.find(:all, :conditions => { :outbreak_type => "NON-FOODBORNE"})
#subcategories = Subcategory.find(:all, :conditions => { :category_id => #categories.first.id})
#subtypes = Subtype.find(:all, :conditions => { :subcategory_id => #subcategories.first.id})
end
#pathogen_types = Property.find(:all, :conditions => {:field => "PATHOGENS:CATEGORY"})
#outbreak_types = Property.find(:all, :conditions => {:field => "OUTBREAKS:OUTBREAK_TYPE"} )
render :update do |page|
page.replace 'outbreak_transmission_div', :partial => 'transmission_mode_select_update'
page.replace 'incident_category_select', :partial => 'incident_category_select_update'
page.replace 'incident_subcategory_select', :partial => 'incident_subcategory_select_update'
page.replace 'incident_subtype_select', :partial => 'incident_subtype_select_update'
end
end
In the Outbreak view (although since this partial is related to Incident it should probably go in that view instead)
<% ActionView::Helpers::FormBuilder.new(:outbreak, #outbreak, #template, {}, proc{}).fields_for :incidents,{:child_index => #next_child_index} do |this_form| %>
<div id="incident_category_select">
<%= render :partial => 'category_select', :locals => {:incident_form => this_form } %>
</div>
<% end %>
The ActionView::Helpers::FormBuilder is used to produce the required fields_for form - The website article goes through this in more detail.
The resulting index is set by the #next_child_index variable which can be passed to the controller by the original AJAX call (for example #next_child_index = 1, then the resulting form element name will be outbreak [incidents_attributes] [1] [category_id] ) - I haven't used this in this example because although in future I want the form to support more than one location per Outbreak for this initial run-through it will just accept a single Location - Incident per Outbreak.
_category_select.erb partial (in Outbreak view atm)
<% with_str = "'category_id=' + value " %>
<% if #outbreak.id %>
<% with_str << "+ '&id=' + #{#outbreak.id}" %>
<% end %>
<%= incident_form.collection_select( :category_id, #categories, :id, :name, {:prompt => "Select a category"}, {:onchange => "#{remote_function(:url => { :action => "update_subcategory"}, :with => with_str)}"}) %>
The with_str is just to optionally pass the Outbreak id if it exists to the controller to find the Outbreak record to produce the form and if not to build a new Outbreak and associated nested attributes like Incidents and Locations.
The must be neater ways of doing this - especially the FormHelper and passing the Outbreak id via the optional with string.

Resources