I'm building an invoice system for a car trader where each invoice is linked to one customer and one vehicle, with customers potentially having many invoices and vehicles also having many invoices. I have got it working with one nested model doing the following:
purchase_invoice.rb
class PurchaseInvoice < ApplicationRecord
belongs_to :vehicle
accepts_nested_attributes_for :vehicle
end
vehicle.rb
class Vehicle < ApplicationRecord
has_many :purchase_invoices
end
purchase_invoices_controller.rb
def new
#vehicle = Vehicle.new
#purchase_invoice = #vehicle.purchase_invoices.build
end
def create
#vehicle = Vehicle.new
#purchase_invoice = #vehicle.purchase_invoices.build(invoice_params)
if #purchase_invoice.save
redirect_to #purchase_invoice
else
render 'new'
end
end
private
def invoice_params
params.require(:purchase_invoice).permit(:buyer, :location, :vehicle_price, :transfer_fee, :balance_due, :payment_cash, :payment_bank_transfer, :payment_comment, :status, vehicle_attributes: [:vrm, :date_first_registered, :make, :model, :colour, :transmission, :vin, :fuel, :power])
end
new.html.erb
<%= form_with model: #purchase_invoice, local: true do |form| %>
<%= form.fields_for #vehicle do |vehicle_form| %>
<% end %>
<% end %>
However when I add a second relationship like this:
purchase_invoice.rb
class PurchaseInvoice < ApplicationRecord
belongs_to :customer
belongs_to :vehicle
accepts_nested_attributes_for :customer
accepts_nested_attributes_for :vehicle
end
I get an error saying :'Unpermitted parameters: :vehicle'
Does anybody know why? Also, how would I modify the controller new/create action for build whilst maintaining strong params?
I've been Googling this for four hours now and tried a lot but had no luck. Thanks in advance to everybody!
Update
Here's my logs:
Started POST "/purchase_invoices" for 127.0.0.1 at 2018-03-20 15:10:01 +0000
Processing by PurchaseInvoicesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"JB8py9zNxew6aQ6/za3JHDEb4j8f9HGujTlS6P1Eyhb+5NtPPP47fW7AHBkt9eURcnXg0gh9Mf1DCKCSwvlAbg==", "purchase_invoice"=>{"customer"=>{"name"=>""}, "vehicle"=>{"vrm"=>"SA07SSX", "make"=>"VAUXHALL", "model"=>"MERIVA DESIGN", "colour"=>"Silver", "vin"=>"W0L0XCE7574216645", "date_first_registered"=>"20/03/2007"}, "vehicle_odomoter_reading"=>"", "vehicle_number_of_keys"=>"", "vehicle_mot_expiry"=>"", "vehicle_hpi_clear"=>"", "vehicle_comments"=>"", "buyer"=>"", "location"=>"", "vehicle_price"=>"", "transfer_fee"=>"0", "balance_due"=>"", "payment_cash"=>"", "payment_bank_transfer"=>"", "payment_comments"=>""}, "commit"=>"Create Purchase invoice"}
Vehicle Load (0.3ms) SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."vrm" = ? ORDER BY "vehicles"."id" ASC LIMIT ? [["vrm", "SA07SSX"], ["LIMIT", 1]]
Unpermitted parameters: :customer, :vehicle, :payment_comments
(0.1ms) begin transaction
(0.1ms) rollback transaction
Rendering purchase_invoices/new.html.erb within layouts/application
Rendered purchase_invoices/new.html.erb within layouts/application (10.2ms)
Rendered layouts/_header.html.erb (1.7ms)
Completed 200 OK in 63ms (Views: 54.4ms | ActiveRecord: 0.4ms)
You approach has some issues but you are very close. The PurchaseInvoice model belongs to a Vehicle and a Customer, and it stores a customer_id and a vehicle_id. That's correct. As you said in the comment, it is much more than just a join model, because it holds many other data, such as price, transfer fees, etc. Anyway, you are passing a lot of params regarding the vehicle to be purchased, and not the id of the vehicle. In fact, you are creating the vehicle in the PurchaseInvoice create, which makes no sense. Moreover, your vehicle_attributes should not be an array, but a hash (because PurchaseInvoice belongs_to :vehicle; so it is just one), and should only have the vehicle_id. And giving that you just need the vehicle_id, you do not need nested_attributes (neither for customer) . I would change:
The controller:
def new
#vehicles = Vehicle.all #All vehicles, to select from the list
#customers = Customer.all #All customers, to select from the list (unless you use current_user)
#purchase_invoice = PurchaseInvoice.new
end
def create
#vehicle = Vehicle.find(params[:vehicle_id])
#customer = Customer.find(params[:customer_id]) # Or use current_user
#The above is only needed to check if vehicle and customer exist, but it is not needed below
#purchase_invoice = PurchaseInvoice.create(invoice_params)
if #purchase_invoice.save
redirect_to #purchase_invoice
else
render 'new'
end
end
def invoice_params
params.require(:purchase_invoice).permit(:customer_id, vehicle_id, :location, :vehicle_price, :transfer_fee, :balance_due, :payment_cash, :payment_bank_transfer, :payment_comment, :status)
end
The view:
<%= form_with model: #purchase_invoice, local: true do |form| %>
<!-- This is necessary to choose the customer. If the customer is current_user, just remove it. -->
<%= form.collection_select(:customer_id, #customers, :id, :name %>
<!-- This is necessary to choose the vehicle. This is extremely simplified -->
<!-- The customer should be able to look at many attributes of the car -->
<!-- to be able to select one to purchase -->
<%= form.collection_select(:vehicle_id, #vehicles, :id, :make %>
<!-- All other invoice fields. -->
<% end %>
Related
I have a marketplace where my users can create plans and their customers can join them. So I have a Plan model and a Customer model. The end goal is to subscribe a customer to a plan so I created a Subscription model and a has_many :through association but I need some help on getting the create working properly. A plan and a customer are already existing by the time the subscription is able to happen so I don't need to worry about creating the plan or customer on subscription#create, I just need to worry about joining the existing ones.
Where I'm at right now is I got the create working on the subscriptions model, but it's not associating to the correct customer. I need a Subscription model created for every customer that I subscribe to the plan and I'm using a multi select tag.
I'm using a has_many :through because a plan has many customers but a customer can also have many plans.
Please let me know if anything is not clear I tried to explain it as clearly and concisely as possible.
Plan Model
class Plan < ActiveRecord::Base
has_many :subscriptions
has_many :customers, through: :subscriptions
end
Customer Model
class Customer < ActiveRecord::Base
has_many :subscriptions
has_many :plans, through: :subscriptions, dependent: :delete_all
end
Subscription Model
class Subscription < ActiveRecord::Base
belongs_to :plan
belongs_to :customer
end
Routes.rb
resources :customers
resources :plans do
resources :subscriptions
end
Subscriptions Controller
class SubscriptionsController < ApplicationController
def new
#user = current_user
#company = #user.company
#plan = Plan.find(params[:plan_id])
#subscription = Subscription.new
end
def create
if #subscription = Subscription.create(plan_id: params[:subscription][:plan_id] )
#subscription.customer_id = params[:subscription][:customer_id]
#subscription.save
flash[:success] = "Successfully Added Customers to Plan"
redirect_to plan_path(params[:subscription][:plan_id])
else
flash[:danger] = "There was a problem adding your customers to this plan"
render :new
end
end
private
def subscription_params
params.require(:subscription).permit(:customer_id, :plan_id, :stripe_subscription_id)
end
end
Form:
<%= form_for [#plan, #subscription] do |f| %>
<%= f.hidden_field :plan_id, value: #plan.id %>
<div class="row">
<div class="col-md-6">
<%= f.select :customer_id, options_from_collection_for_select(#company.customers, 'id', 'customer_name', #plan.customers), {}, multiple: true, style: "width: 50%;" %><br />
</div>
<div class="col-md-12">
<%= f.submit "Add Customer To Plan", class: "btn btn-success pull-right" %>
</div>
</div>
<% end %>
params:
{"utf8"=>"✓",
"authenticity_token"=>"###",
"subscription"=>{"plan_id"=>"5", "customer_id"=>["", "153", "155"]},
"commit"=>"Add Customer To Plan",
"action"=>"create",
"controller"=>"subscriptions",
"plan_id"=>"5"}
params[:subscription][:customer_id] is an array:
"subscription"=>{"plan_id"=>"5", "customer_id"=>["", "153", "155"]},
Are you actually trying to set up a subscription between the plan and each of the customers in this array? If so try calling the update method for #plan object instead, passing these through in params[:plan][:customer_ids] (note the s)
EDIT:
When i said "pass through the ids in params[:plan][:customer_ids]" i was expecting you to do the standard controller behaviour for update, which is something along the lines of
#plan = Plan.find(params[:plan_id])
#plan.update_attributes(params[:plan])
if params = {:plan => {:customer_ids => [1,2,3]}} then the above code will be doing this:
#plan.update_attributes({:customer_ids => [1,2,3]})
which is like saying
#plan.customer_ids = [1,2,3]
#plan.save
When you set up an association, you get lots of methods you can call on the object. One of them is <association>_ids, in this case customer_ids, which is a way of setting the association: when you save, it will make the association between #plan and customers 1,2 & 3.
You were doing this:
#plan.customers << params[:plan][:customer_ids]
which is mixing up the customer records with the ids. If you're going to use push, aka <<, you need to push in customer objects, not ids. Just using customer_ids = is a quicker and simpler way of doing this.
I'm new to Rails and built something based on this
http://railscasts.com/episodes/403-dynamic-forms
but I have a problem with storing data in the additional fields...
I have a ProductType object that has many ProductField objects. The ProductField object also belongs to a ProductType and Product object belongs to a ProductType.
So,new dynamic fields can easily be added via the constructor ProductType, but when I try to set data in this fields via Product controller nothing happens.
I am sure that problem is related to use strong parameters, but fix described here and here did't help.
product.rb
class Product < ActiveRecord::Base
belongs_to :product_type
serialize :properties, Hash
end
product_type.rb
class ProductType < ActiveRecord::Base
has_many :fields, class_name: "ProductField"
accepts_nested_attributes_for :fields, allow_destroy: true
end
product_field.rb
class ProductField < ActiveRecord::Base
belongs_to :product_type
end
products_controller.rb
class ProductsController < ApplicationController
def new
#product = Product.new(product_type_id: params[:product_type_id])
end
def product_params
params.require(:product).permit(:name, :price, :product_type_id, {:properties => []})
end
product_type_controller.rb
class ProductTypesController < ApplicationController
def product_type_params
params.require(:product_type).permit(:name, fields_attributes: [:id, :name, :field_type, :required, :product_type_id])
end
In console log:
Unpermitted parameters: properties
Started PATCH "/products/4" for 127.0.0.1 at 2013-10-04 22:54:59 +0400
Processing by ProductsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"my3ra60OUXexmmguk2eqRetizx3tWPMq04Z2PnODJMQ=", "product"=>{"product_type_id"=>"1", "name"=>"Product1", "properties"=>{"gjfghjf"=>"123", "123"=>[""]}, "price"=>"10"}, "commit"=>"Update Product", "id"=>"4"}
Product Load (0.3ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", "4"]]
Unpermitted parameters: properties
P.S: maybe someone faced a similar problem when watching a podcast?
If you want to return a nested hash as a parameter you have to name the keys in the array in permit.
class ProductsController < ApplicationController
def new
#product = Product.new(product_type_id: params[:product_type_id])
end
def product_params
params.require(:product).permit(:name, :price, :product_type_id, {:properties => [:foo, :bar, :id]})
end
If you are generating the keys dynamically and can't code them into the permit statement then you need to use this style:
def product_params
params.require(:product).permit(:name, :price, :product_type_id).tap do |whitelisted|
whitelisted[:properties] = params[:product][:properties]
end
end
It's not the most friendly code for a new user, I just finished the 3 course rails certificate at UW and they never even covered .tap.
This is not my work, I'm still just understanding the deeper parts of .permit like this. This is the blog entry I used: Strong Parameters by Example
This error has been troubling me for weeks. When I try to build an Item from a specified User and List, the Item is created, but the association, Wish is not.
If i try to do #item.lists.first.name it returns an error:
undefined method 'name' for nil:NilClass
I'm very new to rails, so I'm sure there is something that I have missed or misunderstood. Any help is thus much appreciated!
I have four models:
class User < ActiveRecord::Base
has_many :lists, dependent: :destroy
has_many :wishes, through: :lists
has_many :items, through: :wishes
class List < ActiveRecord::Base
belongs_to :user
has_many :wishes, dependent: :destroy
has_many :items, through: :wishes
class Wish < ActiveRecord::Base
belongs_to :list
belongs_to :item
class Item < ActiveRecord::Base
has_many :wishes
has_many :lists, through: :wishes
I want to create a new Item from lists_controller.rb show action:
class ListsController < ApplicationController
def show
#user = User.find(params[:user_id])
#list = #user.lists.find(params[:id])
#item = #list.items.build if current_user?(#user)
#items = #list.items
end
class ItemsController < ApplicationController
def create
#list = current_user.lists.find(params[:list_id])
#item = #list.items.build(params[:item])
if #item.save
flash[:success] = "Item created!"
redirect_to #item
else
render 'new'
end
end
My route file looks like this:
Wishlist::Application.routes.draw do
resources :users do
resources :lists, only: [:show, :create, :destroy]
end
resources :items, only: [:show, :new, :create, :destroy]
The form lists/show.html.erb:
<div class="modal-body">
<%= form_for(#item, html: { multipart: true }) do |f| %>
<div class="field">
<%= render 'items/fields', f: f %>
<%= hidden_field_tag :list_id, #list.id %>
</div>
</div>
and items/fields:
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :image %>
<%= f.file_field :image %>
<%= f.label :remote_image_url, "or image URL" %>
<%= f.text_field :remote_image_url %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :link %>
<%= f.text_field :link %>
Update
From the log:
Processing by ItemsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"pcDVdaDzZz4M17Kwjx8mKw6tTF9MjUvx1woTzaKRWJY=", "item"=>{"image"=>#<ActionDispatch::Http::UploadedFile:0x007fecdd1fd890 #original_filename="profile.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"item[image]\"; filename=\"profile.jpg\"\r\nContent-Type: image/jpeg\r\n", #tempfile=#<File:/var/folders/6y/j8zfcgmd02x5s439c0np8fjh0000gn/T/RackMultipart20130216-8413-3vzjuj>>, "remote_image_url"=>"", "title"=>"YES", "link"=>"SDFs"}, "list_id"=>"1", "commit"=>"Add"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = '5AXS8-7-YRyLyGDKYHIZRg' LIMIT 1
List Load (0.2ms) SELECT "lists".* FROM "lists" WHERE "lists"."user_id" = 1 AND "lists"."id" = ? LIMIT 1 [["id", "1"]]
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "items" ("created_at", "image", "link", "title", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Sat, 16 Feb 2013 20:57:10 UTC +00:00], ["image", "profile.jpg"], ["link", "SDFs"], ["title", "YES"], ["updated_at", Sat, 16 Feb 2013 20:57:10 UTC +00:00]]
(2.7ms) commit transaction
Redirected to http://localhost:3000/items/1
Completed 302 Found in 830ms (ActiveRecord: 4.0ms)
This appears to be a known issue (but not really an issue) when using build on a has_many :through. You need to change the association for items on List to include inverse_of.
class List < ActiveRecord::Base
belongs_to :user
has_many :wishes, dependent: :destroy
has_many :items, through: :wishes, inverse_of: :wishes
end
It should then build the connecting Wish object for you properly.
See this relevant issue for more information: https://github.com/rails/rails/pull/7661#issuecomment-8614206
Let me try to summarize first: you have users, which have lists, which contain wishes, which are related to one particular item.
So let's have a look at the following line of code first:
#item.lists.first.name
So what happens is, that rails takes the item, goes through the wishes to all lists that contain this item, takes the first list and looks for the lists name. The problem you see occurs when the item is not related to any lists through wishes (e.g. because your create method for items does not connect new items to wishes and therefore the connection to the list is missing as well): So the item has no relation to a list and the result for #item.lists is an empty list. And well, the first element of an empty list is nothing (nil) and of course nil has no name.
The result is, what you see:
undefined method 'name' for nil:NilClass
One way to fix this, is to use try() which executes the related method only if it is available:
#item.lists.first.try(:name)
The other problem, you were talking about, is that no wish is generated, when you build an item from a user. I think you have to explicitly build the wish. I'm not 100% sure, but I think, declaring :through-relations in the model is only used for queries, not for generating/building.
Try something like the following in your items controller (The idea is to build the wish explicitly first, and after that build the item for the wish):
#wish = #list.wishes.build()
#wish.save
#item = #wish.item.build(params[:item])
After lots of googling, and trial and error, I found that I had to build the Wish explicitly. The create action ended up looking like this:
def create
#list = current_user.lists.find(params[:list_id])
#item = Item.create!(params[:item])
#item.wishes.build(list_id: #list.id)
if #item.save
flash[:success] = "Item created!"
redirect_to #item
else
render 'new'
end
end
Project and tasks have a one-to-many relationship, and project accepts_nested_attributes_for :tasks.
In the form, my task objects look like:
project[tasks][2][assigned_time]
project[tasks][2][due_time]
When the form is submitted I get a hash like:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"...=",
"project"=>{"id"=>"1", "tasks"=>{"1"=>{"assigned_time"=>"09:00",
"due_time"=>"17:00"}, "2"=>{"assigned_time"=>"09:00",
"due_time"=>"17:00"}}
Then I expect them to be saved by just saving the project object:
project = Project.find(params[:id])
respond_to do |format|
if project.update_attributes(params[:tasks])
But I get:
WARNING: Can't mass-assign protected attributes: id SQL (0.3ms)
ROLLBACK Completed in 169ms
ActiveRecord::AssociationTypeMismatch (Task(#2188181260) expected, got
Array(#2151973780)):
Any ideas how to fix this?
In your Projects model, accepts_nested_attributes_for :tasks. This will define #project.tasks_attributes= if you have a has_many :tasks association or #project.task_attributes= if you have a has_one :task association.
In your form, the following:
= form_for #project do |f|
= f.label :project_attribute
= f.text_field :project_attribute
= f.fields_for :tasks do |t|
= t.label :task_attribute
= t.text_field :task_attribute
In your projects controller, the following:
def new
#project = Project.new
#project.tasks.build #=> if has_many
#project.build_task #=> if has_one
end
I think, you just forget to add task_attributes to attr_accessible list in your Project model:
attr_accessible :tasks_attributes, ...
And also, note, that, maybe you generating wrong form, because in my current application, form with nested attributes uses task_attributes method, not tasks (like in your hash)
I've created three classes to represent Books, People, and BookLoans. While I am able to show the association of People to Books through BookLoans I've been seeding my database.
I now need to save a checkout of a book. It was my intention to do this action through the book controller. Specifically, creating a loan action in the BooksController. While this makes sense to me in theory I am having a terrible time implementing the appropriate syntax.
I've added the ability to loan a book from the show view of a book. This view now contains a form which uses the loan action of the book controller to record the loan.
I've added what I believe are the appropriate methods to my Book model. With the help of theIV I have captured the appropriate information in the Controller. Unfortunately, when I press Loan on the book show view a book_loan record is no being recorded.
What am I missing?
Book Model
class Book < ActiveRecord::Base
has_many :book_loans
has_many :borrowers, :through => :book_loans, :source => :person
accepts_nested_attributes_for :book_loans, :allow_destroy => true
def loaned?
book_loans.exists?(:return_date => nil)
end
def current_borrower
if loaned?
book_loans.first(:order => "out_date desc").person
end
end
def add_loan (person_id)
book_loans.create(:book_id => id,
:person_id => person_id,
:out_date => Date.current)
end
end
Loan Method from BooksController
def loan
#book.add_loan(params[:book_loan][:person_id])
redirect_to :action => 'book', :id => params[:id]
end
Book Show View w/ Loan Form
<p>
<b>Title:</b>
<%=h #book.title %>
</p>
<p>
<b>Isbn10:</b>
<%=h #book.isbn10 %>
</p>
<p>
Currently loaned to:
<%=h borrower_name(#book) %>
</p>
<% form_for(#book) do |x| %>
<p>
<%= x.label :loan_person_id %><br/>
<%= collection_select(:book_loan, :person_id,
Person.find(:all, :order => 'name ASC'), :id, :name) %>
<%= x.submit 'Loan', :action => 'loan' %>
</p>
<% end %>
BookLoan Model
class BookLoan < ActiveRecord::Base
belongs_to :book
belongs_to :person
end
Person Model
class Person < ActiveRecord::Base
has_many :book_loans
has_many :books, :through => :book_loans
end
Development Log
Processing BooksController#update (for 127.0.0.1 at 2009-09-24 13:43:05) [PUT]
Parameters: {"commit"=>"Loan", "authenticity_token"=>"XskHLuco7Q7aoEnDfVIiYwVrMEh5uwidvJZdrMbYYWs=", "id"=>"1", "book_loan"=>{"person_id"=>"3"}}
[4;35;1mBook Columns (3.0ms)[0m [0mSHOW FIELDS FROM `books`[0m
[4;36;1mBook Load (4.0ms)[0m [0;1mSELECT * FROM `books` WHERE (`books`.`id` = 1) [0m
[4;35;1mSQL (0.0ms)[0m [0mBEGIN[0m
[4;36;1mBook Load (1.0ms)[0m [0;1mSELECT `books`.id FROM `books` WHERE (`books`.`title` = BINARY 'Dreaming in Code: Two Dozen Programmers, Three Years, 4,732 Bugs, and One Quest for Transcendent Software' AND `books`.id <> 1) LIMIT 1[0m
[4;35;1mSQL (1.0ms)[0m [0mCOMMIT[0m
Redirected to http://localhost:3000/books/1
Completed in 19ms (DB: 10) | 302 Found [http://localhost/books/1]
[4;36;1mSQL (0.0ms)[0m [0;1mSET NAMES 'utf8'[0m
[4;35;1mSQL (0.0ms)[0m [0mSET SQL_AUTO_IS_NULL=0[0m
When you has_many a model, you get access to a few extra methods, two in particular are collection.build and collection.create— build is like new, create is like create :). Have a look at the has many documentation. In this case, you could rewrite add_loan as
def add_loan (person_id)
book_loans.create(:book_id => id,
:person_id => person_id,
:out_date => Date.current)
end
or something similar.
To answer your question about what the view will be sending to the controller, it will send the params hash as usual, but if you just want to pass the person_id to add_loan, you can extract that. Assuming that the part of the view you have above is wrapped in a form for a book loan, you can access the person by params[:book_loan][:person_id]. You'll also want to do a find in that action first, or else #book is going to be nil.
Hope this helps. Cheers.
EDIT: I'm not sure if the way you have it right now works, but I think you want to change your Person model to read
class Person < ActiveRecord::Base
has_many :book_loans
has_many :books, :through => :book_loans
end
EDIT 2: Your development log says you aren't actually hitting loan, you're hitting update. A few things you could do: check to make sure you have loan as a listed resource in your routes; merge the loan action into the update action—could start getting kind of messy so I don't know if this is the best approach. I'm sure there are more, but those are the first things that pop to mind. Also, I don't know if you can add :action => 'loan' to a submit tag. I think it will just look at that as if it were an html option. You might want to change your form_for to read
<% form_for(#book), :url => { :action => 'loan' } do |x| %>
once you've made sure that the routes are in order. But as I said earlier, I'm pretty sure you will be thrown an error on that action because you haven't defined a Book.find for it.