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 %>
Related
Model
class Assembly < ApplicationRecord
belongs_to :book
has_and_belongs_to_many :parts
end
Join table
class CreateJoinTableAssemblyPart < ActiveRecord::Migration[7.0]
def change
create_join_table :assemblies, :parts, id: false do |t|
t.index [:assembly_id, :part_id]
t.index [:part_id, :assembly_id]
end
end
end
Schema
create_table "assemblies_parts", id: false, force: :cascade do |t|
t.integer "assembly_id", null: false
t.integer "part_id", null: false
t.integer "assemblies"
t.integer "parts"
end
Controller
def create
#assembly = Assembly.new(assembly_params)
def assembly_params
params.require(:assembly).permit(:book_id, :part_id)
_Form.html
<div class="form-inputs">
<%= f.association :book %>
<%= f.association :parts %>
</div>
This way saves only the book_id
I need to save the part_id but it doesn't save and it doesn't even give an error
You need to whitelist the correct param key and an array of permitted scalar values - not a single value:
def assembly_params
params.require(:assembly)
.permit(:book_id, part_ids: [])
end
Check the logs for the correct param key to use.
Also remove the assemblies and parts columns from the assemblies_parts table. You don't need them and it just seems like an open invitation for bugs.
I have setup has_many and has_many :through association between a Order,User,Product and Order_detail model as a join table.
Models:
class Order < ActiveRecord::Base
has_many :order_details
belongs_to :user
has_many :products, through: :order_details
end
class OrderDetail < ActiveRecord::Base
belongs_to :order
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :order_details
has_many :orders, through: :order_details
end
class User < ActiveRecord::Base
has_many :orders
end
How to save automatically for join table order_details.
Now data save only to order table.
Need to save all products to order_tables for current order and user
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
#order = Order.all
end
def new
# #order = Order.new
#order = current_user.orders.new
end
def create
#order = current_user.orders.new(order_params)
#order.date = Date.today.to_s
if #order.save
# i think this is bad wrong to implementation of functional)
# params[:product_id].each do |detail_product_id|
# #order.order_details.product_id = detail_product_id
# #order.order_details.user_id = current_user
# #order.order_details.save
flash[:success] = "Order was successfully submitted"
redirect_to new_order_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:date, :product_id => [])
end
end
My schema:
create_table "order_details", force: true do |t|
t.integer "order_id"
t.integer "product_id"
t.integer "quantity"
t.integer "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "orders", force: true do |t|
t.integer "user_id"
t.date "date"
end
add_index "orders", ["user_id"], name: "index_orders_on_user_id", using: :btree
create_table "orders_products", id: false, force: true do |t|
t.integer "order_id"
t.integer "product_id"
end
create_table "products", force: true do |t|
t.string "product_name"
t.integer "product_price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "available_status"
t.string "product_type"
end
In your view, add fields for order_details like:
<%= f.fields_for :order_details do |od| %>
<%= od.label 'your attribute for OrderDetail' %>
<%= # od.text_field 'your attribute' %>
<% end %>
Then in your model, accept nested attributes for order_details
like:
accepts_nested_attributes_for :order_details
These are sample values, you can use this logic with your actual attributes.
In your controller, permit attributes for order_details like:
params.require(:order).permit(:id, :name, order_details: [
#attributes of order details
])
Assuming that product_ids is an array of the product ids that you wish to add to the order, you could try assigning them in the following way and Rails should automagically create those association records for order_details when you then call #order.save
#order.products << Product.find_by_id(product_ids)
Am i right add that rows for controller?
Order_controller:
def create
#order = current_user.orders.new(order_params)
#order.products = Product.find_by_id(order_params[:product_ids])
#order.date = Date.today.to_s
if #order.save
flash[:success] = "Order was successfully submitted"
redirect_to new_order_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:date, order_details: [:product_id])
end
end
order model:
accepts_nested_attributes_for :order_details
I'm trying to save from 3 different types of products.
But how to take one product id from each part ? Because now I can choose only one product from all
View:
= simple_form_for(#order, html: {:class => 'well form-horizontal', :method => :post, :action=> :create }) do |f|
.col-xs-12.col-sm-6.col-md-8
= render 'shared/error_messages', object: f.object
%br
= simple_fields_for :order_details do |od|
= od.collection_radio_buttons :product_ids, Product.get_first_course, :id, :product_name ,:item_wrapper_class => 'inline'
%hr
= od.collection_radio_buttons :product_ids, Product.get_main_course, :id, :product_name, :item_wrapper_class => 'inline'
%hr
= od.collection_radio_buttons :product_ids, Product.get_drink, :id, :product_name,:item_wrapper_class => 'inline'
%hr
= f.button :submit, class: "btn btn-primary"
I change my association type to HABTM and that's enough for my situation. So..
models:
class Order < ActiveRecord::Base
has_and_belongs_to_many :products
before_destroy { products.clear }
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :orders
end
Order_controller:
def create
order = current_user.orders.new(date: Date.today.to_s)
#order_products = Product.where(id: order_params[:product_ids])
order.products << #order_products
if order.save
#blalblal - sucsess
else
#blabla - alert-notice
end
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
I need assign to events. I have user edit with checkboxes, where i choose event and after click on save i'd like add insert to database with event_id and user_id. I don't know if is it good idea, but if somebody have better ideas please give me a piece of advice.
My view, On this moment i only know to which event i'm assign:
<% Event.all.each do |event| -%>
<tr>
<td><%= label_tag :event_ids, event.name -%></td>
<td><%= check_box_tag :event_ids, event.id, #user.event.include?(event), :name => 'user[event_ids][]' -%></td>
</tr>
<% end -%>
My schema:
create_table "users", force: true do |t|
t.string "name"
t.string "email"
end
create_table "events", force: true do |t|
t.string "name"
t.datetime "event_date"
t.string "organizator"
t.string "email"
end
create_table "bookings", force: true do |t|
t.integer "user_id"
t.integer "event_id"
end
My models:
class User < ActiveRecord::Base
has_many :bookings
has_many :events, :through => :bookings
class Booking < ActiveRecord::Base
belongs_to :user
belongs_to :event
class Event < ActiveRecord::Base
has_many :bookings
has_many :users, :through => :bookings
Where you have User > Booking > Event, I have Gallery > Entry > Photo.
In the controller for edit:
#photo_ids = []
#gallery.entries.each do |entry|
#photo_ids << entry.photo.id
end
The pertinent part of _form.html.haml is:
- #photos.in_groups_of(15, false) do |group|
%tr
- group.each do |photo|
%td
= label_tag("photo_ids_#{photo.id}", image_tag(photo.image.url(:thumb)))
= raw('<br/>')
= check_box_tag "photo_ids[]", photo.id, #photo_ids.include?(photo.id), :id => "photo_ids_${photo.id}"
And when saving in the controller:
if #gallery.save
photo_ids = params[:photo_ids]
photo_ids ||= []
#gallery.entries.each |entry|
entry_photo_id_s = entry.photo_id.to_s
# if already in gallery and checked, keep it, and remove from list to add
if photo_ids.include?(entry_photo_id_s)
photo_ids.delete(entry_photo_id_s)
else
entry.destroy
end
# if not already in gallery, add an entry
photo_ids.each do |photo_id|
entry = Entry.new
entry.gallery_id = #gallery.id
entry.photo_id = photo_id
entry.save
end
end
blah, blah, blah (render, redirect, whatever)
end
With a little translation of entities (and from HAML to ERB), that should work for your User > Booking > Event.
I have a categories table and a posts table. A post belongs to a category. I want to output a list of all categories with the latest post in that category.
I feel kind of silly, but I've been working on this for a few hours now. I've tried join and include when querying the categories, but I had problems limiting to only the latest post of each category.
I then tried to create my own hash or array, but I just kept running in to problems. So before I waste anymore time, I thought that the following would be the next cleanest way I can imagine it to work.
I would really appreciate any help I can get on how to achieve this.
The following is my code (stripped to the bare minimum).
db/schema.rb
ActiveRecord::Schema.define(:version => yyyymmddhhmmss) do
create_table "categories", :force => true do |t|
t.string "name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "posts", :force => true do |t|
t.string "title"
t.integer "category_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end
app/models/category.rb
class Category < ActiveRecord::Base
...
has_many :posts
...
end
app/models/post.rb
class Post < ActiveRecord::Base
...
belongs_to :category
...
end
app/controller/categories_controller.rb
class CategoriesController < ApplicationController
...
def index
#categories = Category.all
# The following loop is what my question is about
#categories.each do |c|
latest_post = Post.where(:category_id => c.id).order('published_at DESC').first
# "Inject" post.id and post.title in to the current #categories hash
end
end
...
end
app/views/categories/index.html.erb
<% #categories.each do |c| %>
...
<h4><%= c.name %></h4>
# The following line is how I envision the output to work
<p><%= c.post_title %></p>
...
<% end %>
raise #categories.to_yaml before
---
- !ruby/object:Category
attributes:
id: 1
name: General
created_at: 2013-01-10 22:08:57.291758000 Z
updated_at: 2013-01-10 22:09:02.414022000 Z
...
The following is hypothetical.
raise #categories.to_yaml after
---
- !ruby/object:Category
attributes:
id: 1
name: General
created_at: 2013-01-10 22:08:57.291758000 Z
updated_at: 2013-01-10 22:09:02.414022000 Z
post_id: 80
post_title: Lorem Ipsum
...
Create a one to one association first :
class Category
has_one :latest_post, :order => "created_at DESC", class_name => "Post"
end
and then eager load:
#categories = Category.includes(:latest_post).all
And... VoilĂ !