Iterate over multiple joined tables to display items in view - ruby-on-rails

I have an Order model, a LineItem model and a Product model. Below are the associations. I'm trying to figure out how to access the name attribute in the Product model. My first thought was to iterate over the LineItem table to gather the line_items where order_id == order that is used in the show view. Then, iterate over the new line_items and get each product name from the product_id attribute of the line_item. This would have to be done in a method, probably in the Order model. Is there a better way this can be done using Rails associations? My current implementation results in undefined method product for #Order:0x007f5d007305f0
order.rb
class Order < ApplicationRecord
has_many :line_items
belongs_to :user
accepts_nested_attributes_for :line_items
after_validation :set_amount
private
def set_amount
self.amount = line_items.map(&:product).map(&:cost).inject(:+)
end
end
product.rb
class Product < ApplicationRecord
has_many :line_items
end
line_item.rb
class LineItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
order/show.html.erb
<h3>Customer - <%= #order.user.first_name %> <%= #order.user.last_name %></h3>
<h4>Order Date - <%= #order.created_at.to_s(:date_us) %></h4>
<%= #line_items.each do |item| %>
<table class="table">
<thead>
<tr>
<th>Product Quantity</th>
<th>Product Name</th>
<th class="text-right">Subtotal</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"></th>
<td><%= item.order.product.name %></td>
<td class="text-right"></td>
</tr>
</tbody>
</table>
<% end %>
<div class="text-right">
<h3><%= #order.amount %></h3>
</div>
orders_controller.rb
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
#orders = Order.all
end
def show
#order = Order.find(params[:id])
#line_items = LineItem.all
end
def new
#order = Order.new
#order.line_items.build
end
def create
#order = current_user.orders.build(order_params)
if #order.valid?
#order.save
redirect_to order_receipt_path(#order)
else
render :new
end
end
private
def order_params
params.require(:order).permit(line_items_attributes: [:id, :name, :product_id])
end
def products
#products ||= Product.all
end
helper_method :products
end

You receive the following error, because the Order model doesn't have project association, but the LineItem has.
You might want to filter the line items by order in the controller show method:
def show
#order = Order.find(params[:id])
#line_items = #order.line_items
end
And then iterate over them and display their project names:
<h3>Customer - <%= #order.user.first_name %> <%= #order.user.last_name %></h3>
<h4>Order Date - <%= #order.created_at.to_s(:date_us) %></h4>
<%= #line_items.each do |item| %>
<table class="table">
<thead>
<tr>
<th>Product Quantity</th>
<th>Product Name</th>
<th class="text-right">Subtotal</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"></th>
<td><%= item.product.name %></td>
<td class="text-right"></td>
</tr>
</tbody>
</table>
<% end %>
<div class="text-right">
<h3><%= #order.amount %></h3>
</div>

Order doesn't know product. You should call item.product.name instead of item.order.product.name

Also consider eager loading products to avoiding N+1 query problem. You can do this in the OrdersController#show action with: #order.line_items.includes(:product)

Related

Implement order pages with rails

I wanna implement a order page, but it's so hard...
The system is in portugues, for others reasons, sorry about this.
My view:
conta/pedidos/index.html.erb
<h3>Meus pedidos</h3>
<table>
<thead>
<th>#</th>
<th>Data do pedido</th>
</thead>
<tbody>
<% #pedidos.each do |pedido| %>
<tr>
<td><%= link_to pedido.id, pedido_path(pedido.token) %></td>
<td><%= pedido.create_at.to_s(:long) %></td>
</tr>
<% end %>
</tbody>
</table>
My controller:
conta/pedidos_controller.rb
class Conta::PedidosController < ApplicationController
before_action :authenticate_usuario!
def index
#pedidos = current_usuario.pedidos.order("id DESC")
end
end
My model:
pedido.rb
class Pedido < ActiveRecord::Base
belongs_to :pessoa
has_many :itens, class_name: "ItemPedido" , dependent: :destroy
accepts_nested_attributes_for :enderecos
before_create :gerar_token
def gerar_token
self.token = SecureRandom.uuid
end
end
And the error:
ArgumentError in Conta::PedidosController#index
No association found for name `enderecos'. Has it been defined yet?
Please, what I make?
I'm not sure why do you have accepts_nested_attributes_for :enderecos in pedido.rb. It's not mentioned anywhere in the provided code. Can you simple comment/remove it?
If it's need, then you need to set association for it: may be has_many :enderecos

Accessing data from tables

I'm trying to reference a column from another table in my rails application. I want to reference the site_name using the matching site_id columns from both tables(sites and trials).
My controller
def list
#list = Trial.year(params[:year]).order(:region_id)
end
My Models
class Trial < ActiveRecord::Base
attr_accessible :trial_id, :site_id, :year, :trial_type, :region_id
scope :year, ->(year) { where(year: year) }
scope :trial_id, ->(trial_id) { where(trial_id: trial_id) }
belongs_to :site
end
class Site < ActiveRecord::Base
attr_accessible :site_id, :site_name, :region
has_many :trials
end
My View
<table class="table">
<th>Trial Location</th>
<th>Trial Type</th>
<th>Grower</th>
<% #list.each do |list| %>
<tr>
<td>
<%= link_to list.site.site_name, trial_trials_path(trial_id: list.trial_id) %>
</td>
<td>
<%= link_to list.trial_type, trial_trials_path(trial_id: list.trial_id) unless list.trial_type.blank? %>
</td>
<td>
<%= link_to list.trial_type, trial_trials_path(trial_id: list.trial_id) unless list.trial_type.blank? %>
</td>
</tr>
<% end %>
</table>
Setting up the association in your model does write the getter method for you. The attr_accessibles sets up that method.
Add :site to your Trial's attr_accessible

show names instead of ids rails

Hello i created 2 tables (categories and employees),both are in a relationship and i want to show category_name(Categories table) in my employees index view instead of id's
****Here is my Categories Table******
class CreateCategories < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.string :category_name
end
end
def self.down
drop_table :categories
end
end
****Here is my Enployees Table******
class CreateEmployees < ActiveRecord::Migration
def self.up
create_table :employees do |t|
t.string :name
t.string :last_name
t.integer :categories_id
end
execute "ALTER TABLE employees ADD CONSTRAINT fk_employees_categories FOREIGN KEY (categories_id) REFERENCES categories (id)"
end
def self.down
drop_table :employees
end
end
****Here is my Employee controller******
class EmployeeController < ApplicationController
def index
#employees = Employee.all
end
end
****Here is my Category controller******
class CategoryController < ApplicationController
def index
#categories = Category.all
end
end
****Here is my Category Model******
class Category < ActiveRecord::Base
has_many :employees
end
****Here is my Employee Model******
class Employee < ActiveRecord::Base
belongs_to :category
end
****Here is Employee view*******
<table>
<tr>
<th>Employee</th>
<th>Last Name</th>
<th>Category</th>
</tr>
<% #employees.each do |e| %>
<tr>
<td><%=h e.name %></td>
<td><%=h e.last_name %></td>
<td><%=h e.categories_id %>
</td>
Here in e.categories_id i want to show categories_name that is from my categories table
<td>
<%= link_to ("View" ,:controller=>"employee",:action=>"show", :id=>e.id ) %>
</td>
<td>
<%= link_to ("Edit", :controller=>"employee",:action=>"edit",:id=>e.id ) %>
</td>
<td>
<%= link_to ( "Delete",:controller=>"employee",:action=>"destroy", :id=>e.id ,:confirm=>"sure?" %>
</td>
</tr>
<% end %>
</table>
Can someone help me with this problem please?
In your employee index views,
<td><%=employee.categories.category_name %>
instead of
<td><%=h e.categories_id %>
<%= h e.category.category_name %>
will do the trick.
Note that if you just do this, it will cause an SQL query to be executed for each row that you're displaying (known as the N+1 problem).
So in your controller, you'll have to change
#employees = Employee.all
to
#employees = Employee.find(:all, :include => [:category])
This will force eager loading, and reduce the number of queries to 2.
(As a side note, you really should have moved on to Rails 3 by now. Rails 4 is current. Each version offers a lot more features than the last.)
In Your employee controller add this
It means that get category_name to appear on your index view instead of ID.
#category= Category.all()
#category_list=[]
#category.each do |c|
#category_list << [c.category_name,c.id]
end
and then in your index views
<td><%=employee.categories.category_name %>
I know this is a late answer, but for anyone who is looking for an answer, here is how I accomplished this,
I have a table realties that has a one to many association with features table. I used the railscast method for multiple checkbox selection, so for each realty it has certain features. I wanted to show the features of each realty in its row in the index view. I had in the show view, features are shown like this,
#realty.feature_ids
where #realty in the controller is
#realty = Realty.find(params[:id])
however, in the index view the realty.feature_ids only showed numbers only not the names.
To work this out here is what I did in the index view:
<% #realties.each do |realty| %>
<td>
<% realty.feature_ids.each do |feature| %>
<%= Feature.find(feature).name %>
<% end %>
</td>
<% end %>
so for the above question, loop through the ids with each loop, this will give you the id number, find it in the Category model with the .name method. So I guess the solution for you would look like this:
<% #employees.each do |employee| %>
<td>
<% employee.category_ids.each do |category| %>
<%= Category.find(category).name %>
<% end %>
</td>
<% end %>
I hope this will benefit someone.

showing table twice in partial rails

Newbie to rails, I think i might be overlooking something very simple here, but I am displaying a table twice in a partial, not sure if it's to do with my associations.
Here is the Properties controller:
class PropertiesController < ApplicationController
before_filter
def index
#property= Property.all
end
def new
#property = current_user.property.build if signed_in?
end
def show
#property = current_user.property.paginate( params[:page])
end
Here is the Users Controllers:
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
authorize! :index, #user, :message => 'Not authorized as an administrator.'
#users = User.all
end
def show
#user = User.find(params[:id])
#property = #user.property.paginate(page: params[:page])
end
Here are the associations in the models:
user model:
class User < ActiveRecord::Base
has_many :property, dependent: :destroy
property:
class Property < ActiveRecord::Base
attr_accessible :address, :name
belongs_to :user
Here is the _property.html.erb partial
<li>
<table>
<tr>
<th>Name</th>
<th>address</th>
</tr>
<% #user.property.each do |property| %>
<tr>
<td><%= property.name %></td>
<td><%= property.address %></td>
</tr>
<% end %>
</table>
</li>
Here is the show.html.erb
<div class="row">
<aside class="span4">
<section>
<h1>
My Properties
</h1>
</section>
</aside>
<div class="span8">
<% if #user.property.any? %>
<h3>Properties (<%= #user.property.count %>)</h3>
<ol>
<%= render #property %>
</ol>
<%= will_paginate #property %>
<% end %>
</div>
</div>
This is what is rendered in the browser. http://i.imgur.com/SlilDo3.png
Let me know if there is anything else will be of help with this question. All responses appreciated.
Where you do #property = Property.all you are setting up a collection of instances of the Property class... there's clearly more than on in this collection.
When you use
render #property
it will render the _property template for every item in the collection called #property
Even if, inside the _property template you then use user.property.each - then that means that you're effectively saying:
for each property in #property, render the template _property... and
each time you do that, render a new table that does a table row for
each property in user.property.
if you want only one table, and only each row to be rendered per individual "property" in the list of properties that is called #property then you need to pull the table outside of the render.
eg:
<h3>Properties (<%= #user.property.count %>)</h3>
<table>
<tr>
<th>Name</th>
<th>address</th>
</tr>
<%= render #property %>
</table>
and _property:
<tr>
<td><%= property.name %></td>
<td><%= property.address %></td>
</tr>

Adding Cart items Ruby on Rails

I am developing a very basic shopping cart and the issue I am having is that if I add more than one of the same product to my shopping cart I am not seeing the quantity increase and am instead just seeing multiple versions of the item.
For example I see:
1 x green light = £15
1 x green light = £15
rather than seeing:
2 x green light = £30
How can I make it so that my shopping cart will check for multiple versions in the cart and then add them together?
Currently I have:
Application Controller
def current_cart
if session[:cart_id]
#current_cart ||= Cart.find(session[:cart_id])
end
if session[:cart_id].nil?
#current_cart = Cart.create!
session[:cart_id] = #current_cart.id
end
#current_cart
end
Cart Controller
def show
#cart = current_cart
end
Cart Model
has_many :items
def total_price
items.to_a.sum(&:full_price)
end
Cart View
<table id="line_items">
<tr>
<th>Product</th>
<th>Qty</th>
<th class="price">Unit Price</th>
<th class="price">Full Price</th>
</tr>
<% for item in #cart.items %>
<tr class="<%= cycle :odd, :even %>">
<td><%=h item.product.name %></td>
<td class="qty"><%= item.quantity %></td>
<td class="price"><%= gbp(item.unit_price) %></td>
<td class="price"><%= gbp(item.full_price) %></td>
</tr>
<% end %>
<tr>
<td class="total price" colspan="4">
Total: <%= gbp(#cart.total_price) %>
</td>
</tr>
</table>
FURTHER INFORMATION
Products#Index
<%= link_to "Add to Cart", line_items_path(:product_id => product), :method => :post %>
Any advice people can offer would be much appreciated. Thanks!
New Setup - Causing Error Uninitialised Constant CartController
Routes.rb
Checkout::Application.routes.draw do
ActiveAdmin.routes(self)
devise_for :admin_users, ActiveAdmin::Devise.config
post '/add_to_cart/:product_id' => 'cart#add_to_cart', :as => 'add_to_cart'
resources :carts
resources :products
resources :items
root :to => 'products#index'
end
Carts Controller
class CartsController < ApplicationController
def show
#cart = current_cart
end
def add_to_cart
current_cart.add_item(params[:product_id])
redirect_to carts_path(current_cart.id)
end
end
Carts Model
class Cart < ActiveRecord::Base
has_many :items
def add_item(product_id)
item = items.where('product_id = ?', product_id).first
if item
# increase the quantity of product in cart
item.quantity + 1
save
else
# product does not exist in cart
product = Product.find(product_id)
items << product
end
save
end
def total_price
items.to_a.sum(&:full_price)
end
end
Product#Index
<table class="jobs">
<thead>
<tr>
<th scope="col" id="name">Product Code</th>
<th scope="col" id="company">Name</th>
<th scope="col" id="company">Price</th>
<th scope="col" id="company">Action</th>
</tr>
</thead>
<tbody>
<% #product.each do |product| %>
<tr>
<td><%= product.product_code %></td>
<td><%= product.name %></td>
<td><%= gbp(product.price) %></td>
<td><%= button_to "Add to Cart", add_to_cart_path(:product_id => product), :method => :post %></td>
</tr>
<% end %>
</tbody>
</table>
In your Cart model, create a method called
def add_item(product_id)
item = items.where('product_id = ?', product_id).first
if item
# increase the quantity of product in cart
item.quantity + 1
save
else
# product does not exist in cart
cart.items << Item.new(product_id: product_id, quantity: 1)
end
save
end
In routes.rb,
post '/add_to_cart/:product_id' => 'cart#add_to_cart', :as => 'add_to_cart'
Change your Add to Cart route to a call add_to_cart method in the Cart controller.
def add_to_cart
current_cart.add_item(params[:product_id])
# redirect to shopping cart or whereever
end
This should give you the idea of what you are wanting to accomplish.

Resources