When going on the view page, the error "param is missing or the value is empty: restaurant" is displayed.
- I solve the problem by deleting "require(:restaurant)" but don't really understand the trick...do you ?
- I am looking for a solution that allows me to keep the "require(:restaurant)... Any Idea ?
restaurants_controller.rb
class RestaurantsController < ApplicationController
before_action :set_restaurant, only: [:show, :edit, :update]
def index
#restaurants = Restaurant.all
end
def show
end
def new
#restaurant = Restaurant.new(params.permit(:name, :address, :category))
end
def create
#restaurant = Restaurant.new(restaurant_params)
#restaurant.save
redirect_to restaurant_path(#restaurant)
end
private
def set_restaurant
#restaurant = Restaurant.find(params[:id])
end
def restaurant_params
params.require(:restaurant).permit(:name, :address, :category)
end
end
new.html.erb
<%= simple_form_for(#restaurant) do |f| %>
<% if #restaurant.errors.any? %>
<div class="errors-container">
<ul>
<% #restaurant.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.input :name %>
<%= f.input :address %>
<%= f.input :category %>
<%= f.submit "add a resto", class: "btn btn-primary" %>
<% end %>
restaurant.rb
class Restaurant < ApplicationRecord
validates :name, :address, :category, presence: true
validates :category, inclusion: { in: ["chinese", "italian", "japanese", "french", "belgian"]}
has_many :reviews, dependent: :destroy
end
routes
Rails.application.routes.draw do
resources :restaurants, only: [:new, :create, :show, :index] do
resources :reviews, only: [ :new, :create ]
end
end
The inputs of your form should be named like this:
restaurant[name]
restaurant[address]
restaurant[category]
It could be that it is something like is:
name
address
category
You can debug your params by inspecting it.
You could do something like this in your restaurant_params in the controller:
raise params.inspect
Or open your debug console of the browser and see what is posted.
And as mentioned in the comments you can use the restaurant_params in your new method but I don't think that fixes you issue it is more likely that the form is the problem.
# instead of
Restaurant.new(params.permit(:name, :address, :category))
# do
Restaurant.new(restaurant_params)
I assume you're trying to auto-populate the form on your new view with some passed attributes.
Your restaurant#new is looking for a params[:restaurant] hash to create a new Restaurant object and can't find it. Look at how you're calling the new action and what parameters are being passed -- my guess is that you're not passing any or you're passing them in the root params hash like params[:name], params[:address] instead of params[:restaurant] => { :name => 'name'}.
Most new actions are a GET so if you're using inline URL parameters put them in like http://address.com/restaurant/new?restaurant[name]=Bistro&restaurant[category]=burgers
In restaurants_controller.rb, we can declare a method for show like:
def show
#restaurant = Restaurant.find(params[:id])
end
In the View Section where we want to link with a button, we can paste
<td><%= link_to "Show", restaurant %> </td>
I hope it will work
Related
I have Categories (Parents) within which are listed Products (Children).
I want to be able to create a new Product directly from the navbar, anywhere in the app and then, during the creation, assign it to a Category.
However, I get the present error:
NoMethodError in Products#new
Showing /Users/istvanlazar/Mobily/app/views/products/new.html.erb where line #9 raised:
undefined method `products_path' for #<#<Class:0x00007febaa5aec98>:0x00007febae0f9e38>
Did you mean? product_show_path
## product_show_path is a custom controller that has nothing to do with this one,
enabling show and clean redirection from newest_products index bypassing categories nesting.
Extracted source (around line #9):
9 <%= form_for [#product] do |f| %>
10 <div class="form-styling">
11 <div>
12 <%= f.label :name %>
My App works as such:
Models
class Category < ApplicationRecord
has_many :products, inverse_of: :category
accepts_nested_attributes_for :products
validates :name, presence: true
end
class Product < ApplicationRecord
belongs_to :user, optional: true
belongs_to :category, inverse_of: :products
validates :category, presence: true
end
Routes
get 'products/new', to: 'products#new', as: 'new_product'
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :edit]
# resources :products, only: [:index, :show, :new, :edit]
end
Controllers
class ProductsController < ApplicationController
before_action :set_category, except: :new
def index
#products = Product.all
#products = policy_scope(#category.products).order(created_at: :desc)
end
def show
#product = Product.find(params[:id])
end
def new
#product = Product.new
#product.user = current_user
end
def create
#product = Product.new(product_params)
#product.user = current_user
if #product.save!
redirect_to category_product_path(#category, #product), notice: "Product has been successfully added to our database"
else
render :new
end
end
private
def set_category
#category = Category.find(params[:category_id])
end
def product_params
params.require(:product).permit(:name, :price, :description, :category_id, :user, :id)
end
end
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def show
#category = Category.find(params[:id])
end
def new
# Non-existant created in seeds.rb
end
def create
# idem
end
def edit
# idem
end
def update
# idem
end
def destroy
# idem
end
private
def category_params
params.require(:category).permit(:name, :id)
end
end
Views
# In shared/navbar.html.erb:
<nav>
<ul>
<li>Some Link</li>
<li>Another Link</li>
<li><%= link_to "Create", new_product_path %></li>
</ul>
</nav>
# In products/new.html.erb:
<%= form_for [#product] do |f| %>
<div class="form-styling">
<div>
<%= f.label :name %>
<%= f.text_field :name, required: true, placeholder: "Enter product name" %>
<%= f.label :price %>
<%= f.number_field :price, required: true %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div>
<%= f.label :category %>
<%= f.collection_select :category_id, Category.order(:name), :id, :name, {prompt: 'Select a Category'}, required: true %>
</div>
<div>
<%= f.submit %>
</div>
</div>
<% end %>
Do you have any idea of where it went wrong? Or is it impossible to Create a Child before assigning it to a Parent..?
Thanks in advance.
Best,
Ist
You haven't defined any route to handle your new product form's POST. You've defined the new_product path, but this arrangement is breaking Rails' conventions and you're not providing a work-around.
You could define another custom route, e.g. post 'products', to: 'products#create', as: 'create_new_product' and then set that in your form like form_for #product, url: create_new_product_path do |f|.
However, you should consider changing the structure so that product routes are not nested under categories. Think twice before breaking conventions this way.
Edit: I misread the intention, ignore this. Go with Jeremy Weather's answer.
Or is it impossible to Create a Child before assigning it to a Parent..?
With the way you have your relationships and validations set up: yes, it is. A Product requires a Category, through the validates :category, presence: true. And if you're on Rails 5+, the association will already be required by default (meaning that validates is actually redundant). Meaning if you try to create a Product without a Category, it will fail, and the Product will not be saved to the database.
With that constraint in place, here's what I recommend trying:
Remove the custom /products/new route
remove get 'products/new', to: 'products#new', as: 'new_product'
Add :new and :create routes to allow product creation nested under categories
Add :create and :new, to the :only array to the resources :products nested under resources :categories
Move the file views/products/new.html.erb to views/categories/products/new.html.erb (creating a new categories directory in views if necessary).
In that file, alter the form_for so that the form is POSTing to the nested :create route:
form_for [#category, #product] do |f|
Visit the URL /categories/[insert some Category ID here]/products/new, filling out some data, and see if that works.
Your routes.rb should look like this:
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :new, :create, :edit]
end
My problem is connected with my previous problem. It was solved by using nested routing. But there was different idea without using nested resources. I tried it as an exercise. But when I submit data, ActiveRecord::RecordNotFound in SentencesController#create shows up.
Rails.application.routes.draw do
root 'stories#index'
get 'stories/show'
get 'stories/new'
post 'stories/:story_id', to: 'sentences#create'
resources :stories
resources :sentences, only: [:create]
end
shared/_sentence_form.html.erb is part of story/show_form.html.erb
<%= form_for(#sentence) do |f| %>
<%= hidden_field_tag :story_id, value: #story.id %>
<%= f.text_area :content, placeholder: "Compose new sentence..." %>
<%= f.submit "Save"%>
<% end %>
SentencesController
class SentencesController < ApplicationController
before_action :sentence_params
before_action :find_story
def create
#sentence = find_story.sentences.build(sentence_params)
if #sentence.save
flash[:success] = "You wrote the continuation!"
redirect_to root_url
else
flash[:danger] = "I did not save your words!"
redirect_to "#"
end
end
private
def sentence_params
params.permit(:content, :story_id)
end
def find_story
#story = Story.find(params[:story_id])
end
end
and model:
class Sentence < ApplicationRecord
belongs_to :story
validates :story_id, presence: true
validates :content, presence: true, length: { maximum: 150 }
end
I tried many combinations in controller and view. When I put some information in the text_area and click submit they don't save.
The param is nested:
def find_story
#story = Story.find(params[:sentance][:story_id])
end
But nesting the route is a better RESTful design anyways.
The route POST /stories/:story_id/sentances makes it very clear what the action does.
resources :stories do
resources :sentences, only: [:create]
end
<%= form_for([#story, #sentence]) do |f| %>
<%= f.text_area :content, placeholder: "Compose new sentence..." %>
<%= f.submit "Save"%>
<% end %>
This will properly pass params[:story_id] as a segment of the URL.
I am not sure that's a solution to your problem, but I believe that
def sentence_params
params.permit(:content, :story_id)
end
needs a :sentence in there somewhere. Your form is a form_for #sentence so I guess your params are params[:sentence][:content] or similar.
I believe your error happens in the line #story = Story.find(params[:story_id]), right? Make sure that the #story variable is present in your form and that the record can be found.
I am working on a project for which I have to analyse data inside a file, which the user should be able to upload. I have a model InputFile and chose to implement an analysis_facade for my Analysis model which bundles most of the functionality.
Here are the models
#app/models/analysis.rb
class Analysis < ApplicationRecord
has_one :input_file, dependent: :destroy
end
#app/models/input_file.rb
class InputFile < ApplicationRecord
belongs_to :analysis
has_many :access_data, dependent: :destroy
def uploaded_file=(file_field)
self.name = File.basename(file_field.original_filename).gsub(/[^\w._-]/,'')
self.content_type = file_field.content_type.chomp
self.data = file_field.read
end
end
Here comes my AnalysisController and the corresponding facade pattern
#app/controllers/analyses_controller.rb
class AnalysesController < ApplicationController
before_action :set_analysis, only: :show
def show
#analysis_facade = AnalysisFacade.new(#analysis)
end
private
def set_analysis
#analysis ||= Analysis.create
end
end
#app/facades/analysis_facade.rb
class AnalysisFacade
attr_reader :analysis
def initialize(analysis)
#analysis = analysis
end
def new_input_file
#input_file = analysis.build_input_file
end
def input_file
#input_file ||= new_input_file
end
def access_data
#access_data ||= analysis.input_file.access_data
end
end
My controller for InputFiles
#app/controllers/input_files_controller.rb
class InputFilesController < ApplicationController
include LogFileScanner
before_action :set_analysis, only: [:new, :create, :update]
def new
#input_file = Input_file.new
end
def create
#input_file = #analysis.build_input_file(input_file_params)
if #input_file.save
access_data = scan(#input_file.data)
#input_file.access_data.create(access_data)
end
end
def update
#input_file = #analysis.input_file
if #input_file.update
access_data = scan(#input_file.data)
#input_file.access_data.update(access_data)
end
end
private
def input_file_params
params.require(:input_file).permit(:uploaded_file, :analysis_id)
end
def set_analysis
#analysis = Analysis.find(params[:input_file][:analysis_id])
end
end
For the file upload I have these views
#app/views/analyses/show.html.erb
<%= render template: 'input_files/get' %>
#app/views/input_files/get.html.erb
<%= form_with model: [#analysis_facade.analysis, #analysis_facade.input_file], action: :update, html: {multipart: true, class: 'form-horizontal center'} do |form| %>
<div class='form-group.new'>
<% if false %>
<%= form.hidden_field :analysis_id, value: #analysis_facade.analysis[:id] %>
<% end %>
<%= form.file_field 'uploaded_file' %>
<%= form.submit "Upload", class: 'btn btn-default btn-primary' %>
</div>
<% end %>
Executing this results in error
undefined method `analysis_input_files_path' for #<#<Class:0x00007f1208040288>:0x00007f11f0d71d48>
Did you mean? analysis_path
for this loc
<%= form_with model: [#analysis_facade.analysis, #analysis_facade.input_file], action: :update, html: {multipart: true,
class: 'form-horizontal center'} do |form| %>
Why is that? I need to pass the analysis_id to params in order to create the new InputFile. But I want it to be cleaner than manually adding a hidden_field, since model_with should provide this feature. What do I miss here?
Also: Why does rails search for `analysis_input_files_path'? Its clearly a has_one relation, so shouldn't it look for analysis_input_file_path then?
EDIT:
#bin/rails routes
Prefix Verb URI Pattern Controller#Action
analyses GET /analyses(.:format) analyses#index
POST /analyses(.:format) analyses#create
new_analysis GET /analyses/new(.:format) analyses#new
edit_analysis GET /analyses/:id/edit(.:format) analyses#edit
analysis GET /analyses/:id(.:format) analyses#show
PATCH /analyses/:id(.:format) analyses#update
PUT /analyses/:id(.:format) analyses#update
DELETE /analyses/:id(.:format) analyses#destroy
input_file_access_data GET /input_files/:input_file_id/access_data(.:format) access_data#index
POST /input_files/:input_file_id/access_data(.:format) access_data#create
new_input_file_access_datum GET /input_files/:input_file_id/access_data/new(.:format) access_data#new
edit_access_datum GET /access_data/:id/edit(.:format) access_data#edit
access_datum GET /access_data/:id(.:format) access_data#show
PATCH /access_data/:id(.:format) access_data#update
PUT /access_data/:id(.:format) access_data#update
DELETE /access_data/:id(.:format) access_data#destroy
input_files GET /input_files(.:format) input_files#index
POST /input_files(.:format) input_files#create
new_input_file GET /input_files/new(.:format) input_files#new
edit_input_file GET /input_files/:id/edit(.:format) input_files#edit
input_file GET /input_files/:id(.:format) input_files#show
PATCH /input_files/:id(.:format) input_files#update
PUT /input_files/:id(.:format) input_files#update
DELETE /input_files/:id(.:format) input_files#destroy
analyses_show GET /
I was a conceptional mistake. I changed the facade model to
class AnalysisFacade
attr_reader :analysis
def initialize(analysis)
#analysis = analysis
end
def new_input_file
#analysis.build_input_file
end
def input_file
#analysis.input_file ||= new_input_file
end
def access_data
#analysis.input_file.access_data
end
end
And the view works now like this
<%= form_with model: #analysis_facade.input_file, html: {multipart: true, class: 'form-horizontal center'} do |form| %>
<div class='form-group.new'>
<%= form.file_field 'uploaded_file' %>
<%= form.submit "Upload", class: 'btn btn-default btn-primary' %>
</div>
<% end %>
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
I'm currently trying to build a really simple nested form app in my quest to learn rails. In this app I have three models legal_form answer and question. I have my my answers.html.erb, set up as follows:
<%= form_for (#legal_form) do |f| %>
<h1>Title <%= #legal_form.title %></h1>
<p><%= #legal_form.description %></p>
<ul>
<% #legal_form.questions.each do |question| %>
<%= fields_for question.answers.build do |q| %>
<li>
<%= question.question_content %>
<%= q.text_field :answer_content %>
</li>
<% end =%>
<% end %>
</ul>
<p><%= f.submit "Submit" %></p>
<% end %>
Which currently grabs the three questions I have stored and renders text input boxes next to them; works without a problem. However, when I submit the values, I get the "param is missing or empty: legal_form".
I figure that this is most likely due to my strong params configuration in the legal_forms controller, see below.
class LegalFormsController < ApplicationController
before_action :find_legal_form, only: [:show, :edit, :update, :destroy, :answers]
def index
#legal_form = LegalForm.all.order("created_at DESC")
end
def show
end
def new
#legal_form=LegalForm.new
end
def create
#legal_form = LegalForm.new(legal_form_params)
if #legal_form.save
redirect_to #legal_form, notice: "Successfully created new legal form."
else
render 'new'
end
end
def edit
end
def update
if #legal_form.update(legal_form_params)
redirect_to #legal_form
else
render 'edit'
end
end
def destroy
#legal_form.destroy
redirect_to root_path, notice: "Successfully deleted form"
end
def answers
#questions=#legal_form.questions
#legal_form=LegalForm.find(params[:id])
end
private
def legal_form_params
params.reqire(:legal_form).permit(:title, :description, :questions_attribute => [:id, :question_number, :question_content, :_destroy, :answer_attributes => [:id, :answer_content, :question_id, :user_id]])
end
def find_legal_form
#legal_form=LegalForm.find(params[:id])
end
end
And, in case it's helpful, here are the models for each.
class Answer < ActiveRecord::Base
belongs_to :question
end
class LegalForm < ActiveRecord::Base
has_many :questions, :dependent => :destroy
has_many :answers, through: :entity_roles
accepts_nested_attributes_for :questions,
reject_if: proc { |attributes| attributes['question_content'].blank? },
allow_destroy: true
end
class Question < ActiveRecord::Base
belongs_to :legal_form
has_many :answers
accepts_nested_attributes_for :answers,
reject_if: proc { |attributes| attributes['question_content'].blank? },
allow_destroy: true
end
Also, as requested here's my routes file:
Rails.application.routes.draw do
resources :legal_forms do
member do
get 'answers'
end
end
resources :answers
root "legal_forms#index"
end
Any help to finally conquer nested forms would be greatly appreciated. I've been banging my head against it off and on for about a week now. Many thanks in advance.
Try in the controller
def legal_form_params
params.require(:legal_form).permit(...)
end
Also question.answers.build add it to the method of your controller and call the object that returns the responses to fields_for
UPDATE
To be sent through this form your results, should probably be like this
form
<%= f.fields_for :answers do |q| %>
...
<% end =%>
in the new method
def new
#legal_form=LegalForm.new
#answers = #legal_form.question.answers.build
end
def legal_form_params
params.require(:legal_form).permit! #temporarily
end
not tried it, but imagine how it works something like this