Count nested item before save - ruby-on-rails

Let have Order and Item models.
class Order < ApplicationRecord
has_many :items, inverse_of: :order, dependent: :delete_all
before_save do
self.packed_volume = compute_packed_volume
end
private
def compute_packed_volume
items.count * 0.1O
end
end
And
class Item < ApplicationRecord
belongs_to :order, inverse_of: :items
end
The problem is that items.count is equals to 0 since items are not yet created.
How can we get the number of items that will be created to used it when we create an order?

Try size instead, it won't run a query
items.size * 0.1O
Hope that helps!

What you're looking for is a "counter cache". It implements exactly what you're trying to do. ActiveRecord can do this for you:
belongs_to :post, :counter_cache => true
There are a couple of gems that do this, updating a count field in the parent record whenever a child record is created or deleted.
Gem counter-cache does the job simply. Another, counter-culture takes it a bit further, including support for counting children in has_many :through relationships.
Your example is a little more interesting, as you're not looking for the count, but a computation on the count. So, you could either just roll with that and use it to compute the packed_volume on the fly, probably in a method on the model very similar to your compute_packed_volume() method.
If you wanted to store the actual volume in your parent record (perhaps it is very expensive to compute), you need to shift from putting callbacks on the parent model to putting them on the child model. The counter_culture gem supports that with its "Delta Magnitude". Something like this:
class Item < ActiveRecord::Base
belongs_to :order
counter_culture :order, column_name: :packed_volume, delta_magnitude: 0.1
end
class Order < ActiveRecord::Base
has_many :items
end
The delta_magnitude parameter can take a proc, so you can do more complicated things. Perhaps something like this:
counter_culture :order, column_name: :packed_volume, delta_magnitude: -> {|item| item.length * item.width * item.height }
You could roll your own solution along these lines if you have other requirements that preclude using a gem. You'll need to add callbacks to Item for after_create and before_destroy to increment/decrement the parent record. You may also need to update the order when the record is changed, if your volume computation becomes more complicated.

Related

Array not saving from one model to another in rails

I am trying to save an array of multiple ids (item_variation_ids) to a model called items_stock from item variations model. In a column called item_variation_ids in item_stock, it is saving the ids like [1,2,3] for twice. I want the item_variation_ids to be saved once only with 1,2,3 in a single column.
My item_variation model
#app/models/item_variation
class ItemVariation < ApplicationRecord
belongs_to :item
validates_associated :item
after_save :add_to_item_stock
def add_to_item_stock
ItemStock.create(item_variation_ids: ItemVariation.ids, items_id: items_id)
end
end
My item model
#app/models/item
class Item < ApplicationRecord
has_many :item_variations, foreign_key: :items_id
has_many :item_stocks, foreign_key: :items_id
accepts_nested_attributes_for :item_stocks
end
My item_stock model
#app/models/item_stock
class ItemStock < ApplicationRecord
belongs_to :item
end
But how do you know which ItemVariation ids should go on that ItemStock? and you are creating one ItemStock each time any variation gets saved. I don't even think you need to set that ids array since the ItemStock already belongs to an Item which has many variations (#item_stock.item.variations and you are done).
Also now you are talking about a stock_qty attribute you never mentioned before, you are never setting it on the callback and you didn't show your database schema. where does that amout come from? is an attribute on the variation that you want to sum to the current item_stock?
I also don't understand why an item has many item stocks for the code you are showing.
I'll do a wild guess and suggest you do something like:
ItemStock
belongs_to :item
belongs_to :item_variation
end
ItemVariation
after_save :add_to_item_stock
def add_to_item_stock
item_stock = self.item.item_stock.where(item_variation_id: self.id).first_or_initialize
item_stock.stock_qty = self.stock_qty
item_stock.save
end
end
but as I said, it's a wiiiiild guess. I'd recommend you to first try to understand what you are doing, because it seems like you just copied to code from that question you linked and you are no actually understanding it.

Caching for model in rails

product.rb
has_many :votes
vote.rb
belongs_to :product
Every time, i use sorting in my index controller:
index_controller.rb
def index
#products = Product.all.sort { |m| m.votes.count }
end
So, i think it would be good to cache votes count for each product (create additional column votesCount in products table)?
If yes, can i preform that using before_save and before_delete callbacks in vote.rb model?
Or what is the best practice method?
Give me some examples please.
I guess you are looking for counter_cache
The :counter_cache option can be used to make finding the number of belonging objects more efficient
Consider these models:
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
has_many :orders
end
With this declaration, Rails will keep the cache value up to date, and then return that value in response to the size method.
Although the :counter_cache option is specified on the model that includes the belongs_to declaration, the actual column must be added to the associated model. In the case above, you would need to add a column named orders_count to the Customer model

Is there a callback for before loading a relationship in a has_many relationship or perhaps a Proc

I am using Rails 3.2.18. This is a bit of a complex behavior but we have a has_many relationship say the following:
class Item < ActiveRecord::Base
has_many :notes
...
class Note < ActiveRecord::Base
belongs_to :item
attr_accessor :body, :is_enabled
What I'd like to do is when a method does:
#note=Note.find(2)
the item object (or possibly the note object) can do a check before establishing the relationship. Specifically, in our body we have a bunch of content that must be checked for all the items to be valid (say we use a phrase like ITEM2, ITEM17 for item #2 or item #17). If any of the items are invalid, the the note should have is_enabled set to false and not be loaded. How can I check the validity of ITEM2 before the note is loaded? I was hoping for a callback or perhaps a condition which contains a macro like:
has_many :notes, Proc.new { I don't know what to put here? }
Considering is_enabled flag is set in database, you may define multiple associations like:
has_many :notes #This will retrieve all notes
has_many :enabled_notes, :conditions => {:is_enabled => true}

How to combine duplicate rails objects and update all references

I'm working on a Rails app (Ruby 1.9.2 / Rails 3.0.3) that keeps track of people and their memberships to different teams over time. I'm having trouble coming up with a scalable way to combine duplicate Person objects. By 'combine' I mean to delete all but one of the duplicate Person objects and update all references to point to the remaining copy of that Person. Here's some code:
Models:
Person.rb
class Person < ActiveRecord::Base
has_many :rostered_people, :dependent => :destroy
has_many :rosters, :through => :rostered_people
has_many :crews, :through => :rosters
def crew(year = Time.now.year)
all_rosters = RosteredPerson.find_all_by_person_id(id).collect {|t| t.roster_id}
r = Roster.find_by_id_and_year(all_rosters, year)
r and r.crew
end
end
Crew.rb
class Crew < ActiveRecord::Base
has_many :rosters
has_many :people, :through => :rosters
end
Roster.rb
class Roster < ActiveRecord::Base
has_many :rostered_people, :dependent => :destroy
has_many :people, :through => :rostered_people
belongs_to :crew
end
RosteredPerson.rb
class RosteredPerson < ActiveRecord::Base
belongs_to :roster
belongs_to :person
end
Person objects can be created with just a first and last name, but they have one truly unique field called iqcs_num (think of it like a social security number) which can be optionally stored on either the create or update actions.
So within the create and update actions, I would like to implement a check for duplicate Person objects, delete the duplicates, then update all of the crew and roster references to point to the remaining Person.
Would it be safe to use .update_all on each model? That seems kind of brute force, especially since I will probably add more models in the future that depend on Person and I don't want to have to remember to maintain the find_duplicate function.
Thanks for the help!
The 'scalable' way to deal with this is to make the de-duplication process part of the app's normal function - whenever you save a record, make sure it's not a duplicate. You can do this by adding a callback to the Person model. Perhaps something like this:
before_save :check_for_duplicate
def check_for_duplicate
if iqcs_num
dup = Person.find_by_iqcs_num(self.iqcs_num)
if dup && dup.id != self.id
# move associated objects to existing record
dup.crews = dup.crews + self.crews
# update existing record
dup.update_attributes(:name => self.name, :other_field => self.other_field)
# delete this record
self.destroy
# return false, so that no other callbacks get triggered
return false
end
end
end
You'll want to make sure that you index the table you store Person objects in on the iqcs_num column, so that this lookup stays efficient as the number of records grows - it's going to be performed every time you update a Person record, after all.
I don't know that you can get out of keeping the callback up-to-date - it's entirely likely that different sorts of associated objects will have to be moved differently. On the other hand, it only exists in one place, and it's the same place you'd be adding the associations anyway - in the model.
Finally, to make sure your code is working, you'll probably want to add a validation on the Person model that prevents duplicates from existing. Something like:
validates :iqcs_num, :uniqueness => true, :allow_nil => true

De-Normalizing Sum of associated objects in rails

I'm working on a gift registry app using rails 3.0. The app allows multiple guests to contribute towards a gift. When displaying the gifts I need to show the amount remaining, and if the total amount has been given I needs to show the item as purchased.
For performance I want to de-normalize the sum of the total contributions, and the status of the item.
Seems simple enough, but as I tried to figure out how to put this in the model in a way that is completely encapsulated, and works in all circumstances, things got much more complicated then I expected.
I tried a few different approaches including a callback on the association between item and contribution, but ultimately ended up with a callback on the contribution object.
item.rb
class Item < ActiveRecord::Base
# decimal amount
# decimal total_contributed
# boolean purchased
has_many :contributions, :inverse_of => :item
def set_total_contributed
self.total_contributed = 0
contributions.each do |cont|
self.total_contributed += cont.amount
end
purchased = self.total_contributed >= amount
end
end
order.rb
class Order < ActiveRecord::Base
has_many :contributions, :inverse_of => :order, :dependent => :destroy
end
contribution.rb
class Contribution < ActiveRecord::Base
belongs_to :item, :inverse_of => :contributions
belongs_to :order, :inverse_of => :contributions
after_create do |cont|
item.set_total_contributed
item.save
end
after_destroy do |cont|
item.contributions.delete(cont)
item.set_total_contributed
item.save
end
end
This appears to work in the situations I need it to, but it doesn't feel right.
First, the fact that I have to manually update the contributions association in the destroy callback seems odd.
Also, the de-normalized values are only properly updated when objects are persisted.
So the question is, how can I do this better and what's the best practice for this kind of scenario?

Resources