Minimizing number of Queries - ruby-on-rails

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

Related

How to convert mongoid criteria to array?

I have a mongoid criteria categories and I need to convert to an array. I'm using categories.to_a but this dont works and always that the mongoid criteria is iterate by .map it's doing a .find a new query.
How can I fix this?
def self.mapOffers (array, user)
array.map { |u|
{
:id => u.id.to_s,
:name => u.name,
:description => u.description,
:price => u.price,
:url => u.url,
:categories => Category.mapCategories(u.categories.to_a, user),
:picture => u.picture.url,
:accepts_cash => u.accepts_cash_transactions,
:location => {
:longitude => u.longitude,
:latitude => u.latitude,
:street => u.street,
:neighborhood => u.neighborhood,
:number => u.number,
:zip => u.zip,
:city => u.city,
:state => u.state,
:complement => u.complement,
:country => u.country,
},
:fixedMeetingPoint => u.fixedMeetingPoint,
:meetingPoint => {
:street => u.meetingPointStreet,
:neighborhood => u.meetingPointNeighborhood,
:number => u.meetingPointNumber,
:zip => u.meetingPointZip,
:city => u.meetingPointCity,
:state => u.meetingPointState,
:complement => u.meetingPointComplement,
:country => u.meetingPointCountry,
:latitude => u.meetingPointLatitude,
:longitude => u.meetingPointLongitude,
},
:notes => u.notes,
}}
end
def self.mapCategories (array, user)
array.map { |u| {
:id => u.id.to_s,
:name => u.name,
:selected => !user.nil? && u.users.include?(user),
:picture => u.picture.url,
}}
end
Starting from criteria:
scope = Band.where(name: 'foo')
... retrieve the complete result set from the database and store in an array:
bands = scope.to_a
... then iterate the array any number of times:
bands.each { |band| ... }
bands.each { |band| ... }

Shippo API Integration: Status returns as "ERROR" even if "VALID"

I've integrated Shippo with my Ruby on Rails Spree platform. Everything seems to be working great, except that when I go to create a transaction to print shipping labels, I get an error in the response.
Here's my response:
#<Transaction:0x3feee363470c[id=1b419434531e4b43b438c54b93e2a9f5]
{"object_state"=>"VALID", "status"=>"ERROR", "object_created"=>"2017-06-27T23:11:54.567Z", "object_updated"=>"2017-06-27T23:11:55.330Z", "object_id"=>"xxxx", "object_owner"=>"----#gmail.com", "test"=>true, "rate"=>{"object_id"=>"xxxx", "amount"=>"6.52", "currency"=>"USD", "amount_local"=>"6.52", "currency_local"=>"USD", "provider"=>"USPS", "servicelevel_name"=>"Priority Mail", "servicelevel_token"=>"usps_priority", "carrier_account"=>"xxxx"}, "tracking_number"=>"", "tracking_status"=>nil, "tracking_history"=>[], "tracking_url_provider"=>"", "label_url"=>"", "commercial_invoice_url"=>nil, "messages"=>[#<Hashie::Mash code="" source="USPS" text="Request failed. Please try again or contact Shippo support at support#goshippo.com.">], "order"=>nil, "metadata"=>"", "parcel"=>"xxxx"}->#<Shippo::API::ApiObject created=2017-06-27 23:11:54 UTC id="1b419434531e4b43b438c54b93e2a9f5" owner="xxxx#xxxx.com" state=#<Shippo::API::Category::State:0x007fddbca5a2e8 #name=:state, #value=:valid> updated=2017-06-27 23:11:55 UTC>
Here's the code used to create the label:
def self.createLabel(order_info)
shipping_info = order_info.shipping_address
stock_location = order_info.store.stock_location
address_from = {
:name => stock_location.name,
:company => order_info.store.name,
:street1 => stock_location.address1,
:street2 => stock_location.address2,
:city => stock_location.city,
:state => "#{Spree::State.find(stock_location.state_id)}",
:zip => stock_location.zipcode,
:country => "#{Spree::Country.find(stock_location.country_id)}",
:phone => stock_location.phone,
}
address_to = {
:name => "#{shipping_info.firstname} #{shipping_info.lastname}",
:company => shipping_info.company,
:street1 => shipping_info.address1,
:street2 => shipping_info.address2,
:city => shipping_info.city,
:state => "#{Spree::State.find(shipping_info.state_id)}",
:zip => shipping_info.zipcode,
:country => "#{Spree::Country.find(shipping_info.country_id)}",
:phone => shipping_info.phone,
:email => order_info.email
}
parcel = {
:length => getLength(order_info),
:width => getWidth(order_info),
:height => getHeight(order_info),
:distance_unit => :m,
:weight => getWeight(order_info),
:mass_unit => :lb
}
shipment = {
:address_from => address_from,
:address_to => address_to,
:parcels => parcel
}
#Shippo Carrier ids
#ups = Rails.application.secrets.ups_shippo_id
#usps = Rails.application.secrets.usps_shippo_id
transaction = Shippo::Transaction.create(
:shipment => shipment,
:carrier_account => "#{#usps}",
:servicelevel_token => "usps_priority",
:label_file_type => "PDF",
:async => false
)
end
Has anyone run into this issue before? I've looked at their documentation and cannot find any reason for a "status"=>"ERROR" message, when the "object_state"=>"VALID".
I'm happy to post more code if needed. Thanks.
What I found with lots of trial and error, was that the products I was sending to Shippo actually exceeded the weight the shipping carrier would allow in a package. (This was because my database was a bunch of dummy data). Be sure to be setting your measurement units as well, here:
parcel = {
:length => getLength(order_info),
:width => getWidth(order_info),
:height => getHeight(order_info),
:distance_unit => :m,
:weight => getWeight(order_info),
:mass_unit => :lb
}
After changing the data in my database to have reasonable weights on the products, this error went away.

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

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.

NULL in (NULL) does not match properly

Using Rails 3 active relation, I have a scope:
scope :duplicate_contact, lambda {|contact| where(
:person_id => contact.person_id,
:salutation => contact.salutation,
:first_name => contact.first_name,
:last_name => contact.last_name,
:suffix => contact.suffix,
:birthday => contact.birthday,
:address => contact.address,
:city => contact.city,
:state => contact.state,
:zip => contact.zip,
:phone_1 => [contact.phone_1,contact.phone_2,contact.phone_3],
:phone_1_type => [contact.phone_1_type,contact.phone_2_type,contact.phone_3_type],
:phone_2 => [contact.phone_1,contact.phone_2,contact.phone_3],
:phone_2_type => [contact.phone_1_type,contact.phone_2_type,contact.phone_3_type],
:phone_3 => [contact.phone_1,contact.phone_2,contact.phone_3],
:phone_3_type => [contact.phone_1_type,contact.phone_2_type,contact.phone_3_type],
:email => [contact.email,contact.alternate_email],
:alternate_email => [contact.email,contact.alternate_email]
)
}
This has a problem when :email is NULL. It returns back zero rows, when in fact it should return at least 1 row, ie duplicate_contact(contact).size == 0 is true when it should be false.
I think this has to do with this staement from the mysql docs: "In SQL, the NULL value is never true in comparison to any other value, even NULL."
How can I get this to return the correct result?
One possible solution that I found:
scope :duplicate_contact, lambda {|contact|
q = where(
:person_id => contact.person_id,
:salutation => contact.salutation,
:first_name => contact.first_name,
:last_name => contact.last_name,
:suffix => contact.suffix,
:birthday => contact.birthday,
:address => contact.address,
:city => contact.city,
:state => contact.state,
:zip => contact.zip
)
[contact.phone_1,contact.phone_2,contact.phone_3].compact.each{|p| q=q.has_phone(p)}
[contact.phone_1_type,contact.phone_2_type,contact.phone_3_type].compact.each{|p| q=q.has_phone_type(p)}
[contact.email,contact.alternate_email].compact.each{|p| q=q.has_email(p)}
q
}
scope :has_phone, lambda {|phone|
where("'#{phone}' IN (phone_1,phone_2,phone_3)")
}
scope :has_phone_type, lambda {|phone|
where("'#{phone}' IN (phone_1_type,phone_2_type,phone_3_type)")
}
scope :has_email, lambda {|email|
where("'#{email}' IN (email,alternate_email)")
}

Resources