Can't understand how to sum items price checkout in RoR - ruby-on-rails

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

Related

Association between 2 tables doesn't work

I'm new to RoR and I want to create simple page like a task manager (to add and remove tasks) so I created 2 tables with association between them (Track and Item).
Here is 2 models:
class Item < ApplicationRecord
belongs_to :track, optional: :true
end
class Track < ApplicationRecord
has_many :items, dependent: :destroy
end
And I need to set association when I create or delete any track item. But when I create it I just see my track item (with an empty field in associated table)
For example:
rails c
Track.create(item: 'Asafa Pauel', description: 'This is a description') - works fine (added all field to db)
Item.all - track_id field is empty - but it should show id of track item. Why is this?
And my Tracks controller:
class TracksController < ApplicationController
def index
#track = Track.all
end
def show
#track = Track.all
end
def new
#track = Track.new
end
def create
#track = Track.new(track_params)
#item = Item.new(track_id: #track.id)
if #track.save! && #item.save!
flash[:success] = "It works!"
redirect_to tracks_path
else
flash[:success] = "Its wrong!"
end
end
private
def track_params
params.require(:track).permit(:item, :description)
end
end
And Items controller:
class ItemsController < ApplicationController
def create
#item = Item.new(item_params)
end
private
def item_params
params.require(:item).permit(:track_id)
end
end
And db schema:
ActiveRecord::Schema.define(version: 2019_05_23_112947) do
enable_extension "plpgsql"
create_table "items", force: :cascade do |t|
t.bigint "track_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["track_id"], name: "index_items_on_track_id"
end
create_table "tracks", force: :cascade do |t|
t.string "item"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Thanks in advance
Your new 'Track' object doesn't have an ID yet, so you can't assign its value to Item.track_id.
First you'll have to save the Track, then create a new Item.
Also, if you create a new Track from console, you won't trigger your "create" method in the controller: it will be called only if you create a new Track from browser.
If you want to create a new Item every time you create a Track, you'll have to do something like this in your model file "track.rb":
after_save :create_new_item
def create_new_item
self.items.create
end
P.S.: the "track.rb" file is in "app/models" in your Rails application.

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

Defining model methods in 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)

Rails undefined local variable or method `line_item'

Hello I have ran into a bit of trouble when create this conditional for a cart I built in my website to display a total when a part is on sale. In my schema I have parts, line_items which has an id of parts and carts, and carts. Parts have a attribute of discount. If a part has a discount it will display the discount and the new price of the part. My line_items has a method called line_item_discount which will create a new sum of the parts if one part includes a discount. Although it displays the part, the discount, and the new price, the cart total is not updating it.
I created a method called total_price_with_discount here
class Cart < ActiveRecord::Base
has_many :order_items
belongs_to :user
has_many :line_items, dependent: :destroy
def add_part(part_id)
current_part = line_items.find_by(part_id: part_id)
if current_part
current_part.quantity += 1
else
current_part = line_items.build(part_id: part_id)
end
current_part
end
def total_price
line_items.to_a.sum { |item| item.total_price}
end
def total_price_with_discount
line_items.to_a.sum { |item| item.total_price.line_item_discount}
end
Now where I am getting stuck is inside the _cart partial I tried to create a conditional where if a part has a discount it will use the total_price_with_discount method but if a part does not have a discount it will use the total_price. I have tried quite a few ways to create the conditional but i keep getting messages like this
for some reason carts has no instance of line_items or parts it appears.
Here are my tables for carts, parts, and line_items
create_table "carts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "quantity"
t.decimal "subtotal"
end
create_table "parts", force: :cascade do |t|
t.string "name"
t.text "description"
t.integer "category_id"
t.integer "price"
t.boolean "active"
t.integer "discount"
t.string "image"
t.integer "quantity"
end
create_table "line_items", force: :cascade do |t|
t.integer "part_id"
t.integer "cart_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity", default: 1
end
my parts model
class Part < ActiveRecord::Base
has_many :order_items
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item
def ensure_not_referenced_by_any_line_item
if line_items.empty?
return true
else
errors.add(:base, 'Line Items present')
return false
end
end
def subtotal
parts.collect { |part| part.valid? ? (part.quantity * part.unit_price) : 0}.sum
end
def apply_discount
price - (discount.to_f/100 * price)
end
end
my line_items model
class LineItem < ActiveRecord::Base
belongs_to :part
belongs_to :cart
def total_price
part.price * quantity
end
def line_item_discount
part.price - (part.discount.to_f/100 * part.price) * quantity
end
end
and here is the partial view thats throwing the error
<h2>Your Cart</h2> <table>
<%= render(cart.line_items) %>
<tr class="total_line">
<td colspan="2">Total</td>
<%unless cart.line_items.part.discount?%>
<td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
<%end%>
<%if cart.line_items.part.discount?%>
<td class="total_cell"><%= number_to_currency(cart.total_price_with_discount) %></td>
<%end%>
</tr>
</table>
<%= button_to 'Empty cart', cart, method: :delete, data: { confirm: 'Are you sure?' } %>
Thanks for any help and advice with this one
To the Cart model I would add:
has_many :parts, through: :line_items
Add a scope to the Part model:
scope :with_discounts, -> { where.not(discount: nil) }
Then change the view to:
<td class="total_cell">
<%if cart.parts.with_discount.any?%>
<%= number_to_currency(cart.total_price_with_discount) %>
<%else%>
<%= number_to_currency(cart.total_price) %>
<%end%>
</td>
Update: Instead of the above, I believe the code below is more efficient, but I'll present both options and let you pick.
Below we'll always use line_item_discount in the total_price method within the LineItem model:
def total_price
(part.price * quantity) - line_item_discount
end
def line_item_discount
return 0 if part.discount.blank?
(part.discount.to_f/100 * part.price) * quantity
end
Then you don't even need the if statement within the view, total_price will work either way:
<td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
You can then remove the total_price_with_discount method from the Cart model.
We can also tweak the total_price method within the Cart model:
(Works with either code choice)
def total_price
line_items.sum(:total_price)
end
In your view you're calling cart.line_items.part but line_items is a collection of multiple LineItem objects, .part is not a valid method on that collection object.
As you can see in your error, the part method is missing for ActiveRecord::Associations::CollectionProxy.
You should create a scope on LineItem like:
scope :with_discounted_part, { joins(:part).where.not(part: { discount: nil }) }
And then in your view you can do:
<% if cart.line_items.with_discounted_part %>

ActiveScaffold - changing default name of associated object

My model "combobox" has_many "comboboxselects", and "comboboxselects" belongs_to "combobox". Activescaffold of "comboboxes" displays data in comboboxselects-column like "#<Comboboxselect:0x472d25c>". How to make display the "answer" column from table "comboxselects"?
Models:
class Combobox < ActiveRecord::Base
has_many :comboboxselects
end
class Comboboxselect < ActiveRecord::Base
belongs_to :combobox
end
Schema:
create_table "comboboxes", :force => true do |t|
t.string "question"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "comboboxselects", :force => true do |t|
t.integer "combobox_id"
t.string "answer"
t.datetime "created_at"
t.datetime "updated_at"
end
Output:
class ComboboxesController < ApplicationController
active_scaffold :combobox do |config|
config.list.columns = [:id, :question]
config.columns = [:question, :comboboxselects]
end
end
class ComboboxselectsController < ApplicationController
active_scaffold :comboboxselect do |config|
config.list.columns = [:id, :combobox, :answer]
config.columns = [:answer]
end
end
First, all fields referenced in config.list.columns have to be included in config.columns (any explicitly-defined config.*.columns fields must be subsets of config.columns).
Then, in each model that does not already have a name or title field or method, you have to declare this custom method:
class Comboboxselect < ActiveRecord::Base
belongs_to :combobox
def to_label
"#{answer}"
end
end
See ActiveScaffold documentation: Describing Records: to_label
When you say displays I assume you mean in a view? Can you post the code your running to get that output.
Looks to me like you just have Comboboxselect object, have you tried adding .answer to it to access the attribute you want?

Resources