While loop to create multiple records in rails - ruby-on-rails

I'm building an application where users can purchase tracking numbers. I have an Order model and an Order Transaction model. If the Order Transaction returns from the gateway with success, I'm using an after_save callback to trigger a method that creates the tracking numbers and inserts them into the database. Sometimes a user just orders one, but if they order more than one, I can't seem to get rails to create and insert more than one record.
Here's what I'm using -- I've never had to user a loop like this, so I'm not sure what I'm doing wrong.
def create_trackables
if self.success == true
#order = Order.find(order_id)
#start = 0
while #start < #order.total_tokens
#trackable_token = Tracker.create_trackable_token
#start += 1
#trackable ||= Tracker.new(
:user_id => #current_user,
:token => #trackable_token,
:order_id => order_id
)
#trackable.save
end
end
end

dmarkow is right that you should use trackable instead of #trackable but you also should be using = instead of ||=. You also might as well just use create. Here's how I'd write it: def create_trackables
return unless self.success
order = Order.find(order_id) #you shouldn't need this line if it has_one :order
1.upto(order.total_tokens) do
Tracker.create!(
:user_id => #current_user,
:token => Tracker.create_trackable_token,
:order_id => order_id
)
end
end

Change #trackable to trackable to keep it scoped to the loop. Otherwise, the second time the loop runs, #trackable already has a value so the call to Tracker.new doesn't execute, and the #trackable.save line just keeps re-saving the same record. (Edit: Also remove the ||= and just use =).
def create_trackables
if self.success == true
#order = Order.find(order_id)
#start = 0
while #start < #order.total_tokens
#trackable_token = Tracker.create_trackable_token
#start += 1
trackable = Tracker.new(
:user_id => #current_user,
:token => #trackable_token,
:order_id => order_id
)
trackable.save
end
end
end

Related

Rails: why isn't this marked_for_destruction not destroyed?

** update **
it all seems to be related to a custom validator: if I remove it, it works as expected. see code at the end
**
I have a model budget that has many multi_year_impacts
in the console, if I run:
b = Budget.find(4)
b.multi_year_impacts.size #=> 2
b.update_attributes({multi_year_impacts_attributes: {id: 20, _destroy: true} } ) #=> true
b.multi_year_impacts.size #=> 1 (so far so good)
b.reload
b.multi_year_impacts.size #=> 2 What???
and if before b.reload I do b.save (which shouldn't be needed anyway), it's the same.
Any idea why my child record doesn't get destroyed?
Some additional information, just in case:
Rails 3.2.12
in budget.rb
attr_accessible :multi_year_impacts_attributes
has_many :multi_year_impacts, as: :impactable, :dependent => :destroy
accepts_nested_attributes_for :multi_year_impacts, :allow_destroy => true
validates_with MultiYearImpactValidator # problem seems to com from here
in multi_year_impact.rb
belongs_to :impactable, polymorphic: true
in multi_year_impact_validator.rb
class MultiYearImpactValidator < ActiveModel::Validator
def validate(record)
return false unless record.amount_before && record.amount_after && record.savings
lines = record.multi_year_impacts.delete_if{|x| x.marked_for_destruction?}
%w[amount_before amount_after savings].each do |val|
if lines.inject(0){|s,e| s + e.send(val).to_f} != record.send(val)
record.errors.add(val.to_sym, " please check \"Repartition per year\" below: the sum of all lines must be equal of total amounts")
end
end
end
end
it might depend on your rails version, however, comparing your code to the current docs:
Now, when you add the _destroy key to the attributes hash, with a
value that evaluates to true, you will destroy the associated model:
member.avatar_attributes = { :id => '2', :_destroy => '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil
Note that the model will not be destroyed until the parent is saved.
you could try with:
b.multi_year_impacts_attributes = {id: 20, _destroy: true}
b.save
So it looks like the culprit was here
if lines.inject(0){|s,e| s + e.send(val).to_f} != record.send(val)
record.errors.add(val.to_sym, " please check \"Repartition per year\" below: the sum of all lines must be equal of total amounts")
end
changing this to the slightly more complex
total = 0
lines.each do |l|
total += l.send(val).to_f unless l.marked_for_destruction?
end
if total != record.send(val)
record.errors[:amount_before] << " please check \"Repartition per year\" below: the sum of all lines must be equal of total amounts"
end
solved the problem.

ruby/rails how to ignore a comma in when ordering

I have a price field for a product in a catalog. Sometimes the admin user is putting a comma when dealing with thousands (ex: $10,000) and sometimes he is just doing $6000. While I would like to simply tell him to do it one way or the other, I would also like to solve the issue programmatically.
The #show action responsible is here:
def show
#category = Category.find_by_url_name(params[:category_id])
#brand = Brand.find(params[:id])
#search = Product.find(:all, :conditions => ['brand_id = ? and category_id = ?', #brand.id, #category.id],
:order=> params[:order] || 'price DESC')
#products = #search.paginate(:page => params[:page], :per_page => 12 )
#meta_title = "#{#brand.name}"
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #brand }
end
end
I also have a sort_options helper in my application helper that is providing the ordering options to the site user:
def product_sort_options
options_for_select([
['', nil],
['Newest to Oldest', 'descend_by_date'],
['Oldest to Newest', 'ascend_by_date'],
['Price: Highest to Lowest', 'descend_by_price'],
['Price: Lowest to Highest', 'ascend_by_price'],
['Name', 'ascend_by_name']
])
end
any ideas?
To make it a full answer - price should not be a string. The fact that you have 300 products now is not a big deal.
Make a migration:
rails generate migration decimalise
Then edit it (db/migrate/*decimalise.rb), and write something like this:
class Decimalise < ActiveRecord::Migration
def up
connection = ActiveRecord::Base.connection()
# kill the weird chars in the string field
connection.execute("UPDATE products SET price = REPLACE(REPLACE(price, ',', ''), '$', '')")
# convert the string column into a decimal one
change_table :products do |t|
# adjust for your use case - this gives you values up to 9999999.99
# if you need more, increase the 10
t.column :price, :decimal, :precision => 10, :scale => 2
end
end
def down
change_table :products do |t|
t.column :price, :string, :limit => 10
end
end
end
then finally, run
rake db:migrate
(untested, you will probably need to tweak. also, back up your DB before any tinkering - I'll not be responsible for any data loss you suffer)
EDIT One thing I forgot: how to print it out.
<%= number_to_currency #product.price %>
should give you something like $1,999.99 for a price of 1999.99.
You can use String.gsub to search the commas and replace them by nothing.

Trying to master Ruby. How can I optimize this method?

I'm learning new tricks all the time and I'm always on the lookout for better ideas.
I have this rather ugly method. How would you clean it up?
def self.likesit(user_id, params)
game_id = params[:game_id]
videolink_id = params[:videolink_id]
like_type = params[:like_type]
return false if like_type.nil?
if like_type == "videolink"
liked = Like.where(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink").first unless videolink_id.nil?
elsif like_type == "game"
liked = Like.where(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game").first unless game_id.nil?
end
if liked.present?
liked.amount = 1
liked.save
return true
else # not voted on before...create Like record
if like_type == "videolink"
Like.create(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink", :amount => 1)
elsif like_type == "game"
Like.create(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game", :amount => 1)
end
return true
end
return false
end
I would do something like:
class User < ActiveRecord::Base
has_many :likes, :dependent => :destroy
def likes_the(obj)
like = likes.find_or_initialize_by_likeable_type_and_likeable_id(obj.class.name, obj.id)
like.amount += 1
like.save
end
end
User.first.likes_the(VideoLink.first)
First, I think its wrong to deal with the "params" hash on the model level. To me its a red flag when you pass the entire params hash to a model. Thats in the scope of your controllers, your models should have no knowledge of the structure of your params hash, imo.
Second, I think its always cleaner to use objects when possible instead of class methods. What you are doing deals with an object, no reason to perform this on the class level. And finding the objects should be trivial in your controllers. After all this is the purpose of the controllers. To glue everything together.
Finally, eliminate all of the "return false" and "return true" madness. The save method takes care of that. The last "return false" in your method will never be called, because the if else clause above prevents it. In my opinion you should rarely be calling "return" in ruby, since ruby always returns the last evaluated line. In only use return if its at the very top of the method to handle an exception.
Hope this helps.
I'm not sure what the rest of your code looks like but you might consider this as a replacement:
def self.likesit(user_id, params)
return false unless params[:like_type]
query = {:user_id => user_id,
:likeable_id => eval("params[:#{params[:like_type]}_id]"),
:likeable_type => params[:like_type].capitalize}
if (liked = Like.where(query).first).present?
liked.amount = 1
liked.save
else # not voted on before...create Like record
Like.create(query.merge({:amount => 1}))
end
end
I assume liked.save and Like.create return true if they are succesful, otherwise nil is returned. And what about the unless game_id.nil? ? Do you really need that? If it's nil, it's nil and saved as nil. But you might as well check in your data model for nil's. (validations or something)

Globalize2 and migrations

I have used globalize2 to add i18n to an old site. There is already a lot of content in spanish, however it isn't stored in globalize2 tables. Is there a way to convert this content to globalize2 with a migration in rails?
The problem is I can't access the stored content:
>> Panel.first
=> #<Panel id: 1, name: "RT", description: "asd", proje....
>> Panel.first.name
=> nil
>> I18n.locale = nil
=> nil
>> Panel.first.name
=> nil
Any ideas?
I'm sure you solved this one way or another but here goes. You should be able to use the read_attribute method to dig out what you're looking for.
I just used the following to migrate content from the main table into a globalize2 translations table.
Add the appropriate translates line to your model.
Place the following in config/initializers/globalize2_data_migration.rb:
require 'globalize'
module Globalize
module ActiveRecord
module Migration
def move_data_to_translation_table
klass = self.class_name.constantize
return unless klass.count > 0
translated_attribute_columns = klass.first.translated_attributes.keys
klass.all.each do |p|
attribs = {}
translated_attribute_columns.each { |c| attribs[c] = p.read_attribute(c) }
p.update_attributes(attribs)
end
end
def move_data_to_model_table
# Find all of the translated attributes for all records in the model.
klass = self.class_name.constantize
return unless klass.count > 0
all_translated_attributes = klass.all.collect{|m| m.attributes}
all_translated_attributes.each do |translated_record|
# Create a hash containing the translated column names and their values.
translated_attribute_names.inject(fields_to_update={}) do |f, name|
f.update({name.to_sym => translated_record[name.to_s]})
end
# Now, update the actual model's record with the hash.
klass.update_all(fields_to_update, {:id => translated_record['id']})
end
end
end
end
end
Created a migration with the following:
class TranslateAndMigratePages < ActiveRecord::Migration
def self.up
Page.create_translation_table!({
:title => :string,
:custom_title => :string,
:meta_keywords => :string,
:meta_description => :text,
:browser_title => :string
})
say_with_time('Migrating Page data to translation tables') do
Page.move_data_to_translation_table
end
end
def self.down
say_with_time('Moving Page translated values into main table') do
Page.move_data_to_model_table
end
Page.drop_translation_table!
end
end
Borrows heavily from Globalize 3 and refinerycms.

Why doesn't validation in a model referenced by a callback cause my original transaction to fail?

I have a model with an after_create callback. This callback causes a new record to be created in another model. However if a validation fails in the child record creation, the original transaction is still being saved.
This doesn't seem right. According to Rails docs the whole thing is wrapped in a transaction. Am I doing something wrong?
class ServiceProvision < ActiveRecord::Base
has_one :cash_receipt
after_create :receive_payment_for_service_provision, :if => Proc.new { |sp| sp.immediate_settlement == true }
private
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
CashReceipt.create( :account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => ( cash_account ? cash_account.id : nil ) )
end
end
class CashReceipt < ActiveRecord::Base
belongs_to :service_provision
validates_presence_of :cash_account_id
end
The CashReceipt does fail and returns an error when its passed nil for the cash_account_id, however my new ServiceProvision object is still being saved.
it "should fail if a cash account doesn't exist for the currency and institution" do
currency = Factory.create( :currency )
institution = Factory.create( :institution )
service_provision = Factory.build( :service_provision, :currency_id => currency.id, :institution_id => institution.id, :immediate_settlement => true )
service_provision.save.should == false
service_provision.should have( 1 ).error
end
'ServiceProvision service provision creation should raise an error if a cash account doesn't exist for the currency and institution' FAILED expected: false,
got: true (using ==)
This seems to contradict this from the docs
Both Base#save and Base#destroy come
wrapped in a transaction that ensures
that whatever you do in validations or
callbacks will happen under the
protected cover of a transaction. So
you can use validations to check for
values that the transaction depends on
or you can raise exceptions in the
callbacks to rollback, including
after_* callbacks.
And if I manually try to cancel the transaction in the callback like so:
cr = CashReceipt.create( :account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => ( cash_account ? cash_account.id : nil ) )
unless cr.errors.empty?
errors.add_to_base("Error while creating CashReciept [#{cr.errors}].")
return false
end
then the new ServiceProvision object is still saved.
Move the CacheReceipt creation to before_validation filter. Since you have a has_one association on
ServiceProvision, the CacheReceipt object will have the correct :service_provision_id after save. Your code will be as follows:
before_validation :receive_payment_for_service_provision, :if => :immediate_settlement?
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
self.cash_receipt.build(:account_id => account.id,
:amount => self.amount,
:currency_id => self.currency.id,
:cash_account_id => ( cash_account ? cash_account.id : nil ) )
end
Now the save on ServiceProvision instance will return false if there are errors while saving the associated CacheReceipt.
Rollbacks only happen automatically with before callbacks:
The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception.
This makes sense because it allows for AR to prime the model and save it in memory before applying the transaction. Since you've done an after it has no knowledge of what to rollback too. Why not try before_save and see what you get.
You have to check the execution status of CashReceipt.create call in receive_payment_for_service_proviion method.
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
cr = CashReceipt.create( :account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => ( cash_account ? cash_account.id : nil ) )
unless cr.errors.empty?
# Make the ServiceProvision instance invalid
errors.add_to_base("Error while creating CashReciept [#{cr.errors}].")
return false # terminate the callback chain and roll back the TX immediately.
end
end
PS: You can simplify your after_create specification as follows:
after_create :receive_payment_for_service_provision, :if => :immediate_settlement?
Thanks to #KandadaBoggu, who led me to the solution...
Turns out the solution is to change the callback to before_create, and then do this:
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
cr = self.create_cash_receipt( :account_id => account.id,
:amount => self.amount,
:currency_id => self.currency.id,
:cash_account_id => ( cash_account ? cash_account.id : nil ) )
unless cr.errors.empty?
errors.add_to_base( "Error while creating CashReciept [#{cr.errors}]." )
return false
end
end
In other words, we still need to manually check for validation errors in the association.

Resources