Improving query performance of with rails and mongo - ruby-on-rails-3

Assuming that I have two models:
class Radar
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
key :name, String, :required => true
key :slug, String, :required => true
many :stores, index: true
def self.retrieve_radars_and_stores
radars = Radar.all
radars.map { |r|
{
name: r.name,
ip: r.ip,
stores: r.serializable_stores
}
}
end
def serializable_stores
stores.map { |store|
{
name: store.try(:name),
location: store.try(:location)
}
}
end
end
class Store
include MongoMapper::Document
key :name, String, :required => true
key :location, String, :required => true
ensure_index :name
ensure_index :location
end
So, I have a controller method that calls Radar.retrieve_radars_and_stores, getting the result and returning as json.
The code works perfectly, however, having more than 20.000 records, it spends about 1 minute to process the method. Commenting out the stores: r.serializable_stores line, the method spends only few seconds.
My question is, how can I improve this query, reducing the time elapsed?
Thanks!

Related

Rails won't create instance variable

Ok so I am building an eBay clone of sorts and I am trying to implement Braintree as the payments processor and I am having serious issues with my orders controller. All of my logic for creating customers in braintree etc. works. The issue I am having is that I can't get the product id to save when creating my orders.
I have nested the routes for my orders resource below my products resource like so:
resources :products do
resources :orders
end
Due to the routing I have set up, the parameters that are passed are:
{"_method"=>"get", "authenticity_token"=>"", "controller"=>"orders", "action"=>"new", **"product_id"=>"4"}**
My product ID gets passed into the new action. My logic is that I can store it in an instance variable and then save it to my order. My controller is as follows:
def new
if current_user.is_customer?
#client_token = Braintree::ClientToken.generate(:customer_id => current_user.id)
else
#client_token = Braintree::ClientToken.generate()
end
console
end
def create
amount = 50 # only set to a whole decimal number with no '.'
amount_str = "#{amount}.00"
#product=Product.find_by(params[:product_id])
nonce = params[:payment_method_nonce]
render action: :new and return unless nonce
#order = Order.new
#order.amount = amount
#order.user_id = current_user.id
#order.product_id= #product.id
if current_user.is_customer?
result = Braintree::Transaction.sale(
:amount => amount_str,
:payment_method_nonce => nonce,
:order_id => #order.id,
:customer_id => current_user.id,
:options => {
:submit_for_settlement => false,
:store_in_vault_on_success => true,
},
)
if result.success?
#order.save!
redirect_to support_product_path(#product), notice: "Success. Orange you glad I've used Braintree to process your payment?!"
else
render 'new'
flash[:alert] = "Something wasn't quite right. #{result.transaction.processor_response_text}"
end
else
result = Braintree::Transaction.sale(
:amount => amount_str,
:payment_method_nonce => nonce,
:order_id => #order.id,
:customer => {
:id => current_user.id,
:first_name => current_user.first_name,
:last_name => current_user.last_name
},
:options => {
:submit_for_settlement => false,
:store_in_vault_on_success => true,
}
)
if result.success?
#order.save!
redirect_to product_path(#product), notice: "Success. Orange you glad I've used Braintree to process your payment?!"
else
render 'new'
flash[:alert] = "Something wasn't quite right. #{result.transaction.processor_response_text}"
end
end
end
I have an association for orders and products and I have done a references migration like so:
class AddProductsUsersToOrders < ActiveRecord::Migration
def change
add_reference :orders, :product, index: true
add_foreign_key :orders, :products
add_reference :orders, :user, index: true
add_foreign_key :orders, :users
end
end
Order Model:
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
So the logic works and gets created however it saves the product_id as the first product in my database and redirects to it regardless of which product I try to order. I have spent hours on this any help is appreciated.

validates :something, :confirmation => true & attr_accessor confusion

am struggling with Ruby validates :confirmation => true in my Rails app. Consider the following code:
# == Schema Information
#
# Table name: things
#
# id :integer not null, primary key
# pin :integer(8)
# created_at :datetime
# updated_at :datetime
#
class Things < ActiveRecord::Base
#attr_accessor :pin
attr_accessible :pin, :pin_confirmation
validates :pin,
:confirmation => true,
:length => { :within => 7..15 },
:numericality => { :only_integer => true }
end
As the code is above, my validation works fine from the Rails console:
1.9.3-p0 :002 > l2 = Thing.create! :pin => 1234567, :pin_confirmation => 1111111
ActiveRecord::RecordInvalid: Validation failed: Pin doesn't match confirmation
....
1.9.3-p0 :003 > l2 = Thing.create! :pin => 1234567, :pin_confirmation => 1234567
=> #<Thing id: 2, pin: 1234567, created_at: "2012-01-30 22:03:29", updated_at: "2012-01-30 22:03:29">
but testing both through rspec and manually from rails server causes the validation to fail, saying they don't match when they darn well do. If I uncomment the attr_accessor for :pin, the validations will pass but the :pin of course will not be written to the database.
I'm completely sure I'm missing something obvious and vital- just running into a brick wall.
Like Frederick said above, the issue is comparing an instance of String with an instance of Integer.
More than likely, here's what you have in your controller:
Thing.new(params[:thing]) # note all these params come in as a string
What's happening is that since #pin is an integer column, you will get the following behaviour:
my_thing = Thing.new
my_thing.pin = "123456"
my_thing.pin # Will be the integer 123456, the attribute has been auto-cast for you
But since #pin_confirmed is just a regular attribute, not an integer column, here's the weirdness you will see:
my_thing = Thing.new
my_thing.pin_confirmation = "123456"
my_thing.pin_confirmation # Will be the *string* "123456", the attribute has been set as is
So naturally, in that case, no matter what values you have, since they come in through the "params" hash (which is always a set of strings), you will end up assigning string values to both attributes, but they will be cast to different types.
There are several ways to fix this.
One, you could create #pin_confirmation as an integer column in the database.
The other is you could add an attribute setter for #pin_confirmation of the following form:
def pin_confirmation=(val)
#pin_confirmation = val.to_i
end

Why is user.save true but email shows as nil?

I'm using a nested model form for sign-up and am working through the kinks as a beginner. One issue that popped up in particular though that I don't really get is user.email is returning as nil.
Before I started playing around with the nested model form, I could create records in the console wihtout a problem. Now, however I can't create records and some of the latest records created have nil as their email. (I'm not sure if it has anything to do with the nested model at all, but that's my reference point for when it started going haywire.)
If I go into rails console to create a new User/Profile, I follow this process:
user = User.new
user.email = ""
user.password = ""
user.profile = Profile.new
user.profile.first_name = ""
...
user.profile.save
user.save
Everything goes well until user.save, which gives me the NameError: undefined local variable or method 'params' for #<User:>. In rails console it pinpoints to user.rb:25 in create_profile
So here is my User model:
class User < ActiveRecord::Base
attr_accessor :password, :email
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile
validates :email, :uniqueness => true,
:length => { :within => 5..50 },
:format => { :with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i }
validates :password, :confirmation => true,
:length => { :within 4..20 },
:presence => true,
:if => :password_required?
before_save :encrypt_new_password
after_save :create_profile
def self.authenticate(email, password)
user = find_by_email(email)
return user if user && user.authenticated?(password)
end
def authenticated?(password)
self.hashed_password == encrypt(password
end
protected
def encrypt_new_password
return if password.blank?
self.hashed_password = encrypt(password)
end
def password_required?
hashed_password.blank? || password.present?
end
def encrypt(string)
Digest::SHA1.hexdigest(string)
end
end
Can anyone help me figure out what's going on?
UPDATE: I tried changing my regex but I'm still seeing nil for email. Though a prior SO post said not to blindly copy regex without testing, so maybe I just didn't test it correctly. Good news though: I no longer get the error.
attr_accessor simply defines a "property" on the object and has no relation to the attributes of a ActiveRecord model (attributes is a Hash of the fields and values obtained from a table).
ActiveRecord does not save such "properties" as defined by the attr_accessor. (Essentially, attr_accessor defines a attr_reader and attr_writer (i.e. "getter" and "setter") at the same time)

Rails: awesome_nested_set issues

I am using the awesome_nested_set to do a simple drag and drop reordering of news items and the post happens but the position field in my DB is not updated...
Here is my model:
class NewsItem < ActiveRecord::Base
translates :title, :body, :external_url
attr_accessor :locale, :position # to hold temporarily
alias_attribute :content, :body
validates :title, :content, :publish_date, :presence => true
has_friendly_id :title, :use_slug => true
acts_as_indexed :fields => [:title, :body]
acts_as_nested_set
default_scope :order => "publish_date DESC"
# If you're using a named scope that includes a changing variable you need to wrap it in a lambda
# This avoids the query being cached thus becoming unaffected by changes (i.e. Time.now is constant)
scope :not_expired, lambda {
news_items = Arel::Table.new(NewsItem.table_name)
where(news_items[:expiration_date].eq(nil).or(news_items[:expiration_date].gt(Time.now)))
}
scope :published, lambda {
not_expired.where("publish_date < ?", Time.now)
}
scope :latest, lambda { |*l_params|
published.limit( l_params.first || 10)
}
# rejects any page that has not been translated to the current locale.
scope :translated, lambda {
pages = Arel::Table.new(NewsItem.table_name)
translations = Arel::Table.new(NewsItem.translations_table_name)
includes(:translations).where(
translations[:locale].eq(Globalize.locale)).where(pages[:id].eq(translations[:news_item_id]))
}
def not_published? # has the published date not yet arrived?
publish_date > Time.now
end
# for will_paginate
def self.per_page
20
end
end
Anyone know why this wouldn't work?

Rails 3 and has_many :through: automagically set/initialize attributes on join model

I deeply searched the web in order to find a clean and simple way to deal with attributes initialization on the join model of a has_many :through relation, but I did not find a best solution for my need.
In the exaple I provide below, I need to automatically set the attribute role of the Training join model when I create or update a Course object.
This is my model:
QUALIFICATIONS = ["Theoretical Instructor", "Practical Instructor"]
class Course < ActiveRecord::Base
has_many :trainings, dependent: :destroy
has_many :theoretical_instructors, through: :trainings, source: :trainer, conditions: { "trainings.role" => "Theoretical Instructor" }
accepts_nested_attributes_for :theoretical_instructors
has_many :practical_instructors, through: :trainings, source: :trainer, conditions: { "trainings.role" => "Practical Instructor" }
accepts_nested_attributes_for :practical_instructors
end
class Trainer < ActiveRecord::Base
has_many :trainings, dependent: :destroy
has_many :courses, through: :trainings
end
class Training < ActiveRecord::Base
belongs_to :trainer
belongs_to :course
# Join model has the :role attribute, that I wish I could validate this way:
# validates :role, presence: true, inclusion: { in: QUALIFICATIONS }
end
The rationale behind this model is that I want to save Training objects in a single table. I don't want to create the TheoreticalInstructor and the PracticalInstructor join models (potentially exploding the number of tables) to solve this problem.
This view provides the form to submit a new Course:
<%= form_for #course do |course_form| %>
<%- # fields for course attributes, as usual... %>
<%= course_form.label :theoretical_instructor_ids %><br />
<%= course_form.select :theoretical_instructor_ids, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %>
<%= course_form.label :practical_instructor_ids %><br />
<%= course_form.select :practical_instructor_ids, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %>
<%= course_form.submit %>
<% end%>
The question is: what can I do in order to make #course = Course.new(params[:course]) the only line of code in the Course controller needed to save this association on submit of the previous form?
Differently from this question I don't want to create new Trainer objects when I create a new Course: I want to choose them from those already present in the DB (through a multiselect input field).
What I need is that something like #course.theoretical_instructor_ids = [1, 2] creates two Training objects with the role attribute set to Theoretical Instructor
I'm thinking on an after_initialize callback on Training that set role basing on the relation name (:theoretical_instructors and :practical_instructors), but I really don't know how to do it. Any advice? Am I missing some point?
Thank you guys!
EDIT 1 from oli-g
This question deals with a similar problem: the difference is that I don't want to build Trainer objects when I create a new Course, but I simply want to associate existing Trainer objects to a new Course.
EDIT 2 from oli-g
Basing on this (a 5 years old post) and this blog posts, I've changed the Course model in this way:
class Course < ActiveRecord::Base
has_many :trainings, dependent: :destroy
has_many :theoretical_instructors, through: :trainings, source: :trainer, conditions: ["trainings.role = ?", "Theoretical Instructor"] do
def <<(theoretical_instructor)
Training.send(:with_scope, create: { role: "Theoretical Instructor" }) { self.concat theoretical_instructor }
end
end
accepts_nested_attributes_for :theoretical_instructors
has_many :practical_instructors, through: :trainings, source: :trainer, conditions: ["trainings.role = ?", "Practical Instructor"] do
def <<(practical_instructor)
Training.send(:with_scope, create: { role: "Practical Instructor" }) { self.concat practical_instructor }
end
end
accepts_nested_attributes_for :practical_instructors
end
This code enables me to do a thing like this
:001 > c = Course.first
=> #<Course id: 1>
:002 > t1 = Trainer.first
=> #<Trainer id: 1, name: "Tom">
:003 > c.theoretical_instructors << t1
=> #<Trainer id: 1, name: "Tom">
:004 > Training.all
=> [#<Training id: 1, role: "Theoretical Instructor", trainer_id: 1, course_id: 1>]
This is an acceptable workaround, even if in my controller I still can't do just #course = Course.new(params[:course]), but I have to create Training objects iterating on params[:course][:theoretical_instructor_ids] and params[:course][:practical_instructor_ids].
But I am curious, so the question remains open: what can I do in order to enable #course = Course.new(params[:course]) to build Training objects along with the Course?
Now... I think I discovered a bug in Rails:
:005 > c.practical_instructors
=> [] # correct
:006 > c.practical_instructor_ids
=> [] # obviously
:007 > c.reload
=> #<Course id: 1>
:008 > c.practical_instructor_ids
=> [1] # WRONG!!!
:009 > c.practical_instructors
=> [] # now it's correct...
:010 > c.practical_instructor_ids
=> [] # WTF!?
I think I will report this at github issues...
EDIT 3 by oli-g
Bug reported at github
Your issue is that you won't be able to add associations until after your record has been created. In this case, the Training associations are stored using the Course record id, and the Course id isn't defined until after the Course is saved for the first time. What you'll want to do is to use the after_create callback to call a function after the record has been created.
Add this to the end of your Course model:
# Use attr accessors to store the initial values so they won't conflict with the *_instructor_ids methods defined above
attr_accessor :create_theoretical_instructors
attr_accessor :create_practical_instructors
# This will call the create_training_records function after the record is created
after_create :create_training_records
private
def create_training_records
create_theoretical_instructors.each do |instructor_id|
self.theoretical_instructors << Instructor.find(instructor_id)
end
create_practical_instructors.each do |instructor_id|
self.practical_instructors << Instructor.find(instructor_id)
end
save!
end
And change the form in your view to use the new attr_accessors:
<%= course_form.label :create_theoretical_instructors %><br />
<%= course_form.select :create_theoretical_instructors, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %>
<%= course_form.label :create_practical_instructors %><br />
<%= course_form.select :create_practical_instructors, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %>
Now when you submit the form, it will write the instructor ids to the new Course instance variables; after the Course has been validated and saved, it will automatically create the new associations.