Rails 4 combining output from models - ruby-on-rails

I am a beginner, so this question maybe very elementary. I appreciate any help. I have three models - one Metal that does a query of a database to get a price and another QuoteMetal which is nested through has_many and accepts_nested_attributes_for in Lead. I need the results of the query conducted through the Metal model to be available in the lead collection so I can display the results in the view.
My question: how do I get those query results (specifically the price attribute) to be in the Lead instance variable. Of if not the lead instance variable - what is the best way to display the results. Below is my code.
class LeadsController < ApplicationController
def index
#lead = Lead.new
#quote_metal = #lead.quote_metals.build
#quote_melee = #lead.quote_melees.build
#quote_diamond = #lead.quote_diamonds.build
end
def create
#raise params.inspect
#lead = Lead.new(lead_params)
#qm = #lead.quote_metals.map { |f| Metal.calculate_metal(f).first}
##lead.quote_metals.each do |f|
# f[:price] = #qm[:price].to_i
#end
#lead = Lead.create!(lead_params)
end
def show
end
private
def lead_params
params.require(:lead).permit([:name, :email, :phone, :zip, :note, :method,
quote_metals_attributes: [:id, :metal, :weight, :unit, :price],
quote_melees_attributes: [:shape, :quality, :quantity, :totalweight, :size],
quote_diamonds_attributes: [:shape, :size, :color, :clarity]
])
end
end
Metal Model:
class Metal < ActiveRecord::Base
enum metal: {platinum: 1, palladium: 2, "10_karat_gold": 3, "14_karat_gold": 4,
"18_karat_gold": 5, "22_karat_gold": 6, silver: 7}
enum unit: {pennyweight: 1, grams: 2, ounce: 3, pound: 4}
attr_accessor :weight
#Validations
validates :metal, :unit, :weight, presence: true
scope :calculate_metal, ->(quote_metal) {
where(metal: QuoteMetal.metals[quote_metal.metal],
unit: QuoteMetal.units[quote_metal.unit])
}
scope :multiple, ->(quote_metal){
}
end
Create.html.erb
<pre>
<%= #qm.inspect %>
<br>
<%= #lead.quote_metals.inspect %>
<br>
<%= #qm.inspect %>
<br>
<%= #qm2.inspect %>
</pre>
Output:
[#<Metal id: 11, metal: 2, unit: 2, price: #<BigDecimal:7ff01dac4be8,'0.1367E2',18(18)>, created_at: "2015-05-26 10:36:38", updated_at: nil, weight: nil>, #<Metal id: 11, metal: 2, unit: 2, price: #<BigDecimal:7ff01da96450,'0.1367E2',18(18)>, created_at: "2015-05-26 10:36:38", updated_at: nil, weight: nil>]
#<ActiveRecord::Associations::CollectionProxy [#<QuoteMetal id: 231, lead_id: 176, metal: 2, unit: 2, price: nil, weight: #<BigDecimal:7ff01cc71320,'0.1E1',9(18)>, quote: nil, created_at: "2015-07-14 18:26:46", updated_at: "2015-07-14 18:26:46">, #<QuoteMetal id: 232, lead_id: 176, metal: 2, unit: 2, price: nil, weight: #<BigDecimal:7ff01ca49d90,'0.2E1',9(18)>, quote: nil, created_at: "2015-07-14 18:26:46", updated_at: "2015-07-14 18:26:46">]>
[#<Metal id: 11, metal: 2, unit: 2, price: #<BigDecimal:7ff01dac4be8,'0.1367E2',18(18)>, created_at: "2015-05-26 10:36:38", updated_at: nil, weight: nil>, #<Metal id: 11, metal: 2, unit: 2, price: #<BigDecimal:7ff01da96450,'0.1367E2',18(18)>, created_at: "2015-05-26 10:36:38", updated_at: nil, weight: nil>]
[[#<Lead id: nil, name:..., note: "asdf", created_at: nil, updated_at: nil>, #<Metal id: 11, metal: 2, unit: 2, price: #<BigDecimal:7ff01dac4be8,'0.1367E2',18(18)>, created_at: "2015-05-26 10:36:38", updated_at: nil, weight: nil>]]
Thank you.

Related

How to group ruby array based on id in the hash

I have two models post and topic which has a one to many relation
#Post.rb
class Post < ApplicationRecord
belongs_to :topic
validates :topic_id, presence: true
validates :title, presence: true
validates :body, presence: true
end
if i have an array of posts by calling Post.all for example
#<ActiveRecord::Relation [#<Post id: 1, title: "Rails Code", body: "class TopicsController < ApplicationController\r\n ...", created_at: "2018-05-22 09:34:15", updated_at: "2018-05-22 09:34:15", topic_id: "2">,
#<Post id: 2, title: "Post with tags", body: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq...", created_at: "2018-05-22 11:48:54", updated_at: "2018-05-22 11:48:54", topic_id: "2">,
#<Post id: 3, title: "12hello world12", body: "12312sdfsdfsdsdsdsd31231231231231", created_at: "2018-05-23 12:54:02", updated_at: "2018-05-23 12:54:02", topic_id: "2">,
#<Post id: 4, title: "12hello world12", body: "12312sdfsdfsdsdsdsd31231231231231", created_at: "2018-05-23 12:54:09", updated_at: "2018-05-23 12:54:09", topic_id: "1">,
#<Post id: 5, title: "12hellsdssdo world12", body: "12312sdfsdfsdsdsdsd31231231231231", created_at: "2018-05-23 12:54:15", updated_at: "2018-05-23 12:54:15", topic_id: "1">]>
is there a way i could group them by their topic_id so i could display them topic by topic
example
Topic - 1
post 1 post2 post 3
Topic -2
post4 post5
Use this:
Post.all.group_by(&:topic_id).each do |topic_id, posts|
...
end
Found the answer.
You can do it using ruby's Enumerable#group_by:
post.group_by{|post| post.topic_id}

rspec undefined method `id' for nil:NilClass

require 'rails_helper'
feature "comment" do
given(:current_user) do
create(:user)
end
given(:undertaking) do
create(:undertaking)
end
background do
login_as(current_user)
end
scenario "can create comment" do
#below two because undertaking = user_id:2 & asking_id:1
create(:user)
create(:asking)
p undertaking
p Asking.find(1)
p User.find(2)
p User.find(1)
p Undertaking.all
visit undertaking_path(undertaking)
expect(current_path).to eq undertaking_path(1)
within("form#undertake-form-test") do
fill_in "content" , with: "heyheyhey"
end
click_button 'Send'
expect(page).to have_content 'heyheyhey'
end
end
This is spec/features/comment_spec.rb.
and this below is result command rspec.
#<Undertaking id: 1, title: "MyString", content: "MyText", result: false, user_id: 2, asking_id: 1, created_at: "2016-12-13 15:07:08", updated_at: "2016-12-13 15:07:08">
#<Asking id: 1, content: "MyText", fromlang: "MyString", tolang: "MyString", usepoint: 1, finished: false, title: "MyString", deadline: nil, user_id: 1, created_at: "2016-12-13 15:07:08", updated_at: "2016-12-13 15:07:08">
#<User id: 2, email: "shiba.hayato2#docomo.ne.jp", created_at: "2016-12-13 15:07:08", updated_at: "2016-12-13 15:07:08", provider: nil, uid: nil, name: "Shiruba", occupation: "大学生", age: 10, sex: "男性", content: "heyheyheyeheyeheye", skill: "日本語検定3級", picture: "/assets/default_user.jpg", point: 500, country: "Japan", language1: "Japanese", language2: "Korea", language3: "English">
#<User id: 1, email: "shiba.hayato1#docomo.ne.jp", created_at: "2016-12-13 15:07:08", updated_at: "2016-12-13 15:07:08", provider: nil, uid: nil, name: "Shiruba", occupation: "大学生", age: 10, sex: "男性", content: "heyheyheyeheyeheye", skill: "日本語検定3級", picture: "/assets/default_user.jpg", point: 500, country: "Japan", language1: "Japanese", language2: "Korea", language3: "English">
#<ActiveRecord::Relation [#<Undertaking id: 1, title: "MyString", content: "MyText", result: false, user_id: 2, asking_id: 1, created_at: "2016-12-13 15:07:08", updated_at: "2016-12-13 15:07:08">]>
F
Failures:
1) comment can create comment
Failure/Error: <%= #undertaking.id %>
ActionView::Template::Error:
undefined method `id' for nil:NilClass
and this below is undertaking_controller.rb.
class UndertakingController < ApplicationController
def show
#undertaking=Undertaking.find(params[:id])
#comment=Comment.new do |c|
c.user=current_user
end
end
end
and this below is undertaking/show.html.erb.
<%= #undertaking.id %>
Why do I have the error? Why #undertaking is nil in view although Undertaking.first is not nil in spec/features/comment_spec.rb?Please help me.
I think it has to do with the naming used for your controller . The convention is undertakings/show.html.erb for the view instead of undertaking/show.html.erb . I would also use
class UndertakingsController < ApplicationController
instead of
class UndertakingController < ApplicationController
Finally I would check that all my routes also have the correct naming. Hope that helps. Good luck

Why isn't Rails deleting dependent objects?

I have...
/app/models/input.rb:
class Input < ActiveRecord::Base
has_many :questions, :dependent => :destroy
after_commit :create_matching_questions
def create_matching_questions
#element_id = Element.all.select{|e| e.meta == true}.first.id
#standard_id = Standard.all.select{|s| s.meta == true}.first.id
#description = ["Does the site stock ", self.name, "?"].join
Product.all.each do |product|
#question = product.questions.find_or_create_by_element_id_and_standard_id_and_description!(#element_id, #standard_id, #description)
self.questions << #question
#question.fields.find_or_create_by_name("The site sells this product and it is in stock")
#question.fields.find_or_create_by_name("The site sells this product but it is not in stock")
#question.fields.find_or_create_by_name("The site does not sell this product")
#question.update_attributes :active => true
end
return true
end
end
/app/models/question.rb:
class Question < ActiveRecord::Base
belongs_to :input
after_commit :create_matching_surveys
def create_matching_surveys
if self.active == true
self.reload.product.reviews.each do |review|
review.competitors.each do |competitor|
(1..self.iterations).each do |iteration|
survey = competitor.surveys.find_or_create_by_question_id_and_iteration!(self.id, iteration)
survey.save
end
end
end
return true
else
self.destroy_matching_surveys
end
end
def destroy_matching_surveys
self.surveys.each do |survey|
survey.destroy if survey.question_id == self.id
end
return true
end
end
Why, then, do I get...
> #finance = Good.create! :name => "Finance"
=> #<Good id: 6, name: "Finance", created_at: "2013-06-13 02:56:20", updated_at: "2013-06-13 02:56:20">
> #super = Input.create! :name => "Superannuation"
=> #<Input id: 11, name: "Superannuation", mispelling: nil, typo: nil, created_at: "2013-06-13 02:56:28", updated_at: "2013-06-13 02:56:28">
> #first = #super.questions.first
=> #<Question id: 48, standard_id: 1, description: "Does the site stock Superannuation?", element_id: 2, condition_id: nil, blueprint_name: nil, blueprint_url: nil, additive: false, instructions: nil, created_at: "2013-06-13 02:56:41", updated_at: "2013-06-13 02:56:41", active: false, postscript: "<p>If you have any comments about this question or ...", iterations: 1, product_id: 1, precondition_id: nil, input_id: 11>
> #last = #super.questions.last
=> #<Question id: 60, standard_id: 1, description: "Does the site stock Superannuation?", element_id: 2, condition_id: nil, blueprint_name: nil, blueprint_url: nil, additive: false, instructions: nil, created_at: "2013-06-13 02:56:43", updated_at: "2013-06-13 02:56:43", active: false, postscript: "<p>If you have any comments about this question or ...", iterations: 1, product_id: 23, precondition_id: nil, input_id: 11>
> #super.destroy
=> #<Input id: 11, name: "Superannuation", mispelling: nil, typo: nil, created_at: "2013-06-13 02:56:28", updated_at: "2013-06-13 02:56:28">
> #super.destroyed?
=> true
> #first.destroyed?
=> false
> #last.destroyed?
=> false
Surely #first and #last should be destroyed automatically?
I had the same problem, solved it by :dependent => :delete_all instead of :dependent => :destroy.
:delete_all doesn't call the destroy method from your controller and delete data directly from your database.

Why is my after_save callback stopping my ActiveRecord association from saving properly?

When I comment out my after_save call back, my ActiveRecord associations work just fine. In Rails Console, you'd see:
> #report = Report.create :name => "foo"
=> #<Report id: 9, name: "foo", created_at: "2013-03-05 09:51:55", updated_at: "2013-03-05 09:51:55">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 18, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 09:52:32", updated_at: "2013-03-05 09:52:32", additive: false, instructions: nil>
> #report.questions
=> [#<Question id: 18, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 09:52:32", updated_at: "2013-03-05 09:52:32", additive: false, instructions: nil>]
> #question.reports
=> [#<Report id: 9, name: "foo", created_at: "2013-03-05 09:51:55", updated_at: "2013-03-05 09:51:55">]
However, the associations stop working when I add the following after_save callback to question.rb:
def create_matching_surveys
self.reports.each do |report|
report.reviews.each do |review|
review.competitors.each do |competitor|
competitor.surveys.find_or_create_by_question_id(self.id)
end
end
end
end
Then, in Rails Console, you get:
> #report = Report.create :name => "foo"
=> #<Report id: 13, name: "foo", created_at: "2013-03-05 10:20:51", updated_at: "2013-03-05 10:20:51">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 24, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 10:21:02", updated_at: "2013-03-05 10:21:02", additive: false, instructions: nil>
> #report.questions
=> [#<Question id: 24, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 10:21:02", updated_at: "2013-03-05 10:21:02", additive: false, instructions: nil>]
> #question.reports
=> []
This happens whether or not the report has reviews that have competitors.
The strange thing is I thought the callback was meant to happen after the question was saved? So by rights the association should save too before any of this happens, right?
How do I fix it?
UPDATE
I think I have to call the callback in the right spot in the object's life cycle, but I can't find that spot. Here's why I think this:
> #report = Report.create :name => "foo"
=> #<Report id: 20, name: "foo", created_at: "2013-03-05 12:29:35", updated_at: "2013-03-05 12:29:35">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 31, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 12:30:14", updated_at: "2013-03-05 12:30:14", additive: false, instructions: nil>
> #question.reports
=> []
> #question.update_attributes :description => "foo"
=> true
> #question.reports
=> [#<Report id: 20, name: "foo", created_at: "2013-03-05 12:29:35", updated_at: "2013-03-05 12:29:35">]
BTW, the method is now in question_observer.rb:
class QuestionObserver < ActiveRecord::Observer
def after_save(model)
model.reload
model.reports.reload
model.reports.each do |report|
report.reviews.each do |review|
review.competitors.each do |competitor|
competitor.surveys.find_or_create_by_question_id(model.id)
end
end
end
return true
end
end
The answer was to use a neat new callback hook called after_commit which was introduced with Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_commit.
The only issue is after_commit doesn't work "out of the box" with transactional fixtures, but there are plenty of solutions out there, and I found this one worked well for me: https://supportbee.com/devblog/2012/01/14/testing-after_commitafter_transaction-with-rspec/

Update attribute in callback Rails 3

Following on from this question, I have spent the day trying to add a cumulative running sales total to my sales table. It's a bit tricky (for me) because I want a running total for sales where the isbn_id is the same, and, within that set, records where the channel_id is the same - ranked by invoice_date. This is all so I can calculate royalties on a particular range of units sold.
Here's my non-working callback code, in the Sale model:
before_save :runningtotal
private
def runningtotal
#sale = Sale.order("invoice_date ASC")
#lastbal = #sale.find_all_by_isbn_id(#isbn).group_by(&:channel)
#that sucessfully gets all sales ranked by date ascending, then groups them by channel, just for the current isbn.
#lastbal.each do |channel, sale|
sale.each_with_index do |sale, i|
previous_sale = sale[i-1] unless i==0
next unless previous_sale
#total_quantity = previous_sale.quantity + :quantity
write_attribute(:total_quantity,#total_quantity)
end
end
end
Is this roughly how a callback should be written - just in the model? Does it just run magically before_save of a new sale?
My core question is: how can I update the attribute "total_quantity" to be the sum of "quantity" for the current record, and "total_quantity" for the previous record by date, in a before_save callback, within the constraints of the finds for isbn_id and channel_id?
Here's the output of the find:
ruby-1.9.2-p180 :025 > #lastbal = #sale.find_all_by_isbn_id(#isbn).group_by(&:channel)
=> {#<Channel id: 4, isbn_id: nil, channel_name: "Gratis", created_at: "2011-05-26 11:08:22", updated_at: "2011-05-26 11:08:22">=>[#<Sale id: 26, isbn_id: 2, quantity: 10000, value: 40000, currency: "", total_quantity: nil, created_at: "2011-05-26 11:11:30", updated_at: "2011-05-26 11:11:30", customer: "6", retail_price: nil, discount: nil, channel_id: 4, invoice_date: "2011-05-18", rule_id: nil, trenche: nil>], #<Channel id: 1, isbn_id: nil, channel_name: "Home", created_at: "2011-05-16 19:47:27", updated_at: "2011-05-16 19:47:27">=>[#<Sale id: 22, isbn_id: 2, quantity: 1000, value: 5193, currency: "", total_quantity: nil, created_at: "2011-05-25 19:46:03", updated_at: "2011-05-25 19:46:03", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2011-05-11", rule_id: nil, trenche: nil>, #<Sale id: 24, isbn_id: 2, quantity: 1000, value: 4394, currency: "", total_quantity: nil, created_at: "2011-05-26 09:48:16", updated_at: "2011-05-26 09:48:16", customer: "g", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2011-05-10", rule_id: nil, trenche: nil>, #<Sale id: 25, isbn_id: 2, quantity: 1000, value: 4394, currency: "", total_quantity: nil, created_at: "2011-05-26 10:02:38", updated_at: "2011-05-26 10:02:38", customer: "g", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2011-05-05", rule_id: nil, trenche: nil>, #<Sale id: 21, isbn_id: 2, quantity: 1000, value: 5193, currency: "", total_quantity: nil, created_at: "2011-05-25 14:12:45", updated_at: "2011-05-25 14:12:45", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2010-10-15", rule_id: nil, trenche: nil>, #<Sale id: 13, isbn_id: 2, quantity: 50, value: 159, currency: "", total_quantity: nil, created_at: "2011-05-25 12:33:09", updated_at: "2011-05-25 12:33:09", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2010-01-01", rule_id: nil, trenche: nil>, #<Sale id: 14, isbn_id: 2, quantity: 25, value: 129, currency: "", total_quantity: nil, created_at: "2011-05-25 12:33:23", updated_at: "2011-05-25 12:33:23", customer: "", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2010-01-01", rule_id: nil, trenche: nil>, #<Sale id: 12, isbn_id: 2, quantity: 100, value: 415, currency: "", total_quantity: nil, created_at: "2011-05-25 12:32:50", updated_at: "2011-05-25 15:13:21", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2001-10-01", rule_id: nil, trenche: nil>, #<Sale id: 11, isbn_id: 2, quantity: 500, value: 2197, currency: "", total_quantity: nil, created_at: "2011-05-25 12:32:24", updated_at: "2011-05-25 15:11:20", customer: "a", retail_price: nil, discount: nil, channel_id: 1, invoice_date: "2000-10-01", rule_id: nil, trenche: nil>], #<Channel id: 2, isbn_id: nil, channel_name: "Export", created_at: "2011-05-16 19:47:35", updated_at: "2011-05-16 19:47:35">=>[#<Sale id: 23, isbn_id: 2, quantity: 2000, value: 5000, currency: "", total_quantity: nil, created_at: "2011-05-26 09:16:15", updated_at: "2011-05-26 09:16:15", customer: "v", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2011-05-02", rule_id: nil, trenche: nil>, #<Sale id: 17, isbn_id: 2, quantity: 242, value: 657, currency: "", total_quantity: nil, created_at: "2011-05-25 12:34:24", updated_at: "2011-05-25 12:34:24", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-10-15", rule_id: nil, trenche: nil>, #<Sale id: 18, isbn_id: 2, quantity: 54, value: 194, currency: "", total_quantity: nil, created_at: "2011-05-25 12:34:44", updated_at: "2011-05-25 12:34:44", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-10-15", rule_id: nil, trenche: nil>, #<Sale id: 15, isbn_id: 2, quantity: 135, value: 377, currency: "", total_quantity: nil, created_at: "2011-05-25 12:33:48", updated_at: "2011-05-25 12:33:48", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-09-15", rule_id: nil, trenche: nil>, #<Sale id: 16, isbn_id: 2, quantity: 433, value: 830, currency: "", total_quantity: nil, created_at: "2011-05-25 12:34:06", updated_at: "2011-05-25 12:34:06", customer: "b ", retail_price: nil, discount: nil, channel_id: 2, invoice_date: "2010-09-15", rule_id: nil, trenche: nil>]}
Here's the columns in my Sale model:
# id :integer not null, primary key
# isbn_id :integer
# quantity :integer
# value :integer
# currency :string(255)
# total_quantity :integer
# created_at :datetime
# updated_at :datetime
# customer :string(255)
# retail_price :integer
# discount :decimal(, )
# channel_id :integer
# invoice_date :date
# rule_id :integer
Thanks so much in advance.
UPDATE: final solution.
Really not sure that this counts as 'giving back to the community' as it's comically verbose, not DRY, full of puts which I used to figure out all the bugs, and badly formatted to boot, but heck, I'm a noob at and the very least I can come back here and laugh at myself in a few years when I know what I'm doing. So, here's my final solution, in Sale.rb. Poor overstuffed model. I will refactor this, one day.
before_save :runningtotal
after_commit :refresh
private
def runningtotal
# get the latest sale that matches the new sale's isbn and channel id, then rank by invoice date descending, and get the first record:
lastsale = Sale.where(:isbn_id => self.isbn_id).where(:channel_id => self.channel_id).order("invoice_date DESC").first
allsales = Sale.where(:isbn_id => self.isbn_id).where(:channel_id => self.channel_id).order("invoice_date DESC")
# set the total_quantity field in the new sales record to its quantity + the last sale's total.
if allsales.maximum(:invoice_date).nil?
puts "runningtotal thinks the max of invoice date in the allsales relation is nil"
puts "and runningtotal is setting total_quantity on the new sale to be #{self.quantity + (lastsale.try(:total_quantity) || 0)}"
self.total_quantity = self.quantity + (lastsale.try(:total_quantity) || 0)
else
if self.invoice_date < allsales.maximum(:invoice_date)
puts "the runningtotal method has been skipped because runningtotal thinks the current invoice date is less than the highest invoice date in the allsales relation"
else
puts "this is a normal entry so runningtotal has set the total quantity to be #{self.quantity + (lastsale.try(:total_quantity) || 0) }"
self.total_quantity = self.quantity + (lastsale.try(:total_quantity) || 0)
end
end
end
def refresh
allsales = Sale.where(:isbn_id => self.isbn_id).where(:channel_id => self.channel_id).order("invoice_date ASC")
#if the runningtotal callback hasn't run, the total quantity will be nil, and nil triggers this after_commit callback
if total_quantity.nil?
puts "running refresh callback"
puts "here's a sample parameter pass: id: #{id} quantity: #{quantity} date: #{invoice_date} "
puts "allsales class is #{allsales.class}"
# if the new sale that's being saved has a date that's before any previous sale...
puts "before the if, refresh thinks that the earliest invoice date is #{allsales.minimum(:invoice_date)} and that invoice date is #{invoice_date}"
if invoice_date <= allsales.minimum(:invoice_date)
puts "date earlier than existing sales dates"
puts "refresh thinks that the earliest invoice date is #{allsales.minimum(:invoice_date)} and that invoice date is #{invoice_date}"
#... then set its total_quantity to the sale quantity...
update_attribute(:total_quantity, quantity)
puts "total_qty updated with qty"
# ... and update all subsequent records' total_quantity (skipping the before_save callback which would trigger an infinite loop).
allsales.each_with_index do |sale, i|
previous_sale = allsales[i-1] unless i==0
next unless previous_sale
puts "getting qty out of arel when date earlier than others: #{previous_sale.quantity}"
puts "this is adding #{quantity} to #{previous_sale.quantity } which is #{quantity + previous_sale.total_quantity }"
Sale.skip_callback(:save, :before, :runningtotal )
sale.update_attribute(:total_quantity, (sale.quantity + previous_sale.total_quantity ))
Sale.set_callback(:save, :before, :runningtotal)
end
else
# if the invoice date is within the min and max range of the previous sales...
# ... update all previous and subsequent records' total_quantity (skipping the before_save callback which would trigger an infinite loop).
allsales.each_with_index do |sale, i|
previous_sale = allsales[i-1] unless i==0
next unless previous_sale
puts "getting qty out of arel within existing date range: #{previous_sale.quantity}"
puts "this is adding #{quantity} to #{previous_sale.quantity } which is #{quantity + previous_sale.total_quantity }"
Sale.skip_callback(:save, :before, :runningtotal )
sale.update_attribute(:total_quantity, (sale.quantity + previous_sale.total_quantity ))
Sale.set_callback(:save, :before, :runningtotal )
end
end
end
end
Yes, using before_save in the model will run that every time it is saved, whether new or updated. Thus you need to watch out in calculations the expect the current (new) record to not exist yet. ;) You might want to use before_save, :on => :create to limit it to the creation action.
However, if I understand your english statement of the problem, your code is rather convoluted. I don't even see where #isbn is set, that could be dangerous...
Does this need to update the total on other objects this isbn and channel? Usually it's better form to simply calculate that as needed rather than trying to cache the total in every record.
within the callback, self is the current (new?) record, so use it to refer to new values.
#sale = Sale.order("invoice_date ASC")
#lastbal = #sale.find_all_by_isbn_id(#isbn).group_by(&:channel)
can be replaced by this, I think:
#lastbal = Sale.order("invoice_date ASC").where(:isbn_id => self.isbn_id).group_by(&:channel)
I'm assuming that #isbn is actually the new record's isbn.
From there, I'm not sure if you are only intending to update the new record or the old ones... If you want to update the current record, just set the attribute and exit the callback, and it will be saved when the rest is saved:
self.total_quantity = previous_sale.quantity + self.quantity
If you are intending to update the other objects too, then we have to update those objects and save them. I don't see that happening at all here in your code.
Your code goes through several loops, possibly hitting the write_attribute several times... that doesn't make sense.
If you mean you want to find the last record that matches the current isbn and channel to update the new record, here's what I would do:
def runningtotal
lastsale = Sale.where(:isbn_id => self.isbn_id).
where(:channel_id => self.channel_id).
order("invoice_date DESC").first
# that should be the latest sale that matches
# the current isbn and channel
self.total_quantity = self.quantity + (lastsale.try(:total_quantity) || 0)
# watch out for nil if no previous record exists ^
end
`

Resources