How to get the current index during recursive call - ruby-on-rails

I use the gem ancestry to create comments.
Now, I can list all comments.
But I want to push serial number to each comment.
For example, if there were 3 comments, the first comment is annotated by 1, the next annotated by 2,..
I have no idea how to do it?
show.html.haml
- if notice
%p.alert.alert-success= notice
= nested_comments(#comment.subtree.arrange(:order => :created_at))
helper
def nested_comments(comments)
if comments.respond_to? :map
comments.map do |comment, sub_comments|
render(comment) + content_tag(:div, nested_comments(sub_comments), :class => "nested_comments")
end.join.html_safe
end
end
each_with_index won't work on recursive
if I have 4 comments, I want to show 0,1,2,3 for each comment
But each_with_index can not make it because it's a recursive call.
comments.each_with_index.map do |(comment, sub_comments), i|
comments
=> {#<Comment id: 2, user_id: 1, ip: nil, content: "I'm id2 the second floor VIVOTEK Releases New Vers...", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 03:59:38", updated_at: "2014-11-07 06:56:12", ancestry: nil>=>
{#<Comment id: 4, user_id: 1, ip: nil, content: "lala", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 05:22:41", updated_at: "2014-11-07 05:22:41", ancestry: "2">=>
{#<Comment id: 5, user_id: 1, ip: nil, content: "son of 4", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 06:38:04", updated_at: "2014-11-07 06:38:04", ancestry: "2/4">=>
{},
#<Comment id: 6, user_id: 1, ip: nil, content: "dild last 252", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 06:52:15", updated_at: "2014-11-07 06:52:15", ancestry: "2/4">=>
{}}}}

You can use with_index with map
comments.map.with_index do |comment, sub_comments, index|

Every enumerable instance in ruby has a method each_with_index, providing an_enumerator. So in your case I would suggest to use:
- comments.map do |comment, sub_comments|
+ comments.each_with_index.map do |idx, comment, sub_comments|
Hope it helps.

I don't know of an elegant solution. But you could pass a counter into your nested_comments function, and deal with the problem manually -- which might well mean without map at all. Ugly, I know.
To take a simpler example, should you need one:
def nested_foo(result, string, index)
index += 1
result << "\n#{index}: #{string}"
if index >= 10
return result
else
return nested_foo(result, string, index)
end
end

Related

Bring the user name when query an item in Rails

I have an endpoint the returns all the comments on a blog. I would like to have the name of the user that made the comment. Is there a way to bring it all together when hitting that comments endpoint or do I have to make another query for each comment?
def comments
#comments = #blog.comments
render json: { comments: #comments }
end
This is what doing #blog.comments returns
[#<Comment id: 1, content: "This is a very good post", created_at: "2020-09-11 01:55:56", updated_at: "2020-09-11 01:55:56", blog_id: 3, user_id: 1>, #<Comment id: 2, content: "I agree with all of this", created_at: "2020-09-11 01:55:56", updated_at: "2020-09-11 01:55:56", blog_id: 3, user_id: 1>, #<Comment id: 3, content: "I don't agree with all of this", created_at: "2020-09-11 01:55:56", updated_at: "2020-09-11 01:55:56", blog_id: 3, user_id: 1>]>
Assuming you have a users table with a field users.name, and a belongs_to :user relationship on your model comment:
Just replace the line
#comments = #blog.comments
with
#comments = #blog.comments.select('comments.*, users.name').joins(:user)

Different behavior of build method

I was trying to implement a first_or_build method and I encounter a problem when saving my parent : the children were missing.
Everything is working fine when I call my method on the relation like parent.childs.first_or_build(name: 'Foo'); parent.save! whereas nothing happen when I do parent.childs.where(name: 'Foo').first_or_build; parent.save!.
The main objective was to propose a similar behavior than .first_or_create applied to the result of a query for example. (Don't tell me about .first_or_initialize !)
Any idea?
Examples :
# this is not working :(
2.times { |i| parent.childs.where(name: "child #{i}").build { |c| c.age = 42 } } ; parent.childs
=> #<ActiveRecord::Associations::CollectionProxy []>
# while this is
2.times { |i| parent.childs.build { |c| c.name = "#{child #{i}"; c.age = 42 } } ; parent.childs
=> #<ActiveRecord::Associations::CollectionProxy [#<Child name: "child 0", age: 42>, #<Child name: "child 1", age: 42>]>
Sorry, I don't quit understand the part about first_or_build method, so I will just talk about the examples there.
First of all, we know that parent.childs.where(name: "child #{i}") and parent.childs are in different class
parent.children.where(name: "child").class
#=> Child::ActiveRecord_AssociationRelation
parent.children.class
#=> Child::ActiveRecord_Associations_CollectionProxy
so it's clear why their :build method are different, the doc are here
ActiveRecord_Associations_CollectionProxy
ActiveRecord_AssociationRelation
I will try to express my view here.
When you use ActiveRecord_AssociationRelation to build a new child, it will initialize a new Child object, and set its parent_id, but it is just an Child object. In this time, when you execute parent.children, the result is empty.
parent.children.where(name: "child1").build({age: 1})
#=> <Child id: nil, name: "child1", age: 1, parent_id: 1, created_at: nil, updated_at: nil>
parent.children
#=> <ActiveRecord::Associations::CollectionProxy []>
parent.save #=> true
parent.children.reload
#=> <ActiveRecord::Associations::CollectionProxy []>
But when you use ActiveRecord_Associations_CollectionProxy, it will initialize a new Child object, and it will also attach itself to parent, so then when you execute parent.children, the result is not empty.
parent.children.build({name: "child2", age: 2})
#=> <Child id: nil, name: "child2", age: 2, parent_id: 1, created_at: nil, updated_at: nil
parent.children
#=> <ActiveRecord::Associations::CollectionProxy [#<Child id: nil, name: "child2", age: 2, parent_id: 1, created_at: nil, updated_at: nil>]>
parent.save #=> true
parent.children.reload
#=> <ActiveRecord::Associations::CollectionProxy [#<Child id: 3, name: "child2", age: 2, parent_id: 1, created_at: "2015-05-28 17:02:39", updated_at: "2015-05-28 17:02:39">]>
In the second way, parent know it has children, so when it save, it will save its children.I think this is it.

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/

How do I output in Views just the variables' values I want in Ruby On Rails?

Now in my application.html.erb in views folder, I wrote this.
<p>List of all post IDs: <%= Post.all.each {|i| print i.id } %></p>
I would like it to output just the post.id of each post. But instead it shows this
List of all post IDs: [#<Post id: 1, title: "Our First Post", content: "Content for our first post", created_at: "2012-11-24 11:22:02", updated_at: "2012-11-26 17:40:54", user_id: 1>, #<Post id: 3, title: "Our Second Post", content: "Content for our second post", created_at: "2012-11-24 11:51:32", updated_at: "2012-11-26 17:41:33", user_id: 2>, #<Post id: 8, title: "Our Second Post", content: "Content of Our mandatory Second Post", created_at: "2012-11-24 19:42:02", updated_at: "2012-11-27 20:46:57", user_id: 1>, #<Post id: 10, title: "C Post", content: "Hi I'm Cee nice to meet you", created_at: "2012-11-26 17:51:20", updated_at: "2012-11-26 17:51:20", user_id: 3>, #<Post id: 20, title: "11", content: "11", created_at: "2012-11-27 19:58:48", updated_at: "2012-11-27 19:58:48", user_id: 4>, #<Post id: 21, title: "22", content: "22", created_at: "2012-11-27 19:58:53", updated_at: "2012-11-27 19:58:53", user_id: 4>, #<Post id: 25, title: "I'm Super Singha!", content: "Yessar!!!", created_at: "2012-11-27 20:45:07", updated_at: "2012-11-27 20:45:07", user_id: 6>, #<Post id: 26, title: "Should this be a blog or a forums or a whatever-wha...", content: ";asljdfi;asfi;asdf;lasbfurbofioboboeifhosdsdbvisbvw...", created_at: "2012-11-27 20:46:28", updated_at: "2012-12-02 14:17:14", user_id: 1>, #<Post id: 27, title: "Hullow", content: "Yoyoyo", created_at: "2012-11-30 07:35:38", updated_at: "2012-11-30 07:35:54", user_id: 6>, #<Post id: 649, title: "um", content: "hey", created_at: "2012-11-30 12:20:58", updated_at: "2012-11-30 12:20:58", user_id: 2>, #<Post id: 82692, title: "LALALALAL", content: "hiopsdahfiosadhfioahfio", created_at: "2012-12-02 13:59:04", updated_at: "2012-12-02 14:22:41", user_id: 2>, #<Post id: 82693, title: "ggg", content: "fff", created_at: "2012-12-02 14:29:42", updated_at: "2012-12-02 14:29:42", user_id: 2>, #<Post id: 82694, title: "sick", content: "sick", created_at: "2012-12-02 14:41:32", updated_at: "2012-12-02 14:41:32", user_id: 5>]
I have tried, puts instead of print, that doesn't work either.
Further: I would also like to make a link_to each post show page from the intended result, how can I achieve that?
Here's my repo: https://github.com/nixor/cpblog , here's heroku site: http://still-plains-5469.herokuapp.com/
Thanks.
The problem is with how you are using the ERB tags here:
<%= Post.all.each {|i| print i.id } %>
Whenever you use <%=, the result of the block will be rendered. In your case, Post.all.each {} returns an array object, which is exactly what you are seeing in your rendered HTML.
In order to print out each item, you will need to loop through the items using <% and then print out what you want using <%=.
<p>List of all post IDs:
<% Post.all.each |post| do %>
<%= link_to post.id, post_path(post) %>
<% end %>
</p>
<p>List of all post IDs:
</p>
<%= Post.all.each do |e| %>
<p>
<%= e.Id %>
<p>
<%= link_to "Show", e %>
<% end %>

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