Rails: ActionController::UrlGenerationError, No route matches, missing required keys - ruby-on-rails

The following error is displayed when I try to create new data.
ActionController::UrlGenerationError (No route matches {:room_id=>nil, :action=>"index", :controller=>"events"} missing required keys: [:room_id]):
models
models/rooms.rb
has_many :events, inverse_of: :room, dependent: :destroy
has_many :amounts, inverse_of: :room, dependent: :destroy
accepts_nested_attributes_for :events, allow_destroy: true
models/events.rb
has_one :amount, inverse_of: :schedule, dependent: :destroy
accepts_nested_attributes_for :amount, allow_destroy: true
routes.rb
...
resources :events, only: [:new, :create, :edit, :update]
resources :rooms do
resources :events
end
...
When I click link_to for new_room_event_path(1), the above error is displayed.
It generates root/rooms/1/events/new.
view
<% if logged_in? %>
<% if current_user?(#user) %>
<% if schedule.rooms.blank? %>
<%= link_to "Add event", new_room_event_path(1), class: "btn btn-sn btn-primary" %>
<br>
<% end %>
<% end %>
<% end %>
The reason why I designate new_room_event_path(1) is that this is first time to create data.
events_controller.rb
before_action :load_room
def new
#event = Event.new
#event.room = #room
#event.build_amount
#event.amount.schedule = #room.schedule
#event.amount.room = #room
end
private
def load_room
if Room.exists?(id: params[:room_id])
#room = Room.find(params[:room_id])
else
#room = Room.new
end
end
It could be appreciated if you could give me any suggestion.

First, I would recommend removing the resources :events, only: [:new, :create, :edit, :update] from the routes file since you are using events as a nested resource under rooms.
Second, if you are creating a room that doesn't exist, it would probably be better to send them to the actual new_room_path where a room can be created, and you can make it a nested form if you want them to be able to add event(s) at the same time as creating the new room. If the room does already exist, then you can use the nested route as it was designed with new_room_event_path(room)
Looks like you were missing an association to room from your event model, don't forget
# models/event.rb
belongs_to :room
And then from your EventsController you can do this and not worry about a nil room
# controllers/events_controller.rb
before_action :set_room
before_action :set_event, only: [:show, :edit, :update, :destroy]
private
def set_room
#room = Room.find(params[:room_id])
end
def set_event
#event = Event.find(params[:id])
end
See http://guides.rubyonrails.org/routing.html#nested-resources for more details on nested resources

In your events controller, in the new action, change it to #room = #event.room. Your error is saying that it can't find a room_id. The way you have written it in your controller you are asking your new method to set #event.room to #room, but it's the first time the method has seen #room and has no idea what it is. By saying #room = #event.room, the new action understands as you have already, in the lines above, defined what #event is and so assigns the room_id to that #event.

Related

How do you toggle the state from a user with buttons on a page from another table in rails?

I have a rails application with Events and Users and compared them in a third table UserEventStates, which belongs to Users and Events and has an integer named state and makes it possible to set the state for an user for any event separately/different. Now I would like to update the state for the user for this event when the matching button was clicked, how can I do this with Turbo?
# models/event.rb
class Event < ApplicationRecord
has_many :user_event_states
has_many :users, through: :user_event_states
accepts_nested_attributes_for :user_event_states
end
# models/user.rb
class User < ApplicationRecord
has_many :user_event_states
has_many :events, through: :user_event_states
accepts_nested_attributes_for :user_event_states
end
# models/user_event_state.rb
class UserEventState < ApplicationRecord
belongs_to :event
belongs_to :user
end
Updated answer:
If I got the relationships right:
# models/event.rb
class Event < ApplicationRecord
has_many :user_event_states
has_many :users, through: :user_event_states
end
# models/user.rb
class User < ApplicationRecord
has_many :user_event_states
has_many :events, through: :user_event_states
accepts_nested_attributes_for :user_event_states
end
# models/user_event_state.rb
class UserEventState < ApplicationRecord
belongs_to :event
belongs_to :user
accepts_nested_attributes_for :user_event_states
end
And I understand your goal:
I would like to have buttons for each user and update only the state from the selected user
Then here are some things I would change:
Create a UserEventStatesController:
# app/controllers/user_event_states_controller.rb
class UserEventStatesController < ApplicationController
before_action :set_event
before_action :set_user_event_state, only: %i[show edit update destroy]
before_action :set_user, only: %i[show edit update destroy]
...
def update
if #user_event_state.update(user_event_state_params)
# action to perform on success
else
# action to perform on fail
end
end
...
private
def set_event
#event = Event.find(params[:event_id])
end
def set_user_event_state
#user_event_state = #event.user_event_states.find(params[:id])
end
def set_user
#user = #user_event_state.user
end
def user_event_state_params
params.require(:user_event_state).permit(:event_id, :user_id, :state)
end
end
And change your EventsController back to a more standard controller:
#app/controllers/events_controller.rb
before_action :set_event, only: %i[ show edit update destroy]
def show
# get *ALL* UserEventStates for this Event
#ues_events = UserEventState.where(event_id: #event.id)
end
private
def set_event
#event = Event.find(params[:id])
end
def event_params
params.require(:event).permit(:description, :date, :meeting_time, :start_time, :end_time)
end
Nest UserEventState routes under Event:
# config/routes
devise_for :users
resources :events do
resources :user_event_states
end
Run rails routes from your terminal to check the prefixes of the routes. Should look like this:
$ rails routes
Prefix Verb URI Pattern Controller#Action
...
event_user_event_states GET /events/:event_id/user_event_states user_events_states#index
POST /events/:event_id/user_event_states user_events_states#create
new_event_user_event_state GET /events/:event_id/user_event_states/new user_events_states#new
edit_event_user_event_state GET /events/:event_id/user_event_states/edit user_events_states#edit
event_user_event_state GET /events/:event_id/user_event_states/show user_events_states#show
PATCH /events/:event_id/user_event_states/:id user_events_states#update
PUT /events/:event_id/user_event_states/:id user_events_states#update
DELETE /events/:event_id/user_event_states/:id user_events_states#destroy
Now to views:
#app/views/events/show.html.erb
<% #ues_events.each do |ues_ev| %>
<%= ues_ev.user.email %>
<%= ues_ev.state %>
<%= ues_ev.night %>
<% form_with model: ues_ev, url: [#event, ues_ev] do |form| %>
<%= form.text_field :state %> <!-- or whatever kind of input :state needs to be -->
<%= form.submit 'Zusagen' %>
<% end %>
The form should submit a PATCH request to /events/:event_id/user_event_states/:id, which will hit the UserEventStatesController#update action.
The params[:user_event_state] should be { state: 'form response' }
upgrade idea:
Submitting a form and getting redirected can be slow and not very smooth in terms of user experience.
Say you want to say right on the events#index page and just click buttons to toggle the state for a few users.
You could look at replacing your forms with buttons or links that use:
Rails 6 and prior: unobtrusive javascript & AJAX to submit data remotely. Here's a tutorial I recommend
Rails 7: use Turbo
First answer:
get 'right route' can be whatever you want, as long as it is a viable URI path.
Any symbols in the path are accessible via params
So get 'events/:events_id/users/:id/update-event-state' to: ...
Would have params[:event_id] and params[:id].
Since it looks like you need only :event_id params, it doesn't have to include user.
get 'events/:event_id/user-state', to: ...
But it also doesn't have to follow convention:
get 'foo/bar/baz/:event_id', to: ...
If you want to nest the route, you can, but you lose control over what the URL will be:
resources :events do
# url would be events/:id/update_user_event_state
get :update_user_event_state, on: :member
resources :users do
# url would be events/:event_id/users/update_user_event_state
get :update_user_event_state, on: :collection
# url would be events/:event_id/users/:user_id/update_user_event_state
get :update_user_event_state, on: :member
end
end
Also, I think you have a typo. It should be like this, I think:
def update_user_event_state
#ues_event.update(state: 1)
# not self.update
end

rails nested resources no route matches missing required key [:id]

I have a very simple case of nested resources but I am having trouble to get them to work.
My models:
class TodoList < ActiveRecord::Base
has_many :todo_items, dependent: :destroy
class TodoItem < ActiveRecord::Base
belongs_to :todo_list
My controller:
class TodoItemsController < ApplicationController
before_action :set_todo_list
before_action :set_todo_item, only: [:show, :edit, :update, :destroy]
def show
end
private
def set_todo_item
#todo_item = #todo_list.todo_items.find(params[:id])
end
def set_todo_list
#todo_list = TodoList.find(params[:todo_list_id])
end
My show.html.erb:
<%= link_to 'Edit', edit_todo_list_todo_item_path([#todo_item.todo_list, #todo_item]) %>
I got the error
"No route matches {:action=>"edit", :controller=>"todo_items", :id=>nil, ..., missing required keys: [:id].
I know the todo_item_id is missing, but I couldn't figure out why. When I debug, I saw that both #todo_list and #todo_item were getting values. But as soon as the #todo_item was assigned, this error would rise. What did I do wrong? How can I correct this? Any insights will be appreciated.
Try this:
<%= link_to 'Edit', edit_todo_list_todo_item_path(#todo_item.todo_list, #todo_item) %>
or:
<%= link_to 'Edit', edit_todo_list_todo_item_path(todo_list_id: #todo_item.todo_list.id, id: #todo_item.id) %>

Why after validation of post title, when I have got validation error, browser passes to Rails server the previous value of post title?

I get posts by :title instead :id
routes.rb:
Rails.application.routes.draw do
#RSS
get 'feed' => 'posts#feed'
get 'archive' => 'posts#archive'
devise_for :users, controllers: { omniauth_callbacks: "callbacks" }
root to: "posts#index"
resources :posts, param: :title do
resources :comments, only: [:new, :create, :destroy]
resources :images do
resources :comments, only: [:new, :create]
end
resources :links do
resources :comments, only: [:new, :create]
end
resources :photos, only: [:new, :create,:destroy]
resources :songs, only: [:new, :create, :destroy]
end
post_controller:
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create, :show]
respond_to :html, :json, :rss, :atom
def index
if params[:search].blank?
#posts = Post.includes(:comments, :photos).all
else
#search = Post.search do
fulltext params[:search]
end
#posts = #search.results
end
respond_with(#posts)
end
def show
set_post
end
def new
#post = Post.new
end
def edit
#post = Post.find_by(title: params[:title])
end
def create
#post = Post.new(post_params)
#post.errors.add(:base, :invalid) unless #post.save
respond_with(#post)
end
def update
set_post # this #post dont get post becouse browser pass wrong title and rails dont find it
if #post.valid? # here error becouse #post hasnt find
#post.update(post_params)
else
#post.errors.add(:base, :invalid)
end
respond_with(#post)
end
def destroy
set_post
#post.destroy
respond_with(#post)
end
def feed
#posts = Post.all.reverse
respond_with(#posts)
end
def archive
#posts_by_year = Post.limit(300).all.order("created_at DESC").
group_by {|post| post.created_at.beginning_of_year}
end
private
def set_post
#fix N+1 queries and find by posts title
#post = Post.includes(:comments, :photos, :links).find_by(title: params[:title])
end
def post_params
params.require(:post).permit(:title, :content)
end
end
When I create new post, if I include in my post title a dot, I get from Rails error. Therefore I use validation format: method for this case.
post.rb:
class Post < ActiveRecord::Base
#overwrite the to_param for changing routes from posts/id to posts/title
def to_param
title
end
has_many :comments, dependent: :destroy, as: :commentable
has_many :images, dependent: :destroy
has_many :links, dependent: :destroy
has_many :photos, dependent: :destroy
has_many :songs, dependent: :destroy
validates :content, presence: true
validates :title, presence: true, length: { maximum: 100 }
validates :title, format: { without: /\./,
message: "must be without dot" }
searchable do
text :title, :content
end
end
After this, when I update the post,my validation format method works and I get my validation message 'must be without dot'. Well. I delete in my post title input field dots and submit form. Now browser send to server previous post title value with dot.
Started PATCH "/posts/my%20test%20post%20title%20with%20dot." for 127.0.0.1 at 2017-01-26 11:57:34 +0300
ActionController::RoutingError (No route matches [PATCH] "/posts/my%20test%20post%20title%20with%20dot."):
Therefore rails can't find post by title with dot an I get the error.
What can I solve this problem? Maybe :title instead :id in this case is bad idea?
I assume you're using form_for? Rails will set the title on the post object then run validation. When it fails it will display the edit view, form_for will then use the to_param method to set the url, which will use the posts updated title. When you try and update again it'll use the title from the url to try and find the post, but won't be able to because there isn't a post in the database with that title.
You should use something like friendly_id but if you really want to roll your own then a simple implementation would be to have a slug column which gets set based on the title after_validation but obviously you're going to have to make sure it's unique so personally I'd switch to friendly id or another gem that deals with slugs.

Problems using namespaced models and controllers in Rails

I'm developing a Ruby on Rails app and I'm trying to organize my code a bit better. Part of the app is a knowledgebase which is organized into categories and articles - pretty standard stuff.
Here is the way I have everything set up (at least what I think is important):
routes.rb
namespace :knowledgebase do
resources :categories
resources :articles
end
models/knowledgebase/article.rb
class Knowledgebase::Article < ActiveRecord::Base
self.table_name = 'knowledgebase_articles'
belongs_to :category, class_name: 'Knowledgebase::Category', foreign_key: 'knowledgebase_category_id'
end
models/knowledgebase/category.rb
class Knowledgebase::Category < ActiveRecord::Base
self.table_name = 'knowledgebase_categories'
has_many :articles, class_name: 'Knowledgebase::Article'
end
controllers/knowledgebase/articles_controller.rb
class Knowledgebase::CategoriesController < ApplicationController
load_and_authorize_resource
before_action :authenticate_user!
before_action :set_category, only: [:show, :edit, :update, :destroy]
def new
#category = ::Knowledgebase::Category.new
end
def create
#category = ::Knowledgebase::Category.new(category_params)
if #category.save
redirect_to knowledgebase_categories_url, notice: '...'
else
render :new
end
end
# ...
private
def set_category
#category = ::Knowledgebase::Category.find(params[:id])
end
def category_params
params.require(:category).permit(:title)
end
end
views/knowledgebase/categories/_form.html.erb
<%= form_for #category do |form| %>
<%= form.text_field :title
<%= form.submit %>
<% end %>
My Problem: when I try to submit the form (either new or edit) I get the error:
param is missing or the value is empty: category
When I setup the form like this: <%= form_for ([:knowledgebase, #category]) do |form| %> I get the following error:
undefined method `knowledgebase_knowledgebase_categories_path' for #<#<Class:0x007fecd7a78908>:0x007fecd707ad20>
The same goes for articles. I'm using Rails 4.2.3.
If anybody could help me or guide me into the right direction I would be really thankful - I have googled for three days now I tried everything I could think of.
Update 1
This is the params log generated:
{"utf8"=>"✓",
"authenticity_token"=>"fjOs...VA==",
"commit"=>"Category erstellen",
"knowledgebase_category"=>{"title"=>"Test Title"}}
As you look into the params hash, it has knowledgebase_category not category. So try changing category_params to the below
def category_params
params.require(:knowledgebase_category).permit(:title)
end
An Advice:
Always look into the generated params in the log. It really helps a lot in finding the solution to most of the errors.

Rails 4 Devise, Pundit, Join Table ActionView::Template::Error (undefined method `task_definition_path' for #<#<Class:0x37682f95>:0x6919b2b2>):

Here is my use case:
I have one user model with Devise for AuthN and I am using Pundit for AuthZ.
I restrict access to the main application through a subdomain constraint.
I have some pages that are for end users (will be a different LandF at some point) and I have administrative pages etc. Common story you know the drill.
I am using a has_and_belongs_to_many utilizing a join table with :id's
I have my controllers, view directories and migrations named as plurals, my models as singular. Example: TaskDefinition => Model, TaskDefinitions => Controller and Tables.
The default routes are generated and i have provided the content.
I am using partials in the view directories and this is very new issue since a port from Ruby to JRuby.
Stack Trace:
ActionView::Template::Error (undefined method `task_definition_path' for #<#<Class:0x37682f95>:0x6919b2b2>):
10: <div class="col-md-10">
11: <div class="panel-body">
12: <div class="form">
13: <%= bootstrap_form_for #task do |f| %>
14: <div class="form-group">
15: <%= render '/admin/task_definitions/errors' %>
16: </div>
app/views/admin/task_definitions/edit.html.erb:13:in`_app_views_admin_task_definitions_edit_html_erb__1276994696_33458'
Migrations:
class CreateTaskDefinitions < ActiveRecord::Migration
def change
create_table :task_definitions do |t|
# foreign key
t.integer :organization_id
# attributes
....
t.timestamps
end
# index
add_index :task_definitions, :name, unique: true
end
end
class CreateOrganizations < ActiveRecord::Migration
def change
create_table :organizations do |t|
# for the relationship between parent orgs and child nodes
t.references :parent
# Used to determine Parent or Child
t.string :org_type
# Subdomain used for scoping site
t.string :subdomain
# Common fields
....
t.timestamps
end
# index
add_index :organizations, [:name, :subdomain], unique: true
end
end
class CreateOrganizationsTaskDefinitions < ActiveRecord::Migration
def change
create_table :organizations_task_definitions, id: false do |t|
t.integer :organization_id
t.integer :task_definition_id
end
add_index :organizations_task_definitions, [:organization_id, :task_definition_id], name: 'index_organizations_task_definitions'
end
end
models:
class Organization < ActiveRecord::Base
#associations
has_many :users, class_name: 'User', inverse_of: :organization
has_and_belongs_to_many :task_definitions, class_name: 'TaskDefinition', inverse_of: :organizations
has_one :address, class_name: 'Address'
has_many :children, class_name: 'Organization', foreign_key: 'parent_id'
belongs_to :parent, class_name: 'Organization'
accepts_nested_attributes_for :address
end
class TaskDefinition < ActiveRecord::Base
#associations
has_many :steps, class_name: 'TaskStep', inverse_of: :task_definition
has_and_belongs_to_many :organizations, class_name: 'Organization', inverse_of: :task_definitions
has_and_belongs_to_many :task_events, class_name: 'TaskEvent', inverse_of: :task_definitions
accepts_nested_attributes_for :steps
end
Controller:
class Admin::TaskDefinitionsController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized
.....
def edit
#tasks = current_organization.task_definitions
if(#tasks.size > 0 )
#task = #tasks.find(params[:id])
authorize #task
# add breadcrumb
add_breadcrumb #task.name, admin_task_definition_path(#task)
unless current_user.org_super_admin? or current_user.finch_admin?
unless #user == current_user
redirect_to :back, :alert => "Access denied."
end
end
end
end
end
Routes:
Rails.application.routes.draw do
......
constraints(Finch::Constraints::SubdomainRequired) do
#
# dashboards
#
resource :dash_boards, only: [:index, :show, :edit, :update, :destroy]
#
# orgss
#
resource :organizations, only: [:index, :show, :edit, :update, :destroy]
#
# Only Admins are allowed to access
#
namespace :admin do
#
# Workflow Data
#
resources :task_definitions, only: [:index, :show, :edit, :update, :destroy]
resources :task_steps, only: [:show, :edit, :update, :destroy]
resource :task_actions, only: [:show, :edit, :update, :destroy]
resource :task_action_attributes, only: [:show, :edit, :update, :destroy]
resource :task_transitions, only: [:show, :edit, :update, :destroy]
end
end
end
view:
<div class="form">
<%= bootstrap_form_for #task do |f| %>
<div class="form-group">
<%= render '/admin/task_definitions/errors' %>
</div>
rake routes:
edit_organizations GET /organizations/edit(.:format) organizations#edit
organizations GET /organizations(.:format) organizations#show
PATCH /organizations(.:format) organizations#update
PUT /organizations(.:format) organizations#update
DELETE /organizations(.:format) organizations#destroy
admin_task_definitions GET /admin/task_definitions(.:format) admin/task_definitions#index
edit_admin_task_definition GET /admin/task_definitions/:id/edit(.:format) admin/task_definitions#edit
admin_task_definition GET /admin/task_definitions/:id(.:format) admin/task_definitions#show
Undefined method errors ending in _path or _url usually indicate a bad route helper.
Checking the spot of the error, it seems that a helper method (bootstrap_form_for) is calling the route helper task_definitions_path, which is incorrect. That route is namespaced under admin according to your routes file, so the proper route helper is:
admin_task_definitions_path
I don't know what is in the bootstrap_form_for helper method, so I don't have a specific fix for you. Assuming you use the Bootstrap forms gem, skip it and write the form manually.
In the future, rake routes will show all registered route helpers. A handy list for debugging bad route helpers.

Resources