Rails 4 - Displaying items from database tables - ruby-on-rails

I have the following models and their associations:
#models
class Order < ActiveRecord::Base
has_many :order_items
has_many :items, :through => :order_items
accepts_nested_attributes_for :items, :order_items, :allow_destroy => true
end
class Item < ActiveRecord::Base
has_many :order_items
has_many :orders, :through => :order_items
end
class OrderItem < ActiveRecord::Base
belongs_to :item
belongs_to :order
end
I want to display the item.name, order_item.quantity and order_item.price in one table as attempted below:
<tbody>
<% #order.items.each do |item| %> <<<<<<<< need this to call item.name
<% #order.order_items.each do |order_item| %> <<<<<<< need this to call the other fields
<tr>
<td><%= item.name %></td>
<td><%= order_item.quantity %></td>
<td><%= order_item.price %></td>
</tr>
<% end %>
<% end %>
</tbody>
the above works in calling the specific field but how it is written will not do because inner loop needs to finish before outer and hence we don't get what we need.
#Tables snippets
create_table "items", force: true do |t|
t.string "name"
t.decimal "price"
t.integer "stock"
t.string "location"
t.decimal "discount"
t.boolean "status"
end
create_table "order_items", force: true do |t|
t.integer "item_id"
t.integer "order_id"
t.integer "quantity"
t.decimal "price"
end
create_table "orders", force: true do |t|
t.string "code"
t.integer "user_id"
t.text "memo"
t.boolean "status"
t.integer "client_id"
t.decimal "sub_total"
end
#orders form
<%= compact_form_for(#order) do |f| %>
<%= f.association :client, collection: Client.all, label_method: :name, value_method: :id, prompt: "Client Name", required: true %>
<%= f.simple_fields_for :items do |o| %>
<%= o.input :name, collection: Product.all, label_method: :name, value_method: :id %>
<%= o.input :quantity %>
<% end %>
<%= f.button :submit %>
<% end %>

You may wish to use the .delegate method with the OrderItem model, like this:
#app/models/order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :item
belongs_to :order
delegate :name, to: :item #-> allows you to call #order_item.name
end
This will allow you to call:
<tbody>
<% #order.order_items.each do |item| %>
<tr>
<td><%= item.name %></td>
<td><%= item.quantity %></td>
<td><%= item.price %></td>
</tr>
<% end %>
</tbody>
Fix
A much better way to make this work will be to use the following model names:
#app/models/product.rb
class Product < ActiveRecord::Base
has_many :items
has_many :orders, :through => :items
end
#app/models/order.rb
class Order < ActiveRecord::Base
has_many :items
has_many :products, :through => :items
accepts_nested_attributes_for :products, :allow_destroy => true
end
#app/models/item.rb
class Item < ActiveRecord::Base
belongs_to :product
belongs_to :order
delegate :name, to: :product #-> allows you to call #item.name
end
This will allow you to call:
<tbody>
<% #order.items.each do |item| %>
<tr>
<td><%= item.name %></td>
<td><%= item.quantity %></td>
<td><%= item.price %></td>
</tr>
<% end %>
</tbody>

Related

Nested forms in Ruby with checkboxes and many-to-many relationship

I am new to ruby and having a trouble in following scenario. I am trying to build a relationship between bill and items. In my case, I want to generate a bill at run time like when user clicks on create new bill, he is directed to a route like http://localhost:3000/bills/new and then he has a list of items from which he has to choose by checking the checkboxes and adding the quantity. I have 3 tables, Items, Bills, BillItems. They have following fields in them:
create_table "bill_items", force: :cascade do |t|
t.integer "bill_id"
t.integer "item_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "quantity"
end
create_table "bills", force: :cascade do |t|
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "items", force: :cascade do |t|
t.string "name"
t.float "price"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "category_id"
end
I have my models created like this:
Bill.rb
class Bill < ApplicationRecord
has_many :bill_items
has_many :items, through: :bill_items
accepts_nested_attributes_for :bill_items, :allow_destroy => true, :reject_if => :all_blank
end
Item.rb
class Item < ApplicationRecord
has_many :bill_items
has_many :bills, through: :bill_items
end
BillItem.rb
class BillItem < ApplicationRecord
belongs_to :bill
belongs_to :item
end
I have my form like:
<%= form_for #bill do |f| %>
<% if #allItems %>
<% #allItems.each_with_index do |item, index| %>
<tr class="table-success" scope="col-8">
<%= f.fields_for :bill_items do |s| %>
<td class="text-secondary"><%= item.category.name %></td>
<%= s.hidden_field :name, value: item.name %>
<td class="text-primary"><%= s.label item.name %></td>
<td><%= check_box_tag "item_ids[]", item.id, false, class: 'selectable' %> </td>
<td><%= s.number_field(:quantity, in: 1.0..100.0, step: 1) %></td>
<td><%= s.label :price, item.price %></td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<div class="form-group row justify-content-center">
<%= f.submit "Create Order with Selected items", class: "btn btn-secondary" %>
</div>
<% end %>
Then I have my controller setup like this:
def new
#bill = Bill.new
#bill_items = #bill.bill_items.build
end
def create
byebug
#bill = Bill.new(bill_params)
#bill.save
redirect_to new_bill_path
end
private
def bill_params
params.require(:bill).permit(bill_items_attributes: [:quantity, :item_ids])
end
When I run my code and send data to form and check params via byebug it shows me following params, while i selected two items, of ids 1 and 4:
<ActionController::Parameters {"authenticity_token"=>"hVnrTkWxWwuXqS4tb01INVkNwRaFooVERKe2L8YkXykyPqImKCVRrvqjhK8sA0Q26nsOS+dSNdLvIOPTfis8nQ==", "bill"=>{"bill_items_attributes"=>{"0"=>{"name"=>"sheer", "quantity"=>"2"}, "1"=>{"name"=>"burger", "quantity"=>""}, "2"=>{"name"=>"custurs", "quantity"=>""}, "3"=>{"name"=>"sib", "quantity"=>"4"}}}, "item_ids"=>["1", "4"], "commit"=>"Create Order with Selected items", "controller"=>"bills", "action"=>"create"} permitted: false>
Then I click submit and it only saves bill in the db and gives me error
Unpermitted parameter: :name
Unpermitted parameter: :name
Unpermitted parameter: :name
Unpermitted parameter: :name`
I have tried many techniques and couldn't find a solution. It will be very helpful if someone can help me with this. Even if I need to redesign my logic then do help me with this. Thanks.
<td><%= check_box_tag "item_ids[]", item.id, false, class: 'selectable' %> </td>
has to be
<td><%= check_box_tag "bill[item_ids[]]", item.id, false, class: 'selectable' %> </td>
Problem: There is a mismatch in nested attributes naming
Bill model is accepting nested attributes for :items
On Controller you have specified :bill_items_attributes and
Form generating fields for bill_items -
f.fields_for :bill_items
Solution: make it consistent
On Bill model -
accepts_nested_attributes_for :items
On Bill Controller -
permit(items_attributes:
On form new.html.erb -
f.fields_for :items
However this will create another problem for items_id, which I am not sure what are trying to achieve there.
You were getting those errors because you had hidden_field :name in form which is not required while submitting form. I removed that. However there was an issue with strong parameters and checkbox naming. I corrected strong params and kept checkbox naming descriptive so that you can see how form will generates it.
Try this code. I was able to create records with this code.
bills_controller.rb
private
def bill_params
params.require(:bill).permit(bill_items_attributes: [:quantity, :item_id])
end
new.html.erb
<%= form_for #bill do |f| %>
<% if #allItems %>
<% #allItems.each_with_index do |item, index| %>
<%= f.fields_for :bill_items do |s| %>
<tr class="table-success" scope="col-8">
<td class="text-primary"><%= s.label item.name %></td>
<td><%= check_box_tag "bill[bill_items_attributes][#{index}][item_id]", item.id, false, class: 'selectable' %> </td>
<td><%= s.number_field(:quantity, in: 1.0..100.0, step: 1) %></td>
<td><%= s.label :price, item.price %></td>
</tr>
<% end %>
<% end %>
<% end %>
<div class="form-group row justify-content-center">
<%= f.submit "Create Order with Selected items", class: "btn btn-secondary" %>
</div>
<% end %>

HABTM - add and remove buttons (on the show view)

I am displaying an invoice, to which I want to add 'fly' products. I want to do this using a drop down box for the products along with an add button. Once a product has been added to the invoice I want to be able to click a delete button to remove it.
I currently have this working through the console, but am not sure how to do this on the front end.
How it is set up::
User Model:
class User < ActiveRecord::Base
....
has_many :invoices
....
end
Invoice Model:
class Invoice < ActiveRecord::Base
attr_accessible :active
validates :user_id, presence: true
belongs_to :user
has_many :categorizations
has_many :flies, through: :categorizations
end
Invoice migration:
class CreateInvoices < ActiveRecord::Migration
def change
create_table :invoices do |t|
t.boolean :active
t.integer :user_id
t.timestamps
end
add_index :invoices, :user_id
end
end
Categorization Model:
class Categorization < ActiveRecord::Base
attr_accessible :fly_id, :user_id
belongs_to :invoice
belongs_to :fly
end
Categorization migration:
class CreateCategorizations < ActiveRecord::Migration
def change
create_table :categorizations do |t|
t.integer :user_id
t.integer :fly_id
t.timestamps
add_index :categorizations, :user_id
add_index :categorizations, :fly_id
end
end
end
Fly Model:
class Fly < ActiveRecord::Base
attr_accessible :description, :name
validates :description, :name, presence: true
has_many :categorizations
has_many :invoices, through: :categorizations
end
Fly migration:
class CreateFlies < ActiveRecord::Migration
def change
create_table :flies do |t|
t.string :name
t.string :description
t.timestamps
end
end
end
Show invoice view:
<h3>Invoice</h3>
<p>User Name:
<%= #invoice.user.name %></p>
<p>
Invoice ID:
<%= #invoice.id %></p>
<p>
Invoice Active?:
<%= check_box_tag 'admin', '1', #invoice.active, :disabled => true %></p>
<p>Email:
<%= #invoice.user.email if #invoice.user.email %></p>
<table class="table table-condensed">
<thead>
<tr>
<th>Invoice Flies</th>
</thead>
<tbody>
<% #invoice.flies.each do |fly| %>
<tr>
<td><%= fly.name %></td>
</tr>
<% end %>
</tbody>
</table>
<%= simple_form_for(#categorization) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
#this is where I want to add my 'add product to invoice' functionality
<%= f.submit "Add Fly to Invoice", class: "btn btn-large btn-primary" %>
<% end %>
<%= button_to "Mark as Sent", {:controller => :invoices, :action => :activate, :id => #invoice.id }, {:method => :post } %>
<%= button_to "Mark as not sent", {:controller => :invoices, :action => :deactivate, :id => #invoice.id }, {:method => :post } %>
<br><br>
<%= link_to "Back to list of invoices", invoices_path %>
There's two good gems for the purpose:
https://github.com/nathanvda/cocoon
https://github.com/ryanb/nested_form

Displaying an image associated with an artist

I am trying to display an image that is in my public/image folder and I
don't know how to.
What I want to do is to be able to assign a photo to an artist so you
can navigate through different artist's photo.
This is my image model
class Image < ActiveRecord::Base
has_many :publishings
has_many :artists, :through => :publishings
has_many :comments,:through => :friends
has_many :comments, :as => :resource, :class_name => "Commentable"
end
This is my image show.html
<p id="notice"><%= notice %></p>
<p>
<b>Title:</b>
<%= #image.title %>
</p>
<p>
<b>Filename:</b>
<%= #image.filename %>
</p>
<p>
<b>Likes:</b>
<%= #image.likes %>
</p>
<%= link_to 'Edit', edit_image_path(#image) %> |
<%= link_to 'Back', images_path %>
This is the database for the images
class CreateImages < ActiveRecord::Migration
def self.up
create_table :images do |t|
t.string :title :null => false
t.string :filename :null =>false
t.integer :likes :default =>0
t.timestamps
end
end
def self.down
drop_table :images
end
end
Thanks in advance
<%= image_tag #image.filename %>

How to delete asset in nested_form through carrierwave?

Good day.
Has somebody working solution for deleting asset in nested form in Carrierwave?
MODEL
has_many :article_images, :dependent => :destroy
accepts_nested_attributes_for :article_images
mount_uploader :image, ImageUploader
belongs_to :article, :polymorphic => true
schema.rb
create_table "article_images", :force => true do |t|
t.string "image"
t.string "article_id"
end
create_table "articles", :force => true do |t|
t.string "title"
end
CONTROLLER
def edit
#article = Article.find(params[:id])
#article.article_images.build
end
VIEW
_form.html.erb
<%= f.fields_for :article_images do |article_image| %>
<% if article_image.object.new_record? %>
<%= article_image.file_field :image %>
<% else %>
<%= image_tag(article_image.object.image.url(:thumb)) %>
<%= article_image.check_box :remove_image %> #DON'T WORK
<% end %>
<% end %>
I think it's better if you do this:
class ArticleImage < ActiveRecord::Base
# ...
attr_accessible :remove_image
after_save :clean_remove_image
def changed?
!!(super || remove_image)
end
def clean_remove_image
self.remove_image = nil
end
end
It worked for me.
What happens if you add this to your accepts_nested_attributes_for in your model:
accepts_nested_attributes_for :article_images, :allow_destroy => true
and change this in your view code:
<%= article_image.check_box :remove_image %> #DON'T WORK
To this:
<%= article_image.check_box :_destroy %> #MIGHT WORK?

collection_select selected value

i have two identical collection_selects on one page (one message belonging to 2 groups)
<%=
collection_select(:message,:group_ids, Group.find(:all),:id, :title, {}, {:name=>'message[group_ids][]'} )
%>
<%=
collection_select(:message,:group_ids, Group.find(:all),:id, :title, {}, {:name=>'message[group_ids][]'} )
%>
is it possible to set two different selected values for them using collection_select?
edit:
i guess i'd have to do something like
<%
#message.group_id=5
%>
<%=
collection_select(:message,:group_id, Group.find(:all),:id, :title, {}, {:name=>'message[group_ids][]'} )
%>
<%
#message.group_id=6
%>
<%=
collection_select(:message,:group_id, Group.find(:all),:id, :title, {}, {:name=>'message[group_ids][]'} )
%>
but of course it doesn't work and gives method missing error
edit2:
guess there is no way to do it with collection_select. unless group has a method, returning single group_id each time.
what i ended up with is
select_tag 'message[group_ids][]', "<option></option>"+options_from_collection_for_select(Group.find(:all), 'id', 'title',group1.id)
select_tag 'message[group_ids][]', "<option></option>"+options_from_collection_for_select(Group.find(:all), 'id', 'title',group2.id)
You need to set up your models and relationships like so:
class Message < ActiveRecord::Base
has_many :message_groups
has_many :groups, :through => :message_groups
accepts_nested_attributes_for :message_groups #Note this here!
end
class Group < ActiveRecord::Base
has_many :message_groups
has_many :messages, :through => :message_groups
end
class MessageGroup < ActiveRecord::Base
belongs_to :group
belongs_to :message
end
Then in your form...
<% form_for(#message) do |f| %>
<%= f.error_messages %>
<% f.fields_for :message_groups do |g| %>
<p>
<%= g.label :group_id, "Group" %>
<%= g.select :group_id, Group.find(:all).collect {|g| [ g.title, g.id ] } %>
</p>
<% end %>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>
And here's my migrations for completeness
class CreateGroups < ActiveRecord::Migration
def self.up
create_table :groups do |t|
t.string :title
t.timestamps
end
end
def self.down
drop_table :groups
end
end
class CreateMessages < ActiveRecord::Migration
def self.up
create_table :messages do |t|
t.text :body
t.timestamps
end
end
def self.down
drop_table :messages
end
end
class CreateMessageGroups < ActiveRecord::Migration
def self.up
create_table :message_groups do |t|
t.integer :message_id
t.integer :group_id
t.timestamps
end
end
def self.down
drop_table :message_groups
end
end
Hope this helps...!

Resources