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
Related
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 %>
I using a has_many :through many-to-many relation in a multi-select via collection_select :multiple => true. I don't understand, why "genre_ids"=>["", "2", "3", "4"] always has empty first element?
Models:
class Book < ApplicationRecord
has_many :book_genres
has_many :genres, through: :book_genres
end
class Genre < ApplicationRecord
has_many :book_genres
has_many :books, through: :book_genres
end
class BookGenre < ApplicationRecord
belongs_to :book
belongs_to :genre
end
_form.html.erb
<%= form_for #book do |f| %>
...
<%= f.collection_select(:genre_ids, Genre.all, :id, :genre, {include_blank: "Select genre"}, {multiple: true, size: 6}) %>
...
<%= f.submit %>
<% end %>
books_controller.rb
class BooksController < ApplicationController
def new
#book = Book.new
end
def create
render plain: params.inspect
end
end
Parameters:
<ActionController::Parameters {
"utf8"=>"✓",
"authenticity_token"=>"8WRSXJHwMyHM....",
"book"=>{"title"=>"",
"genre_ids"=>["", "2", "3", "4"],
"desc"=>"",
"published_at"=>"1982"},
"commit"=>"Create Book",
"controller"=>"books",
"action"=>"create"} permitted: false>
May be it's because the "Select genre" is also selected since, it is a multiple select dropdown. Either remove it before sending the data over to the server or avoid using the "include_blank" option.
This is an intentional Rails behavior to work around a limitation of the HTML spec. HTML says that an unselected item should be sent to the server in the same way as the absence of that item would be. For example, if you select zero items, your browser will send a request with that attribute entirely missing from the request, as if the entire select element didn't exist.
But this puts us in a bad spot because normally, a parameter not specified in the request means that we shouldn't change the attribute. So we cannot tell the difference between "the user didn't want to change this value" and "the user wanted to clear this value". Therefore Rails adds a hidden form element for every select that adds a blank string, so the browser will always send the list.
See: https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-check_box
I have 2 types of users, one is called list_owner and one is called subscriber. These are both users made with devise. Subscribers should be able to follow the list_owner. I have another model called Relationship to keep track who the subscriber follows.
I used http://ruby.railstutorial.org/ mostly as an example.
These are the relations
class Relationship < ActiveRecord::Base
attr_accessible :list_owner_id, :subscriber_id
belongs_to :list_owner
belongs_to :subscriber
end
class ListOwner < ActiveRecord::Base
has_many :messages
has_many :relationships, :dependent => :destroy, :foreign_key => "list_owner_id"
end
class Subscriber < ActiveRecord::Base
has_many :relationships, :foreign_key => "subscriber_id",
:dependent => :destroy
def follow!(list_owner)
puts "-----------------------"
relationships.create!(:subscriber_id => subscriber.id, :list_owner_id => list_owner.id)
end
end
I made a list with list owners with a button where the subscriber can subscribe to.
<%= form_for current_subscriber.relationships.build(:list_owner_id => l.id, :subscriber_id => #subscriber.id),
:remote => true do |f| %>
<%= f.hidden_field :list_owner_id %>
<%= f.hidden_field :subscriber_id %>
<%= f.submit "Subscribe", :class => "button btn" %>
<% end %>
class RelationshipsController < ApplicationController
def create
puts "---------------------relationships CREATE---------------------"
#list_owner = ListOwner.find(params[:relationship][:list_owner_id])
current_subscriber.follow!(#list_owner)
redirect_to root_path
end
end
This is the error i get. What am i doing wrong?
Started POST "/relationships" for 127.0.0.1 at Wed Sep 07 15:37:17 +0200 2011
Processing by RelationshipsController#create as JS
Parameters: {"commit"=>"Subscribe", "relationship"=>{"list_owner_id"=>"2", "subscriber_id"=>"1"}, "authenticity_token"=>"m5plsJLdzuwNt1yKfDJFKD28GcR138V+pezbfbECCPk=", "utf8"=>"✓", "_"=>""}
ListOwner Load (0.2ms) SELECT "list_owners".* FROM "list_owners" WHERE "list_owners"."id" = 2 LIMIT 1
Subscriber Load (0.2ms) SELECT "subscribers".* FROM "subscribers" WHERE "subscribers"."id" = 1 LIMIT 1
Completed 500 Internal Server Error in 34ms
NameError (undefined local variable or method `subscriber' for #<Subscriber:0x102f140c0>):
app/models/subscriber.rb:21:in `follow!'
app/controllers/relationships_controller.rb:8:in `create'
There's no such 'subscriber' object defined, but you don't need one, since you're calling the create!() method on the association collection, which will pre-populate the parent object. That is, calling subscriber.relationships.create! will set subscriber_id = subscriber.id.
def follow!(list_owner)
relationships.create!(:list_owner_id => list_owner.id)
end
I'm trying to build a form for an "Incident" that allows users to add notes (text fields) to it dynamically with some javascript. So when they click an "Add Note" button in the form, a text field pops up and they can add a note. If they click it again, another field will show up. So far this works fine when creating a new incident, but when I edit the incident and add a new field, it doesn't pick up the relationship between incident_note and user.
For example, here is what I see when I create a new incident.
INSERT INTO "incident_notes" ("created_at", "updated_at", "user_id", "note", "incident_id") VALUES('2010-07-02 14:07:42', '2010-07-02 14:07:42', 2, 'A Note', 8)
As you can see, the user_id field has a number assigned to it. But here is what happens during the edit when I add another note:
INSERT INTO "incident_notes" ("created_at", "updated_at", "user_id", "note", "incident_id") VALUES('2010-07-02 14:09:11', '2010-07-02 14:09:11', NULL, 'Another note', 8)
The user_id is NULL. I'm not sure what I've done. The code is very similar for both "edit" and "new" in the controller.
I have the following models and relationships (only showing the relevant sections):
class Incident < ActiveRecord::Base
has_many :incident_notes
belongs_to :user
end
class IncidentNote < ActiveRecord::Base
belongs_to :incident
belongs_to :user
end
class User < ActiveRecord::Base
has_many :incidents
has_many :incident_notes
end
Here is the relevant part of the new form (the edit is essentially the same):
<% form_for([#customer,#incident]) do |f| %>
<p>
<% f.fields_for :incident_notes do |inf| %>
<%= render "incident_note_fields", :f => inf %>
<% end %>
<p><%= link_to_add_fields "Add Note", f, :incident_notes %></p>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
And here are the create and update methods in the Incident controller.
def create
#incident = #customer.incidents.build(params[:incident])
#incident.capc_id = generate_capc_id
for inote in #incident.incident_notes
(inote.user = current_user) if (inote.user == nil)
end
respond_to do |format|
if #incident.save #etc
end
def update
#incident = #customer.incidents.find(params[:id])
for inote in #incident.incident_notes
(inote.user = current_user) if (inote.user == nil)
end
respond_to do |format|
if #incident.update_attributes(params[:incident])
#etc
end
There may be a better way to do this, but as you can see in the "create" method I had to manually set the incident_note user field to the current user. This works fine, but the same does not seem to work in the update method.
Any ideas, suggestions, and help will be much appeciated! I'm very stuck at the moment. :)
I suggest that you don't have incident_notes directly belonging to user. In other words a user has many incidents and an incident has many incident notes.
class Incident < ActiveRecord::Base
has_many :incident_notes
belongs_to :user
end
class IncidentNote < ActiveRecord::Base
belongs_to :incident
end
class User < ActiveRecord::Base
has_many :incidents
has_many :incident_notes, :through => :incident
end
The user's incident notes are then obtained through her incidents model
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.