Nested routes - rails 5.2.4 - Undefined local variable or method - ruby-on-rails

To register addresses for clients in my application I am using nested routes, however when clicking on the link to register a new address or to edit an existing address the system presents the error:
undefined local variable or method `address' for #<##Class:0x00007fd1c815fa58>:0x00007fd1d05ef598>
Did you mean? #address.
Can someone with a good heart inform me where the error is or what is missing that I haven't been able to see yet?
Here is the application code:
routes.rb
resources :clients do
resources :addresses
end
models/addresses.rb
class Address < ApplicationRecord
belongs_to :client
end
models/clients.rb
class Client < ApplicationRecord
has_many :addresses
end
controllers/addresses_controller.rb
class Backoffice::AddressesController < BackofficeController
before_action :set_client
before_action :set_address, only: [:edit, :update, :destroy]
def new
#address = #client.addresses.build
end
def create
#address = #client.addresses.build(params_address)
if #address.save
redirect_to backoffice_addresses_path, notice: "Endereço cadastrado com sucesso!"
else
render :new
end
end
def edit
end
def update
if #address.update(params_address)
redirect_to backoffice_client_addresses_path(#client), notice: "#{#client.name} alterado com sucesso!"
else
render :new
end
end
private
def set_client
#client = Client.find(params[:client_id])
end
def set_address
#address = #client.addresses.find(params[:id])
end
def params_address
params.require(:address).permit(:nickname,
:cep,
:street,
:number,
:district,
:city,
:state,
:client_id)
end
end
views/addresses/index.html.erb
<%= link_to new_backoffice_client_address_path(#client)" do %>
Novo
<% end %>
<% #client.addresses.each do |address| %>
<tr>
<td><%= address.nickname %></td>
<td> ... </td>
<%= link_to edit_backoffice_client_address_path(#client, address) do %>
Editar
<% end %>
<% end %>
views/addresses/edit.html.erb
<%= render partial: "addresses/shared/form" %>
views/addresses/shared/_form.html.erb
<%= form_with(model: [#client, address], local: true) do |f| %>
...
<% end %>

<%= form_with(model: #address, url: [#client, #address], local: true) do |f| %>
…
<% end %>
According to the docs, the values for url are "Akin to values passed to url_for or link_to", so using an array should work.
This also works with shallow nesting since the array is compacted.

Related

Rails: "Validation failed: Class must exist" in a Form_with

I've a multiple relation table named Order which belongs_to a Relai(to avoid singular/plurials complications), a Customer and a Composition. I set my Order model accordingly with nested_attributes as mentioned below. Before adding the customer part, I want to send a mail with just the #composition and a #relai chose with a dropdown.
class Order < ApplicationRecord
belongs_to :relai
belongs_to :composition
accepts_nested_attributes_for :relai
accepts_nested_attributes_for :composition
end
I set my OrdersController to get the :composition_id from my params
def new
#order = Order.new
#composition = Composition.find(params[:composition_id])
#relais = Relai.all
end
def create
#florist = Florist.first
#composition = Composition.find(params[:composition_id])
##relai = Relai.find(params[:relai_id]) # If I add this line it returns "Couldn't find Relai without an ID"
#order = Order.new(order_params)
if #order.save!
raise
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(
florist_attributes: [:id],
relai_attributes: [:id, :name, :address],
composition_attributes: [:id, :name]
)
end
My View:
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai, #relais, :id, :name %>
<%= compo.fields_for :composition do |fc| %>
<%= fc.collection_select :composition, #compositions, :id, :name %>
<% end %>
# Start Block that doesn't display the Relai.all
#<%#= compo.fields_for :relai do |fr| %>
#<%#= fr.label :relai, 'Ici la liste des relais :' %>
#<%#= fr.association :relai, collection: #relais %>
#<%#= fr.association :relai, #relais, :id, :name %>
#<%# end %>
# End Block
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>
And the routes:
resources :compositions, only: [:show, :index] do
resources :orders, only: [:show, :new, :create, :index]
end
I also tried:
"nested_attributes" which seems to not works (I can't see my Relai collection in view.
=> https://www.pluralsight.com/guides/ruby-on-rails-nested-attributes)
the "optional: true" in model which throw me an error of:
PG::NotNullViolation: ERROR: null value in column "relai_id" of relation "orders" violates not-null constraint
Can someone explain me why I got a "Validation failed: Relai must exist, Composition must exist" whereas these appears in my params?
{"authenticity_token"=>"[FILTERED]", "order"=>{"relai"=>"2"}, "commit"=>"MAIL", "composition_id"=>"3"}
I'm on Rails 6.1.4 ; Ruby 2.6.6
accepts_nested_attributes_for works from parent to children. You are using it on the child side (Order).
If you just need to assign an existing Relai and Composition to Order just use a select for both of them:
class Order < ApplicationRecord
belongs_to :relai
belongs_to :composition
end
def new
#order = Order.new
#compositions = Composition.all
#relais = Relai.all
end
def create
#order = Order.new(order_params)
if #order.save!
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:relai_id, :composition_id)
end
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai_id, #relais, :id, :name %>
<%= compo.collection_select :composition_id, #compositions, :id, :name %>
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>
EDIT: Setting Composition on the controller.
def new
composition = Composition.find(params[:composition_id])
#order = Order.new(composition: composition)
#relais = Relai.all
end
def create
#order = Order.new(order_params)
if #order.save!
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:relai_id, :composition_id)
end
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai_id, #relais, :id, :name %>
<%= compo.hidden_field :composition_id %>
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>

Transmit params with form_tag

In my views I have a form and trying to update quantity for an order line:
<div class="quantity">Quantity</br>
<%= form_tag(order_line_path(line.id), method: "patch") do %>
<%= number_field_tag 'qty', '1', within: 1...line.book.stock %>
<%= submit_tag "Update", class: "btn btn-primary" %>
<% end %>
</div>
The instance variable in the rest of my view is a collection of order lines, so I cannot use it.
Then I have in my controller the update method:
def update
#order = current_order
#order_line = #order.order_lines.find(params[:id])
#order_line.update_attributes(order_line_params)
end
And my strong params definition:
def order_line_params
params.require(:order_line).permit(:qty)
end
I get this error :
param is missing or the value is empty: order_line
Could someone please have a look?
Thanks!
The reason you are getting param is missing or the value is empty: order_line is that you are using form_tag which gives a "flat" params hash.
However this is easily avoidable if you just use form_with/form_for.
# routes.rb
resources :orders do
resources :line_items, shallow: true
end
# app/views/order_items/edit.html.erb
# use `form_with(model: #order_item)` in Rails 5
<%= form_for(#order_item) do |f| %>
<%= f.label :qty, within: 1...f.object.book.stock %>
<%= f.number_field :qty, %>
<%= f.submit %>
<% end %>
class OrderItemsController < ApplicationController
before_action :set_order_item, only: [:show, :edit, :update, :destroy]
# ...
# GET /order_items/:id/edit
def edit
end
# PATCH /order_items/:id
def update
if #order_item.update(order_item_params)
redirect_to #order_item, notice: 'Line updated'
else
render :edit
end
end
private
def set_order_item
#order_item = OrderItem.find(params[:id])
end
def order_item_params
params.require(:order_item).permit(:qty)
end
end
But what you're really looking for unless you are doing the update/creation of nested items with AJAX is most likely a combination of accepts_nested_attributes and fields_for which will let the user mass edit the line items:
class Order < ApplicationRecord
accepts_nested_attributes_for :order_items
end
<%= form_for(#order) do |f| %>
<%= fields_for(:order_items) do |oif| %>
<%= f.label :qty, within: 1...f.object.book.stock %>
<%= f.number_field :qty, %>
<% end %>
<%= f.submit %>
<% end %>
class OrdersController < ApplicationController
# PATCH /orders/:id
def update
if #order.update(order_params)
redirect_to #order, notice: 'Order updated.'
else
render :new
end
end
private
def order_params
params.require(:order).permit(order_items_attributes: [:qty])
end
end

Trying to have 2 forms pass to 2 different controllers from one view

I have 2 forms in one view one is displayed if the user is a moderator and the other if it is a normal user and they both send the information to 2 different controllers. My problem is that if its a normal user, the form that is displayed for them uses the wrong controller.
Here is the coding
categories/new.html.erb
<% if current_user.mod_of_game? #guide %>
<%= form_for([#guide, #category], url: guide_categories_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Category name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
<% else %>
<%= form_for([#guide, #check_category], url: check_category_post_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Category name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
<% end %>
Categories controller
before_action :mod_checker, only: [:create]
def new
#guide = Guide.friendly.find(params[:guide_id])
#category = Guide.friendly.find(#guide.id).categories.new
#check_category = CheckCategory.new
end
def create
#guide = Guide.friendly.find(params[:guide_id])
#category = Guide.friendly.find(#guide.id).categories.new(category_params)
if ((#category.save) && (current_user.mod_of_game? #guide))
flash[:info] = "guide category added succesfully!"
redirect_to #guide
else
render 'new'
end
end
private
def category_params
params.require(:category).permit(:name)
end
def mod_checker
#guide = Guide.friendly.find(params[:guide_id])
unless current_user.mod_of_game? #guide
flash[:danger] = "Sorry something went wrong!"
redirect_to root_path
end
end
check_categories controller
def new
end
def create
if #check_category.save
flash[:info] = "Game category added successfully. A mod will apporve it shortly."
redirect_to #guide
else
render 'new'
end
end
private
def check_category_params
params.require(:check_category).permit(:name)
end
and the routes
resources :guides do
resources :categories, only: [:new, :create, :edit, :update]
end
resources :check_categories, only: [:new, :edit, :update]
match 'guides/:guide_id/categories/' => 'check_categories#create', :via => :post, as: :check_category_post
sorry the coding is a bit messy, the 4 spaces to put it in a code block was spacing my coding weird.
When i have a non moderator user submit the form, the before action in the categories controller is run and I'm redirected to the homepage. I don't know why it does this because the submit path should go to the check_categories controller for non moderator users, the check_categories controller doesn't have the before filter.
Why does it use the before filter in the controller I'm not using for that form? How can I fix it?
Building this app to learn rails better. So I can only assume lack of rails knowledge is causing me to do something wrong.
Bad practice to have two forms with identical code (apart from the path) - goes against DRY Don't Repeat Yourself.
As mentioned by #Akash, this sounds like a job for authorization.
Further, it also denotes that you have issues with the underlying structure of your code. Specifically, you have an antipattern with CheckCategory (you can put it all into the Category model):
#config/routes.rb
resources :guides do
resources :categories, only: [:new, :create, :edit, :update] do
patch :approve, on: :member
end
end
#app/models/category.rb
class Category < ActiveRecord::Base
before_action :set_guide
def new
#category = current_user.categories.new
flash[:notice] = "Since you are not a moderator, this will have to be approved." unless current_user.mod_of_game? #guide
end
def create
#category = current_user.categories.new category_params
#category.guide = #guide
#category.save
end
def approve
#category = #guide.categories.find params[:id]
#category.approve
end
private
def set_guide
#guide = Guide.find params[:guide_id]
end
end
#app/views/categories/new.html.erb
<%= form_for [#guide, #category] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Category name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
The above will solve most of your structural issues.
--
To fix the authorization issue, you'll be best denoting whether the category is "approved" in the model:
#app/models/category.rb
class Category < ActiveRecord::Base
enum status: [:pending, :approved]
belongs_to :user
belongs_to :guide
validates :user, :guide presence: true
before_create :set_status
def approve
self.update status: "approved"
end
private
def set_status
self[:status] = "approved" if self.user.mod_of_game? self.guide
end
end
--
If I understand correctly, you want to allow anyone to create a category, but none-mods are to have their categories "checked" by a moderator.
The code above should implement this for you.
You will need to add a gem such as CanCan CanCanCan to implement some authorization:
#app/views/categories/index.html.erb
<% #categories.each do |category| %>
<%= link_to "Approve", guide_category_approve_path(#guide, category) if category.waiting? && can? :update, Category %>
<% end %>
Use "Cancan" Gem and give authorization

form_for a "belongs_to" model in 'show' of parent model

I have one model "Breads" that has_many "Posts".
I would like to have a form to create a new "Post" on the 'show' page for a given "Bread" that creates the association to the record of 'Bread' which the 'show' page is displaying.
I have tried a few different methods, but all are giving an error. The method that I have shown below gives a "Association cannot be used in forms not associated with an object" error.
/views/breads/show.html.erb:
<p>
<strong>Bread Type:</strong>
<%= #bread.bread_type %>
</p>
<table>
<tr>
<th>Uploaded By</th>
<th>Comment</th>
<th>Picture</th>
</tr>
<% #bread.posts.each do |post| %>
<tr>
<td><%= post.uploader %></td>
<td><%= post.comment %></td>
<td><%= image_tag post.attachment_url.to_s %></td>
</tr>
<% end %>
</table>
<%= #bread.id %>
<%= simple_form_for #bread do |b| %>
<%= simple_fields_for :posts do |p| %>
<%= p.input :uploader %>
<%= p.input :comment %>
<%= p.association :bread, value: #bread.id %>
<%= p.file_field :attachment %><br>
<%= p.button :submit %>
<% end %>
<% end %>
<%= link_to 'Back', breads_path %>
config/routes.rb
Rails.application.routes.draw do
get 'welcome/index'
root 'welcome#index'
resources :breads
resources :posts
end
controllers/breads_controller.rb:
class BreadsController < ApplicationController
def index
#breads = Bread.all
end
def show
#bread = Bread.find(params[:id])
end
def new
#bread = Bread.new
end
def edit
#bread = Bread.find(params[:id])
end
def create
#bread = Bread.new(bread_params)
if #bread.save
redirect_to #bread
else
render 'new'
end
end
def update
#bread = Bread.find(params[:id])
if #bread.update(bread_params)
redirect_to #bread
else
render 'edit'
end
end
def destroy
#bread = Bread.find(params[:id])
#bread.destroy
redirect_to breads_path
end
private
def bread_params
params.require(:bread).permit(:bread_type)
end
end
models/bread.rb:
class Bread < ActiveRecord::Base
has_many :posts
validates :bread_type, presence: true, uniqueness: true
end
models/post.rb:
class Post < ActiveRecord::Base
belongs_to :bread
mount_uploader :attachment, AttachmentUploader
end
Do this -
<%= simple_form_for #bread do |b| %>
<%= b.simple_fields_for(:posts,#bread.posts.build) do |p| %>
<%= p.input :uploader %>
<%= p.input :comment %>
<%= p.file_field :attachment %><br>
<%= p.button :submit %>
<% end %>
<% end %>
and make changes in beard_params
def beard_params
params.require(:bread).permit!
end
Here permit! requires all parameters and for other way you can use #pawan's answer.
Extending #Amit Suroliya answer, you need to add posts_attributes to bread_params
def bread_params
params.require(:bread).permit(:id, :bread_type, posts_attributes: [:id, :uploader, :comment, :bread_id, :attachment])
end
Update:
You also need to add accepts_nested_attributes_for :posts in Bread model.
Iam sorry, but this is not good way at all, try to don't abuse rails and rest routes :)
Here is easy example how to do that:
config/routes.rb
resources :bread do
resources :posts
end
This means there will be routes like:
bin/rake routes
breads - breads#index
bread/:id - breads#show
etc..
and most important
bread/:bread_id/posts/:id
...
That means posts are nested resources for bread...
app/controllers/breads_controller.rb
controller BreadsController < BaseController
before_action :find_bread, except: %i(index create new)
.... action new, update, edit etc..
end
but now its the important part in PostsController..
app/controllers/posts_controller.rb
controller PostsController < BaseController
before_action :find_bread
before_action :find_post, except: %i(index new create)
before_action :build_post, only: %i(new create)
.... action new, update, edit etc..
# Example with :return link
def create
if #post.save
if params[:back] == 'bread_show'
redirect_to bread_path(#bread)
else
redirect_to bread_post_path(#bread, #post)
end
else
render 'new'
end
end
private
def build_post
if params[:post]
#post = #bread.posts.build(post_params)
else
#post = #bread.posts.build
end
end
def find_post
#post = #bread.posts.find(params[:id])
end
def find_bread
#bread = Bread.find(params[:bread_id])
end
... post params ...
end
Now you have rest full routes and you're able to do what you want without such a pain and clean
... output hidden
<%= #bread.id %>
<%= simple_form_for #bread.posts.build do |b| %>
<%= p.input :uploader %>
<%= p.input :comment %>
<%= p.file_field :attachment %><br>
<%# Send back link to return on proper page %>
<%= p.hidden_field :back, 'bread_show' %>
<%= p.button :submit %>
<% end %>
<%= link_to 'Back', breads_path %>
There can be some mistakes, I write this code from memory, can't try that :(

'Categories' show action isn't displaying associated 'Listings' (Ruby on Rails)

I'm creating a website where users can post their items & services for sale (a classified ads site) and have setup the 'listing', 'category', and 'user' models. The listings and categories are associated with each other in a "has_many"-->"belongs_to" relationship, with categories owning listings.
However, even though the Listings are successfully associating with a Category upon creation of a new listing (I believe...?), the "show" page for Categories is not displaying it's associated listings; (displaying the "No listings to display" message"). What could be the problem?
-Here's my 'show' page for Categories:
<h1><%= #category.name %></h1>
<%= render partial: 'listings/list', locals: {
listings: #category.listings } %>
-and here's the controller for Categories:
class CategoriesController < ApplicationController
def show
#category = Category.find(params[:id])
end
end
-Here's the controller for Listings:
class ListingsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, except: [:create, :index, :new]
def index
#listings = Listing.all
end
def show
end
def new
#listing = Listing.new
end
def edit
end
def create
#listing = current_user.listings.build(listing_params)
if #listing.save
redirect_to #listing
flash[:success] = "Listing was successfully created."
else
render 'new'
end
end
def update
if #listing.update(listing_params)
flash[:success] = "Listing was successfully updated."
redirect_to #listing
else
render 'edit'
end
end
def destroy
#listing.destroy
flash[:success] = "Listing deleted."
redirect_to request.referrer || root_url
end
private
def listing_params
params.require(:listing).permit(:name, :description, :price, :image,
:category_id)
def correct_user
#listing = current_user.listings.find_by(id: params[:id])
redirect_to root_url if #listing.nil?
end
end
-This is listing partial referenced in the show page:
<% if #listings.nil? %>
No listings to display! Go <%= link_to 'create one', new_listing_path %>.
<% else %>
<table class="table table-striped">
<tbody>
<% #listings.each do |listing| %>
<tr>
<td><%= link_to listing.name, listing %></td>
<td class="text-right">
<% if listing.price %>
<%= number_to_currency(listing.price) %>
<% end %>
</td>
<td class="text-right">
<% #listings.each do |listing| %>
<% if listing.category %>
<%= link_to listing.category.name, listing.category %>
<% end %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
-The model file for listing:
class Listing < ActiveRecord::Base
belongs_to :user
belongs_to :category
default_scope -> { order('created_at DESC') }
validates :name, presence: true
validates :description, presence: true
validates :price, presence: true
validates :user_id, presence: true
mount_uploader :image, ImageUploader
end
-The model file for category:
class Category < ActiveRecord::Base
has_many :listings
end
<%= render partial: 'listings/list', locals: {
listings: #category.listings } %>
Makes listings avalable via listings var, not #listings, as in your template.
Just remove # sign.

Resources