Defining model methods in Rails - ruby-on-rails

Currently I have an Order, OrderItems and Products model. I want to define a method called subtotal in OrderItems, which will return the value of the quantity times the price (through relationship product.price).
How could I accomplish that? I dont know how to access columns and columns through a relationship.
class OrderItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
validates :order_id, presence: true
validates :product_id, presence: true
def subtotal
quantity * product.price
end
end
Table schema
create_table "order_items", force: :cascade do |t|
t.integer "product_id"
t.integer "order_id"
t.integer "quantity"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Thanks for your help.

A little unsure about the relationship between OrderItems and Products but if you've done belongs_to: products in your models/OrderItems.rb, then you can simply do quantity * product.price

I think in your Order model you have a relationship as follows,
has_many :order_items
Hence, if you fetch an order row from the database then, in order to calculate the total you can use the following code.
Define a method called total_price in OrderItem class.
def total_price
tot_price = self.product.price * self.quantity
tot_price
end
then you can call it as follows
order = Order.first
total_price = order.order_items.sum(&:total_price)

Related

How to set an instance of a model as an attribute of another model in Rails?

I am fairly new to Rails and working on an app that will allow a user to make a List containing their top 5 Items of a certain category. The main issue I'm having is how to keep track of the List order (which should be allowed to change and will be different for each User)?
My Items can belong to many Lists and my Lists should have many Items so, as of now, I am using a has_and_belongs_to_many association for both my Item and List models.
My idea to keep track of the list order right now is to have my #list have 5 attributes: one for each ranking on the list (ie. :first, :second, :third, :fourth, :fifth) and I am attempting to associate the #item instance to the #list attribute (ie. #list.first = #item1, #list.second = #item2 , etc...). Right now I am saving the #list attribute to the #item ID (#list.first = 1), but I would prefer to be able to call the method .first or .second etc and have that point directly at the specific Item instance.
Here is my current schema for lists, items, and the join table list_nominations required for the has_and_belongs_to_many association-which I'm pretty sure I am not utilizing correctly (the :points attribute in items will be a way of keeping track of popularity of an item:
create_table "lists", force: :cascade do |t|
t.integer "user_id"
t.integer "category_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "first"
t.string "second"
t.string "third"
t.string "fourth"
t.string "fifth"
end
create_table "items", force: :cascade do |t|
t.string "name"
t.integer "category_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "points", default: 0
end
and here is the code currently in my List and Item models:
class List < ApplicationRecord
belongs_to :user
belongs_to :category
has_and_belongs_to_many :items
end
class Item < ApplicationRecord
belongs_to :category
has_and_belongs_to_many :lists
end
Is there a way to do this or any suggestions on a better way to keep track of the List order without creating multiple instances of the same Item?
I'm afraid your tables don't fit any known approach, you can achieve what you want but this is not a perfect nor a recommended solution, you could specify the primary key on many has_one associations inside lists but in items it's not very possible to have all lists in one association but you can have an instance method which query lists and returns the matched ones
the hacky solution:
class List < ApplicationRecord
belongs_to :user
belongs_to :category
has_one :first_item, primary_key: :first, class_name: "Item"
has_one :second_item, primary_key: :second, class_name: "Item"
has_one :third_item, primary_key: :third, class_name: "Item"
has_one :fourth_item, primary_key: :fourth, class_name: "Item"
has_one :fifth_item, primary_key: :fifth, class_name: "Item"
end
class Item < ApplicationRecord
belongs_to :category
def lists
List.where(
"first = ? OR second = ? OR third = ? OR fourth = ? OR fifth = ?", self.id, self.id, self.id, self.id, self.id
)
end
end
you can read about how to create a many-to-many relationship via has_and_belongs_to_many associations here: https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association (your tables will need a field to properly point to each other)
What I recommend doing is following a many-to-many through relationship guide (mono-transitive association) :
you will need 1 extra table because you want to track the order(first,second, etc)
DB:
create_table "lists", force: :cascade do |t|
.. all your other fields without first,second, etc..
end
create_table "items", force: :cascade do |t|
.. all your other fields
end
create_table "lists_items", force: :cascade do |t|
t.integer "list_id"
t.integer "item_id"
t.integer "rank" there is where you will store your order (first, second ..) bas as an integer
end
Models:
class ListsItem < ApplicationRecord
belongs_to :list
belongs_to :item
end
class List < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :lists_items, -> { order(:rank) }, limit: 5
has_many :items, through: :lists_items
end
class Item < ApplicationRecord
belongs_to :category
has_many :lists_items
has_many :lists, through: :lists_items
end
you can read more about many-to-many via has_many through here https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
and the difference between the 2 approaches here https://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many

How can I create a resource in Rails/SQL that belongs_to many different resources but does not require a foreign id for all of them?

Here is my recurring payment model. I want it to optionally belong to each of the included resources, but to allow creation with only one parent resource. I tried setting the default for each foreign key to 0, but I still get an error when I try to create instances saying "bank account must exist", "investment must exist", etc. I am sure there must be a way to accomplish this, but I cannot see how? *Edit: I solved this by defaulting the foreign id's to 1 instead of 0, since this will just be seed data anyway, but I would still love to know if anyone has a better solution!
class RecurringPayment < ApplicationRecord
belongs_to :bank_account
belongs_to :credit_card
belongs_to :investment
belongs_to :loan
def bank_account_name
self.try(:bank_account).try(:name)
end
def bank_account_name=(name)
bank_account = BankAccount.find_by(name: name)
if bank_account
self.bank_account = bank_account
end
end
def credit_card_provider
self.try(:credit_card).try(:provider)
end
def credit_card_provider=(provider)
credit_card = CreditCard.find_by(provider: provider)
if credit_card
self.credit_card = credit_card
end
end
def investment_name
self.try(:investment).try(:name)
end
def investment_name=(name)
investment = Investment.find_by(name: name)
if investment
self.investment = investment
end
end
def loan_name
self.try(:loan).try(:name)
end
def loan_name=(name)
loan = Loan.find_by(name: name)
if loan
self.loan = loan
end
end
end
Here is the schema:
create_table "recurring_payments", force: :cascade do |t|
t.string "source"
t.boolean "status"
t.date "pay_date"
t.integer "pay_amount"
t.integer "duration"
t.integer "bank_account_id", default: 0
t.integer "credit_card_id", default: 0
t.integer "loan_id", default: 0
t.integer "investment_id", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "payment_frequency"
end
In Rails 5, belongs_to associations are required by default.
You can change this behavior in a config file, by adding:
Rails.application.config.active_record.belongs_to_required_by_default = false
Or in a specific class:
class RecurringPayment < ApplicationRecord
belongs_to :bank_account, optional: true
belongs_to :credit_card, optional: true
belongs_to :investment, optional: true
belongs_to :loan, optional: true
end
In your code you should always check before using the association model, as it may not exist.
Check section 4.20 in this documentation: http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html

How to resolve associated models dropdown menu prob in active admin

I m getting unreadable values # dropdown menu in active admin.
I have an attribute which has a inclusion of specific values(around 10 values) but when I am making a new object of that class by using active admin... firstly it is showing the unreadable drop down menu
second its showing that attribute to be blank even if I m choosing some unreadable value..
plz help
my admin/Resident.rb page: :
ActiveAdmin.register Resident do
permit_params :room_number,:roll_number,:name,:hostel,:hostel_id
index do
column :room_number
column :roll_number
column :name
column :hostel
actions
end
filter :name,:as => :string
filter :hostel, :as => :select
filter :room_number
filter :roll_number
form do |f|
f.semantic_errors *f.object.errors.keys
inputs 'Enter the student details' do
input :room_number
input :roll_number
input :name
input :hostel
actions
end
end
end
I have two models : Hostel And Resident :
models/hostel.rb
class Hostel < ActiveRecord::Base
has_many :residents
end
models/resident.rb
class Resident < ActiveRecord::Base
belongs_to :hostel
validates :room_number,presence: true,uniqueness: {case_sensitive: false}
validates :roll_number,presence: true,uniqueness:{case_sensitive: false}
validates :name, presence: true,length:{ maximum: 50 }
validates :hostel,presence: true
def display_name
hostel
end
end
schema: :
Resident
create_table "residents", force: :cascade do |t|
t.string "room_number"
t.string "roll_number"
t.string "name"
t.string "hostel"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "hostel_id"
end
Hostel:
create_table "hostels", force: :cascade do |t|
t.string "hostel"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
For making the text readable, you can reimplement the "to_s" method :
class Hostel < ActiveRecord::Base
#...
def to_s
self.name
end
end
But active admin is smart enough to use the "name" row if existing usually. The bad side of this method is everywhere in your log etc. it will use the name. Something like [self.id,self.name].join('-') seems better for debugging if the name field of your table hostels is not unique.
Your second problem is caused by one of theses two things:
Check your params permits, and allow "hostel_id"
Ensure your <select name="model[hostel_id]" > and not <select name="model[hostel]">. This should be done automatically, if not it's probably because you didn't defined well the link has_many / belongs_to in your two models.
Finally, the last way is you can still push your own collection as parameter of the f.input into the active admin:
f.input :hostel_id, collection: Hostel.all.map{|x| [x.name, x.id]}
One more time, this should be done automatically. In your case it's not, so look closely your db schema.
Happy Coding,
Yacine.
class Hostel < ActiveRecord::Base
def display_name
name # or that code will return a representation of your Model instance
end
end
in place of name use the column name whose value u want to be displayed in drop down

Rails 4 - Complicated query between polymorphic associations

I have a Transaction model, which has a polymorphic association with both Product and Service.
This are the migrations:
create_table :products do |t|
t.string :name
t.timestamps null: false
end
create_table :services do |t|
t.string :name
t.integer :duration
t.timestamps null: false
end
create_table :transactions do |t|
t.integer :amount
t.decimal :price
t.references :sellable, polymorphic: true, index: true
t.references :bill, index: true, foreign_key: true
t.timestamps null: false
end
This are the models
class Transaction < ActiveRecord::Base
belongs_to :bill
belongs_to :sellable, polymorphic: true
def total; price * amount; end
end
class Product < ActiveRecord::Base
has_many :transactions, as: :sellable
end
class Service < ActiveRecord::Base
has_many :transactions, as: :sellable
end
Basically every transaction knows which items was sold, how many units, and how much each unit costed.
How do I get the top 5 object, by units sold?
How do I get the top 5 object, by units money brought-in?
Seems like the use case for custom queries/arel_table
Top 5 object, by units sold?
ANS:
SELECT *, SUM(amount) AS units FROM TRANSACTIONS GROUP BY transactions.sellable_id, transactions.sellable_type ORDER BY units desc LIMIT 5;
Please note that it would work fine in MySQL but might not work in other databases like Postgres. You need to limit the columns in SELECT clause to make it work with them
top 5 object, by units money brought-in
SELECT *, SUM(amount * price) AS money_brought_in FROM TRANSACTIONS
GROUP BY transactions.sellable_id, transactions.sellable_type
ORDER BY money_brought_in desc
LIMIT 5;
You can execute the raw query or using arel whichever you feel comfortable with and can then fetch the respective objects.
Just a rough idea. I hope it helps

Can't understand how to sum items price checkout in RoR

How can I get the grad_total of a item bases on the price times the quantity?
def grand_total
grand_total = 0
line_items.each do |item|
grand_total += item.quantity * item.phone.price
end
grand_total
end
If you have an active record object with multiple records, you'll want to try something like this.
class << self
def grand_total
self.phone.price * self.quantity
end
end
you would want to create a class in the db called cart.
This class would have information about the product.
create_table "cart", force: true do |t|
t.integer "product_id"
t.integer "quantity", default(1), not null
t.integer "user_id"
...
t.datetime "created_at"
t.datetime "updated_at"
end
now that we have a cart on this class we can now do something like this:
#cart.rb
class Cart < ActiveRecord::Base
belongs_to :product
belongs_to :user
...
def grand_total
product.price.to_f * quantity.to_i
end
end

Resources