Multi-Step Form debugging Rails - ActionController::ParameterMissing - ruby-on-rails

I understand that this issue has been raised several times, but I can't find to find a way around..
I am using this solution to create a multi-step form without the Wicked gem:
Best way to make Multi-Steps form in Rails 5
It seems that I am able to create a product when I use binding.pry and enter the needed commands inside my rails console.
However, the app in itself does not function and I can't manage to work around it..
Two issues throw the ActionController::ParameterMissing error:
1) First, anytime I intend to press the back_button it raises ParameterMissing error (see at the end for exact error message).
2) When I get to the last_step of the form, it displays all the needed information, but will neither create nor save new products (also ParameterMissing).
Here is my code:
Routes
get 'products/new', to: 'products#new', as: 'new_product'
post 'products', to: 'products#create', as: 'create_new_product'
resources :categories, only: [:index, :show, :new, :edit, :destroy] do
resources :sub_categories, only: [:index, :show, :new, :edit, :destroy]
resources :products, only: [:index, :show, :destroy]
end
Products Controller
class ProductsController < ApplicationController
skip_after_action :verify_authorized, except: :index, unless: :skip_pundit?
skip_after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?
before_action :set_category, only: [:index, :show]
def new
session[:product_params] ||= {}
authorize #product = Product.new(session[:product_params])
#product.current_step = session[:product_step]
#product.user = current_user
end
def create
session[:product_params].deep_merge!(params_product) if params_product
authorize #product = Product.new(session[:product_params])
#product.current_step = session[:product_step]
#product.user = current_user
if #product.valid?
if params[:back_button]
#product.previous_step
elsif #product.last_step?
if #product.all_valid?
#product.save!
flash[:notice] = 'Your product was created successfully'
redirect_to newest_products_path && return
end
else
#product.next_step
end
end
session[:product_step] = #product.current_step
if #product.new_record?
return render :new
else
session[:product_step] = session[:product_params] = nil
end
end
private
def set_category
authorize #category = Category.find(params[:category_id])
end
def params_product
params.require(:product).permit(:name, :price, :description, :category, :category_id,
:sub_category, :sub_category_id, :user, :user_id, :id)
end
end
Product Model
class Product < ApplicationRecord
attr_writer :current_step
belongs_to :user, optional: true
belongs_to :sub_category
belongs_to :category, inverse_of: :products
validates :category, presence: true
validates_presence_of :name, :category_id, if: lambda { |e| e.current_step == "card" }
validates_presence_of :sub_category_id, :description, :price, if: lambda { |e| e.current_step == "details" }
def current_step
#current_step || steps.first
end
def steps
%w[card details confirmation]
end
def next_step
self.current_step = steps[steps.index(current_step) + 1]
end
def previous_step
self.current_step = steps[steps.index(current_step) - 1]
end
def first_step?
current_step == steps.first
end
def last_step?
current_step == steps.last
end
def all_valid?
steps.all? do |step|
self.current_step = step
valid?
end
end
end
New Products View
<%= form_for #product, url: create_new_product_path do |f| %>
<%= render "#{#product.current_step}_step", :f => f %>
<div class="bottom-signup">
<%= f.submit "Continue" unless #product.last_step? %>
<%= f.submit "Submit Product" if #product.last_step? %>
<%= f.submit "Back", :name => "back_button" unless #product.first_step? %>
</div>
<% end %>
Here is the exact error that is thrown by ActionController:
ActionController::ParameterMissing in ProductsController#create
param is missing or the value is empty: product
#around ligne (96)
95 def params_product
96 params.require(:product).permit(:name, :price, :description, :category, :category_id,
97 :sub_category, :sub_category_id, :user, :user_id, :id)
98 end
And finally, here is what appears if I raise params.inspect:
params.inspect
=> "<ActionController::Parameters {\"utf8\"=>\"✓\", \"authenticity_token\"=>\"AHfNwkeFOWBeEup+fCCvfZv1RowuP/YULHjo8kTnzer5YNCY7lMYAKzrrWJBVMcfOO+P1GmZGgi9PDpa/09rzw==\", \"commit\"=>\"Submit Product\", \"controller\"=>\"products\", \"action\"=>\"create\"} permitted: false>"
If someone understands where I'm wrong, I'd be more than glad to discuss it!
Thanks in advance.
Best,
Ist

Related

Rails - routing to a parent show page with a child_id

I would like to route to the parent (journal) show page after deleting the child (habit) using the child_id.
In my app, a User can create journals, which then have multiple habits. I would like to be able to delete (and edit) a habit and then return to the journal show page, which displays all of the habits.
Getting the following and similar errors:
ActiveRecord::RecordNotFound in HabitsController#destroy
journal.rb
class Journal < ApplicationRecord
has_many :entries
has_many :habits
belongs_to :user
end
habit.rb
class Habit < ApplicationRecord
belongs_to :journal
has_many :completed_dates
end
show.html.erb
<h3>Habits</h3>
<% #journal.habits.each do |habit| %>
<div iv class="habit-list">
<div class="habit-name"><%= habit.name %></div>
<div class="habit-status">
<%= simple_form_for [#journal, #habit] do |f| %>
<%= f.input :status, label: false, :inline_label => true %>
<% end %>
</div>
<div>
<%= link_to habit_path(habit), method: :delete do %>
<i class="fas fa-trash btn-edit"></i>
<% end %>
</div>
</div>
<% end %>
habits_controller.rb
class HabitsController < ApplicationController
before_action :set_journal, only: [:new, :create, :edit, :update, :destroy]
before_action :set_habit, only: [:show, :edit, :update, :destroy]
def index
#habits = Habit.all.sort_by &:name
end
def new
#habit = Habit.new
end
def show
end
def create
#habit = #journal.habits.new(habit_params)
if #habit.save
redirect_to journal_path(#journal)
else
render :new
end
end
def edit
end
def update
if #journal.habits.update(habit_params)
redirect_to journals_path(#journal)
else
render :edit
end
end
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
private
def habit_params
params.require(:habit).permit(:name, :status, :user_id, :journal_id)
end
def set_journal
#journal = Journal.find(params[:journal_id])
end
def set_habit
#habit = Habit.find(params[:id])
end
end
Your trying to create URL to a deleted resource (#habit).
Change
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
to
def destroy
#habit.destroy
redirect_to journal_path(#journal)
end

how to make my created award object become assigned to a car object?

I am new to rails and building my first app. I can't figure how to assign a car_id to new instances of awards that get created with the views/awards/new.html.erb file. Can anyone take a look and see where I am going wrong?
awards controller:
class AwardsController < ApplicationController
before_action :set_award, only: [:show]
def new
#award = Award.new
end
def index
#awards = Award.all
end
def create
#car = Car.params
#award = Award.new(award_params)
#award.save
redirect_to user_path(current_user)
end
def show
end
def destroy
end
private
def set_award
#award = Award.find(params[:id])
end
def award_params
params.require(:award).permit(:title, :year, :description)
end
end
cars controller
class CarsController < ApplicationController
before_action :set_car, only: [:show, :edit, :update, :destroy]
def new
#car = Car.new
end
def index
#cars = Car.all
end
def create
#car = Car.new(car_params)
#car.save
current_user.cars << #car
redirect_to current_user, :flash => { :success => "car created!" }
end
def edit
end
def update
#car.update(car_params)
if #car.valid?
#car.save
redirect_to user_path(current_user)
else
render :edit
end
end
def show
#car = Car.find(params[:id])
end
def destroy
#car.destroy
redirect_to user_path(current_user)
end
private
def set_car
#car = Car.find(params[:id])
end
def car_params
params.require(:car).permit(:make,:model,:year,:color)
end
end
car.rb
class Car < ApplicationRecord
belongs_to :user
has_many :awards
end
award.rb
class Award < ApplicationRecord
belongs_to :car
has_one :user, through: :cars
end
routes
Rails.application.routes.draw do
resources :awards
resources :cars
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :users
root 'welcome#home'
resources :users do
resources :cars
end
resources :cars do
resources :awards
end
end
#awards form
<%= form_for #award do |f| %>
<%= f.label :title %>
<%= f.text_field :title %><br>
<%= f.label :year %>
<%= f.text_field :year %><br>
<%= f.label :description %>
<%= f.text_area :description %><br>
<%= f.submit %>
<%end%>
I need to be able to assign awards to specific cars, does anyone know how I can go about doing this?
Since you are using nested routes:
resources :cars do
resources :awards
end
Then:
app/controllers/awards_controller.rb
class AwardsController < ApplicationController
# add more into the array as you need
before_action :set_car, only: [:new, :create]
def new
#award = #car.awards.new
end
def create
#award = #car.awards.new(award_params)
#award.save
redirect_to user_path(current_user)
end
private
def set_car
#car = Car.find(params[:car_id])
end
end
app/views/awards/new.html.erb
<%= form_for [#car, #award] do |f| %>
...
Trivia:
because you are using nested routes, then your links would be something like:
<%= link_to 'New Award', new_car_award_path(SOMECAR) %>
<%= link_to 'Awards', car_awards_path(SOMECAR) %>
<%= link_to 'Edit Award', edit_car_award_path(SOMECAR, SOMEAWARD) %>
<%= link_to 'Delete Award', car_award_path(SOMECAR, SOMEAWARD), method: :delete %>
...just replace SOMECAR and SOMEAWARD with a Car and Award instances respectively, that you want to be in that link.
Recommendations:
handle validations errors. See tutorial
If possible, use shallow nesting

How implement the Has Many Through Association on Controllers and methods

in my app I'm creating the categories in admin(so the admins creates, updates and destroys the categories name) and then when the users will create the posts they will select( or I'm thinking for switch with checkbox) a category for the posts.
I decide to do this implementation with a has many through for posts and categories. But I' having doubts for implements:
the post_params;
the methods for add the categories and then destroy the categories
and the parts for create, update and destroy the posts.
How can I implement this? It's a better way do different? So if someone help me with this I will appreciate.
Post.rb
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories, through: :categorizations
...
def add_category(category)
categorizations.create(category_id: category.id)
end
def remove_category(category)
categorizations.find_by(category_id: category.id).destroy
end
category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, through: :categorizations
validates :name,
presence: true,
length: { minimum: 3, maximum: 30 },
uniqueness: true
end
categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
validates :post_id, presence: true
validates :category_id, presence: true
end
controllers/admin/PostsController
class Admin::PostsController < Admin::ApplicationController
def new
#post = Post.new
#categories = Category.all.map{ |c| [c.name, c.id]}
end
def create
#post = Post.new(post_params)
#post.author = current_user
#post.categories << params[:category_id]
if #post.save
flash[:notice] = "Post has been created."
redirect_to #post
else
flash[:alert] = "Post has not been created."
render "new"
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
flash[:notice] = "Post has been deleted."
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title,
:subtitle,
:content,
:attachment,
:attachment_cache,
:remote_attachment_url,
:categorizations_attributes => [:id,
:post_id,
:category_id,
:_destroy,
:categories_attributes => [:id,
:name]
]
)
end
end
controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update]
def index
#posts = policy_scope(Post)
end
def show
authorize #post, :show?
end
def edit
authorize #post, :update?
end
def update
authorize #post, :update?
if #post.update(post_params)
flash[:notice] = "Post has been updated."
redirect_to #post
else
flash.now[:alert] = "Post has not been updated."
render "edit"
end
end
private
def set_post
#post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The post you were looking for could not be found."
redirect_to posts_path
end
def set_category
#category = Category.find(params[:category_id])
end
def post_params
params.require(:post).permit(:title, :subtitle, :content, :attachment, :attachment_cache, :remove_attachment, :remote_attachment_url, :category_id)
end
end
posts/_form.html.slim
= simple_form_for([:admin, #post], :html => { :multipart => true }) do |f|
= select_tag(:category_id, options_for_select(#categories), :prompt => "Select ad Category")
routes
Rails.application.routes.draw do
namespace :admin do
root 'application#index'
resources :posts, only: [:new, :create, :destroy]
resources :categories
resources :users do
member do
patch :archive
end
end
end
devise_for :users
root "posts#index"
resources :posts, only: [:index, :show, :edit, :update]
end
In your form
= select_tag(:category_ids, options_for_select(#categories), :prompt => "Select ad Category", multiple: true)
In your controller
params.require(:post).permit(:title, :subtitle, :content, :attachment, :attachment_cache, :remove_attachment, :remote_attachment_url, :category_ids)
Also need to give some advise, in your posts_controller.rb
remove below line
rescue ActiveRecord::RecordNotFound
Insterad of this write it in application_controller.rb , so it will work for whole application.
What I do for fix was put on the PostsController this:
class Admin::PostsController < Admin::ApplicationController
before_action :set_categories, only: [:new, :create]
.
.
.
private
def set_categories
#categories = Category.all.select(:id, :name)
end
def post_params
params.require(:post).permit(:title,
:subtitle,
:content,
:attachment,
:attachment_cache,
:remote_attachment_url,
category_ids:[]
)
end
And on the form I changed for use checkboxes and with that I can select and add more categories for a Post:
= f.association :categories, label: "Select the Categories", as: :check_boxes , collection: #categories.map{|c| [c.name, c.id]}, include_hidden: false
Add on the class Post and Category a dependent: :destroy for destroy the joins properly.
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
And was necessary delete the validation for post_id on categorization so I just comment because when was uncommented when I try to create a Post it wasn't possible, So I do this:
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
#validates :post_id, presence: true
validates :category_id, presence: true
end
And work's!

Rails-Simple Form and Devise current_user issue when using model associations

I am using Devise as my authentication system and simple form. I get a NoMethodError in Groups#show and an undefined method 'name' for nil:NilClass error. I use model associations to tie the groups and posts together. When I do puts post.user.name it correctly displays in my terminal but that line causes the above error and it's referencing Groups#show for some reason. Any thoughts?
Routes
resources :groups do
resources :posts
end
Group Model
class Group < ActiveRecord::Base
validates :user_id, presence: true
belongs_to :user
has_many :posts
has_many :comments
has_many :attachments
end
Post Model
class Post < ActiveRecord::Base
validates :user_id, presence: true
validates :caption, presence: true
belongs_to :user
belongs_to :group
has_many :comments, dependent: :destroy
end
Group Controller
class GroupsController < ApplicationController
before_action :authenticate_user!
def new
#group = current_user.groups.build
end
def create
#group = current_user.groups.build(group_params)
#group.user_id = current_user.id
if #group.save
redirect_to groups_path
else
render :new
end
end
...
private
def group_params
params.require(:group).permit(:group_name, :description, :user_id)
end
end
Posts Controller
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :owned_post, only: [:edit, :update, :destroy]
before_action :authenticate_user!
def index
#posts = Post.paginate(page: params[:page], per_page: 3).order('created_at DESC')
#post = current_user.posts.build
#attachments = Attachment.all
end
...
def new
#post = current_user.posts.build
end
def create
#post = current_user.posts.build(post_params)
#group = Group.find(params[:group_id])
#post.group_id = #group.id
if #post.save
redirect_to groups_path
else
render :new
end
end
...
private
def post_params
params.require(:post).permit(:caption, :user_id)
end
def set_post
#post = Post.find(params[:id])
end
def owned_post
unless current_user == #post.user
redirect_to root_path
end
end
end
groups/show.html.erb
<%= render "posts/index" %>
...
posts/_index.html.erb
<%= render 'posts/form' %>
<%= render 'posts/posts' %>
...
posts/_form.html.erb
<%= simple_form_for([#group, #group.posts.build]) do |f| %>
...
posts/_posts.html.erb
<% #group.posts.each do |post| %>
<%= puts post.user.name %> ISSUE
<%#<%= render 'posts/post', post: post %>
<% end %>
SOLUTION: After asking on Reddit Rails, a generous user offered a solution that works. Apparently the <%= simple_form_for([#group, #group.posts.build]) do |f| %> creates a new post and adds it to the groups.posts array and so this causes issues when it iterates over _posts.html.erb and there is no user. More information can be found here: https://www.reddit.com/r/rails/comments/4lygix/unidentified_method_for_nil_class_devise_and/
But replace the above simple_form line of code with <%= simple_form_for([#group, Post.new(group: #group)]) do |f| %> seemed to do the trick, as suggested by the generous user in the reddit link above.

How to create booking according to its pitch?

I have a model pitch where i am fetching grounddetail_id. I want to show all the pitch available in the ground. How i can book pitch of ground..
grounddetails_controller.rb
class GrounddetailsController < ApplicationController
before_action :find_ground, only: [:show, :edit, :destroy, :update]
def index
#grounddetails = Grounddetail.all.order('created_at DESC')
end
def new
#grounddetail = Grounddetail.new
end
def edit
end
def show
end
def create
#grounddetail = Grounddetail.new(ground_params)
if #grounddetail.save
redirect_to #grounddetail
else
render 'new'
end
end
def update
if #grounddetail.update(ground_params)
redirect_to #grounddetail
else
render 'edit'
end
end
def destroy
#grounddetail.destroy
redirect_to root_path
end
private
def find_ground
#grounddetail = Grounddetail.find(params[:id])
end
def ground_params
params.require(:grounddetail).permit(:name, :working_hours, :end_time, :address, :contact_no, :email, :number_of_grounds, :description, :featured_ground)
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users
devise_for :admins
resources :grounddetails do
resources :pitches
end
root "grounddetails#index"
end
model
grounddetail.rb
class Grounddetail < ActiveRecord::Base
has_many :pitches, dependent: :destroy
end
pitch.rb
class Pitch < ActiveRecord::Base
belongs_to :grounddetail
end
for now i just have pitch model and routes but in controller i am confused what to use. i can i book pitch of the ground. But for single ground i am able to book.
In the grounddetails#show action
def show
#pitches = #grounddetail.pitches
end
Then in the groundetails/show.html.erb, you can just use <%= #pitches %> to display the pitches of that grounddetail.
Update:
#in the show.html.erb
<% #pitches.each do |p| %>
<%= form_tag book_pitch_path(p) do %>
#your attributes here
<%= submit_tag "Book this Pitch" %>
<% end %>
<% end %>
#routes.rb
post :book_pitch/:id, to: 'pitches/book_pitch', as: 'book_pitch'
#in pitches_controller.rb
def book_pitch
#your actions here
end

Resources