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!
Related
I was recently working on a project where I faced a dilemma of choosing between two ways of getting same results. Here is the class structure:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
end
An author has first name, last name. I want to get the full name of the author for a given book as an instance method.
In simple active record terms, since book is associated with author, we can get the author name for a book as follows:
For example in Book class, we have:
class Book < ApplicationRecord
belongs_to :author
def author_name
"#{author.first_name} #{author.last_name}"
end
end
And we get the result!
But, according to the target of minimizing dependencies (POODR Book), future ease of change and better object oriented design, the book should not know properties of an author. It should interact with an author object by interfaces.
So Book should not be the one responsible for getting the Author name. The author class should.
class Book < ApplicationRecord
belongs_to :author
def author_name
get_author_name(self.author_id)
end
private
#minimizing class dependecies by providing private methods as external interfaces
def get_author_name(author_id)
Author.get_author_name_from_id(author_id)
end
end
class Author < ApplicationRecord
has_many :books
#class methods which provides a gate-way for other classes to communicate through interfaces, thus reducing coupling.
def self.get_author_name_from_id(id)
author = self.find_by_id(id)
author == nil ? "Author Record Not Found" : "#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
Now, book is just interacting with the public interface provided by Author and Author is handling the responsibility of getting full name from its properties which is a better design for sure.
I tried running the queries as two separate methods in my console:
class Book < ApplicationRecord
def author_name
get_author_name(self.author_id)
end
def author_name2
"#{author.last_name} + #{author.first_name}"
end
end
The results are shown below:
Looks like both run the same queries.
My questions are
Does rails convert author.last_name called inside the Book class to
the same SQL query as Author.find_by_id(author_id).last_name called inside
Author class (through message passing from Book class) in case of bigger data size?
Which one is more performant in case of bigger data size?
Doesn't calling author.last_name from Book class violates design
principles ?
It's actually much more common and simplier to use delegation.
class Book < ApplicationRecord
belongs_to :author
delegate :name, to: :author, prefix: true, allow_nil: true
end
class Author < ApplicationRecord
has_many :books
def name
"#{first_name.titleize} #(last_name.titleize}"
end
end
As to performance, if you join the authors at the time of the book query you end up doing a single query.
#books = Book.joins(:author)
Now when you iterate through #books and you call individually book.author_name no SQL query needs to be made to the authors table.
1) Obviously not, it performs JOIN of books & authors tables. What you've made requires 2 queries, instead of 1 join you'll have book.find(id) and author.find(book.author_id).
2) JOIN should be faster.
3) Since last_name is a public interface, it absolutely doesn't violate design principles. It would violate principles if you were accessing author's last name from outside like that: Book.find(1).author.last_name - that's a bad thing. Correct is: Book.find(1).authors_last_name - and accessing author's name inside Model class.
Your provided example seems to be overcomplicated to me.
According to the example you shared, you only want to get full name of the book's author. So, the idea of splitting responsibility is correct, but in Author class should be simple instance method full_name, like:
class Author < ApplicationRecord
has_many :books
def full_name
"#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
class Book < ActiveRecord::Base
belongs_to :author
def author_name
author.full_name
end
end
Note, there're no direct queries in this code. Once you'll need the author's name somewhere (in a view, in api response, etc), Rails will make the most optimized query possible (depends on your use case though, it may be ineffective for example, if you call iterate over books and call author in a loop)
I prefer the second approach because the full_name is property of author not a book. If the book wants to access that information, it can using book.author&.full_name (& is for handling cases of books with no authors).
but I would suggest a refactoring as below:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
def full_name
"#{firstname} #{lastname}"
end
end
Does rails convert author.last_name called inside the Book class to the same SQL query as Author.find_by_id(author_id).last_name called inside Author class (through message passing from Book class) in case of bigger data size?
Depend upon the calling factor, like in your example both will generate the same query. But if you have a include\join clause while getting the Book/Author, both will generate different queries.
As per the rails convention, Author.find_by_id(author_id).last_name is not recommended as it will always fire a query on database whenever the method is called. One should use the rails' association interface to call the method on related object which is smart to identify the object from memory or fetch it from database if not in memory.
Which one is more performant in case of bigger data size?
author.last_name is better because it will take care of joins, include, and memoization clauses if used and avoid the N+1 query problem.
Doesn't calling author.last_name from Book class violates design principles?
No, you can even use delegate like #Steve Suggested.
In my experience, it's a balancing act between minimizing code complexity and minimizing scalability issues.
However, in this case, I think the simplest solution that would separate class concerns and minimize code would be to simply use: #book.author.full_name
And in your Author.rb define full_name in Author.rb:
def full_name
"#{self.first_name} #{self.last_name}"
end
This will simplify your code a lot. For example, if in the future you had another model called Magazine that has an Author, you don't have to go define author_name in the Magazine model as well. You simply use #magazine.author.full_name. This will DRY up your code nicely.
I'm looking to create a callback where update a object if find the id attribute of another model.
in this case if find update Odata model if find the order_id.
someone know how to find the object based on another model id attribute?
class Order < ActiveRecord::Base
after_update :update_odata
def update_odata
order = Order.find_by_id(attributes['id'])
od = Odata.find_by_id(attributes['order_id'])
od.shipping_cost = order.shipping_cost
od.shipping_method = order.shipping_method
od.status = order.status
od.feedback_id = order.feedback_id
od.track_number = order.track_number
od.seller_name = order.seller_name
od.buyer_name = order.buyer_name
od.save
end
end
In general you should check the docs and at least make an effort to learn the tools you're using before resorting to asking for someone to help explain it to you on StackOverflow.
To answer your question, find(1) is effectively a shortcut method for find_by(id: 1). Thusly, if you want to find an order by customer_id you could do this: Order.find_by(customer_id: 42).
Or, if you're trying to make this contingent on order (making some assumptions based on how Rails apps are built vs this unusual attributes stuff you have in your example):
order = Order.find(params[:id])
od = Odata.find_by(order_id: order.id)
In which case, you should probably just use relations:
class Order < ApplicationRecord
has_one :odata
end
class Odata < ApplicationRecord
belongs_to :order
end
# controller:
order = Order.find params[:id]
od = order.odata
If you wanted to do exactly what you are above, which is probably a bad path to go down, you would probably want to do something like this:
class Order < ApplicationRecord
has_one :odata
def attributes_for_odata
%w{ shipping_cost shipping_method status feedback_id track_number seller_name buyer_name }
end
def update_order_data
odata.update attributes.slice(*attributes_for_odata)
end
end
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
I have a requirement where I need to calculate the average of units sold for a product based on the company they were sold at. From there I will calculate the percentage difference to unit sold. There is one model with products in it. Each product has the attributes of:
product_name
unit_sold
company
There are many companies.
This code works for calculating the average on all records, however I'd like to calculate the average conditionally based on the attribute 'company'.
def average_UnitSold
self.class.average(:unit_sold)
end
def averagechange_UnitSold
(self.unit_sold - average_UnitSold) / average_UnitSold * 100
end
I came up with this, but it is not working:
def average_UnitSold
self.class.sum(:unit_sold), :conditions => "company = self.company")) / :unit_sold
end
Any ideas?
On another note, is a more viable approach storing all these averages somewhere and only updating them on a daily basis more efficient?
Based on the answer, I have now implemented this code, and it seems to work:
def self.average_unit_sold(company)
where(company: company).average(:unit_sold)
end
def average_unit_sold
self.class.average_unit_sold(self.company)
end
def averagechange_UnitSold
(self.unit_sold - average_unit_sold) / average_unit_sold * 100
end
It's very strange that you're doing this in an instance method, since the result doesn't actually have anything to do with a particular instance. Instead, define a class method:
class Product < ActiveRecord::Base
# `self.average_unit_sold` is a class method that takes `company` as an
# argument and executes an SQL query like this (where 'some_company' is the
# company given in the argument):
#
# SELECT AVG(products.unit_sold) FROM products
# WHERE products.company = 'some_company'
#
def self.average_unit_sold(company)
where(company: company).average(:unit_sold)
end
end
# ...then...
Product.average_unit_sold(some_company)
If you really want to have an instance method, you can add one (but keep the logic in a class method):
# `average_unit_sold` is an instance method that takes the value of the
# instance's own `company` attribute and calls `Product.average_unit_sold`:
def average_unit_sold
self.class.average_unit_sold(self.company)
end
(This could also be a scope, but for aesthetic reasons I prefer to use scopes only when the result is a model instance or collection of instances, which isn't the case here.)
Assuming you have your associations set up correctly this is pretty easy to accomplish. So assuming that a Company has many products:
class Company < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :company
end
Average units sold for all companies:
Product.average(:unit_sold)
Average units sold for one company:
company = Company.find(1)
company.products.average(:unit_sold)
I'm studying Rails and am now trying to organize interaction among models. What I've written works, but I think that the code smells bad.
For example, I have two models with database tables Parcel and Warehouse. When I create a new Parcel, I want to increase the :current_weight of the Warehouse instance which is related to this new Parcel.
Again, everything works, but this type of code, interaction between two different objects, will be used frequently and something deep in my mind says: "Dude, this code sucks and will cause problems in the future!".
Maybe there are some good practices to organize or refactor it? Maybe it's
better to create a universal module for such interactions, or even create
method_missing logic to use methods with universal put_, remove_,
check_, like warehouse.put_parcel and warehouse.remove_parcel.
In ruby console:
parcel = Parcel.new
parcel.weight = 10
parcel.warehouse_id = 1
parcel.save
# Create parcel and increase :current_weight of related warehouse by 10 after save
warehouse.rb:
class Warehouse < ActiveRecord::Base
has_many :parcels
attr_accessible :name, :current_weight
end
parcel.rb:
class Parcel < ActiveRecord::Base
belongs_to :warehouse
belongs_to :vehicle
attr_accessible :name, :weight, :warehouse_id, :vehicle_id
after_save :set_current_weight
#Bad code:
def set_current_weight
#wh = self.warehouse
#wh.current_weight = #wh.current_weight + self.weight
#wh.save
end
end
How about
warehouse.parcels.sum(:weight)
That way you are running a 'live' query based on the current data, rather the incrementing.
Slightly more terse version of your current model too:
def set_current_weight
#wh = self.warehouse
#wh.current_weight += self.weight
#wh.save
end
The current_weight of the warehouse is really not part of a Parcel object mandate. You have also given it more than one reason to change. Thus, this breaks the single responsibility principle.
I would suggest removing :current_weight and set_current_weight altogether. Get the total weight inside warehouse like this:
def Warehouse < ActiveRecord::Base
has_many :parcels
# ...
def current_weight
parcels.sum(:weight)
end
end
As suggested by #muttonlamb in his post.