same active record dataset across many instances - ruby-on-rails

i have a model say modela with a calculate field. like below
class modelA < ActiveRecord::Base
attr_accessor : calc_field
def calc_field
#stuff = modelb.all
//do fancy things with stuff
end
end
now my problem is if i have 100 records of modelA. the modelb.all gets called a 100times. but its the same dataset each time. but the query gets sent out a 100 times.
is there anyway/anywhere i can declare #stuff globally so its gets shared across all instances of modelA. so it gets called only once.

There are numerous ways on how to tackle this problem.
Solution A:
class modelA < ActiveRecord::Base
attr_accessor : calc_field
def calc_field(modelb_info)
#stuff = modelb_info
//do fancy things with stuff
end
end
And then in your code work flow
model_info = ModelB.all
model_a_array.collect{|model_a| model_a model_info}
Solution B
class modelA < ActiveRecord::Base
##stuff ||= ModelB.all
end

You could use a class variable.
def calc_field
##stuff ||= modelb.all
//do fancy things with stuff
end

If you can modify the way of getting the ModelA instances, I would suggest this:
modelas = ModelA.includes(:modelbs)
From then on, the database will not be hit anymore when you call (e.g.)
modelas.first.calc_field
I hope that helps

Related

Is overriding an ActiveRecord relation's count() method okay?

Let's say I have the following relationship in my Rails app:
class Parent < ActiveRecord::Base
has_many :kids
end
class Kid < ActiveRecord::Base
belongs_to :parent
end
I want parents to be able to see a list of their chatty kids, and use the count in paginating through that list. Here's a way to do that (I know it's a little odd, bear with me):
class Parent < ActiveRecord::Base
has_many :kids do
def for_chatting
proxy_association.owner.kids.where(:chatty => true)
end
end
end
But! Some parents have millions of kids, and p.kids.for_chatting.count takes too long to run, even with good database indexes. I'm pretty sure this cannot be directly fixed. But! I can set up a Parent#chatty_kids_count attribute and keep it correctly updated with database triggers. Then, I can:
class Parent < ActiveRecord::Base
has_many :kids do
def for_chatting
parent = proxy_association.owner
kid_assoc = parent.kids.where(:chatty => true)
def kid_assoc.count
parent.chatty_kids_count
end
end
end
end
And then parent.kids.for_chatting.count uses the cached count and my pagination is fast.
But! Overriding count() on singleton association objects makes the uh-oh, I am being way too clever part of my brain light up big-time.
I feel like there's a clearer way to approach this. Is there? Or is this a "yeah it's weird, leave a comment about why you're doing this and it'll be fine" kind of situation?
Edit:
I checked the code of will_paginate, seems like it is not using count method of AR relation, but i found that you can provide option total_entries for paginate
#kids = #parent.kids.for_chatting.paginate(
page: params[:page],
total_entries: parent.chatty_kids_count
)
This is not working
You can use wrapper for collection like here
https://github.com/kaminari/kaminari/pull/818#issuecomment-252788488​,
just override count method.
class RelationWrapper < SimpleDelegator
def initialize(relation, total_count)
super(relation)
#total_count = total_count
end
def count
#total_count
end
end
# in a controller:
relation = RelationWrapper.new(#parent.kids.for_chatting, parent.chatty_kids_count)

Ruby on rails associations parents

I would like to know, whatever the association is (simple belongs_to, polymorphic ...), when I make an association like :
class Toto < ActiveRecord::Base
belongs_to :test_one
belongs_to :test_two
end
class TestOne < ActiveRecord::Base
has_many :totos
end
class TestTwo < ActiveRecord::Base
has_many :totos
end
and then
test_one = TestOne.create
test_two = TestTwo.create
test1 = test_one.totos.create
test2 = test_two.totos.create
I would like to know into a callback of Toto what object instantiate me. In this case, it's obviously test_one and then test_two. I know I could check ids for example but the problem is when i do :
test3 = test_one.totos.create(test_two: test_two)
I can't know if test3 was created through test_one or test_two.
Thank you.
According to your example, I understand that you want to identify the type of object which is associated to your totos object (has_many :totos).
Since there are multiple different objects that might be associated to your totos object through the has_many and belongs_to associations, you might want to perform some kind of verification first to identify the type of the associated object.
First Answer:
This will only work if you know beforehand all the object types that has_many :totos
if test3.respond_to?(:test_one)
test = test3.test_one
elsif test3.respond_to?(:test_two)
test = test3.test_two
end
Second Answer:
I found this on Stackoverflow, and it somehow answeres your question. So if I rephrase the answer to:
def get_belongs_to(object)
associated = []
object.class.reflect_on_all_associations(:belongs_to).map do |reflection|
associated << object.try(reflection.name)
end
associated.compact
end
This method will return an array of all objects associated to your totos object. This will also work when totos belongs to multiple objects say test_one and test_two at the same time. So the following:
associated_objects = get_belongs_to(test3)
and in your case associated_objects[0] will yield the object you desire.
Hope this helps.
Rails does not persist the data you're looking for, so you'll have to store it yourself if you want it. This means you'll need a migration for the new field:
rails generate migration AddOriginalParentTypeToTotos original_parent_type:string
rake db:migrate
You can then override the assignment methods so that the first parent assigned will assign the original_parent_type attribute (and it will remain the same once assigned):
class Toto < ActiveRecord::Base
def test_one=(val)
self[:original_parent_type] ||= 'test_one'
super
end
def test_one_id=(val)
self[:original_parent_type] ||= 'test_one'
super
end
def test_two=(val)
self[:original_parent_type] ||= 'test_two'
super
end
def test_two_id=(val)
self[:original_parent_type] ||= 'test_two'
super
end
end
You can then use send to add an original_parent method:
class Toto < ActiveRecord::Base
def original_parent
send(original_parent_type) if original_parent_type
end
end

How to display this instance method from rails model?

I have two rails models, Usage & Price. So far they look like this:
class Usage < ActiveRecord::Base
has_one :price
def spend
usage.amount * price.amount
end
end
and
class Price < ActiveRecord::Base
belongs_to :usage
end
I'm trying to call the "spend" by doing this in the console:
Usage.create(amount:100)
Price.create(amount:0.1)
usage=Usage.find(1)
puts usage.price
Where am I going wrong??
You need to create the price through the usage model to have the association work.
usage = Usage.create(amount:100)
usage.create_price(amount:0.1)
As the price belongs to usage, you should first create the usage object, and then using that you can create the price object.
usage = Usage.create(amount:100)
price = usage.price.create(amount:0.1)
Then you will get that price related to the usage model.
Then in the usage model you can write,
class Usage < ActiveRecord::Base
has_one :price
def spend
self.amount * (self.price.amount)
end
end
You can call the association like above, self.price.amount where "self" is the Usage object.
The problem lay in two things I was doing. Thanks to blamattina for pointing out this is how you create new associated objects after they've been defined in the model:
u = Usage.create(amount:100)
u.create_price(amount:0.1)
Also, in the model itself, when referring to the parent class within a model's instance method, the class should be referred to as self:
def spend
self.amount * price.amount
end
That last bit was where I was going wrong, and spend can be easily called with u.spend!

What should be the model design of Categories that will have different logic?

I plan on giving different logic to different categories and I don't know if I should make 20 different models or if I can have just one StoreCategory model and put the logic in there. Some of the logic will be small and others large.
So If I had different store categories like discount, online, delivery, retail, etc, and wanted to give them their own special logic, what should I do?
It's a good question. In your place, I would create a class "Category" and my other classes inherit from the first. In the Category class I would put the shared code and the specific code in subclasses. You could use polymorphic associations if it's necessary. It's just an idea.
Edit 1 : Add code example
class StoreCategory < ActiveRecord::Base
before_save :something_private
def a_public_function
#...
end
protected
def a_protected_function
#...
end
private
def something_private
#something after save
end
end
class DiscountCategory < StoreCategory
def my_first_function
#I could use a_public_function and a_protected_function
end
end
class OnlineCategory < StoreCategory
def a_protected_function
#I could use a_public_function
#I could override a_protected_function
#I could use super to run the Category's function
end
end
the function something_private is called after the save of each subclasses. I think it's clear and organisated. It works like ApplicationController class.

ActiveRecord Always Run Some Code After Retrieval

I'm trying to always run some code after .find or .where or whatever is used to retrieve objects.
For example, the following describes what I want, but does not work
Class Person < ActiveRecord::BA
#mortality=true
end
I want #mortality=true to run whenever a Person object is created
And based on my current understanding of ORM/ActiveRecord, a new object is created whenever retrieval is done. Hopefully that is correct.
You want to do this in the after_initialize method:
class Person < ActiveRecord::Base
def after_initialize
#mortality = true
end
end
Note that this is something you should avoid doing if possible because it happens on every object, even when you retrieve enormous result sets.
In this (albeit simple) case, you can do the assignment lazily by overriding the getter:
class Person < ActiveRecord::Base
def mortality
#mortality.nil? ? true : #mortality
end
end
(you can't use the nil gate ||= here because it filters false values as well)
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Look for after_find and after_initialize callbacks.

Resources