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.
Related
I'm making simple CRUD and the current goal is to add data. However, I found that I can't add any data, and the terminal log also shows "[Webpacker] Everything's up-to-date. Nothing to do", which means there is no error message.
According to my design in the controller, the new data must have failed, so I stopped at new.html.erb. I'm guessing it has something to do with the model's relationship.
This is model User
class User < ApplicationRecord
has_many :reviews
has_many :recipes, through: :reviews
end
This is the model Recipe
class Recipe < ApplicationRecord
has_many :reviews
belongs_to :user
end
This is model Review
class Review < ApplicationRecord
belongs_to :user
belongs_to :recipe
end
This is the RecipeController
class RecipesController < ApplicationController
def index
#recipes = Recipe.all
end
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(recipe_params)
if #recipe.save
redirect_to recipes_path, notice: "Successful!"
else
render :new
end
end
private
def recipe_params
params.require(:recipe).permit(:title, :money)
end
end
this is the web page
<h1>Add New One</h1>
<%= form_for(#recipe) do |r| %>
<%= r.label :title, "Title" %>
<%= r.text_field :title%>
<%= r.label :money, "Budget" %>
<%= r.text_field :money %>
<%= r.submit %>
<% end %>
<%= link_to "Back to list", recipes_path %>
You should first add a callback to ensure that only signed in users can create recipes (unless you actually want to let anomynous users create/update/delete recipies).
For example with Devise you would use its authenticate_user! helper which will bail and redirect to the sign in path if the user is not authenticated:
class RecipesController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
# ...
end
If you're reinventing the authentication wheel you should create a similiar method which is used to prevent access.
You would then initialize the resource off the current user:
class RecipesController < ApplicationController
before_action :authenticate_user!, except: [:show, :index]
def create
#recipe = current_user.recipes.new(recipe_params)
if #recipe.save
redirect_to recipes_path, notice: "Successful!"
else
render :new
end
end
end
Here I am assuming that you have a current_user method which will retrieve the user based on an id stored the session.
Since you have an indirect assocation this will create a row in the reviews table with the users id and the recipe id as the record in the recipies table.
You also want to display the validation errors in the form so that the user gets feedback.
You are probably right that it is a validation error related to the belongs_to relationship. You should display validation errors for your form as described here https://guides.rubyonrails.org/getting_started.html#validations-and-displaying-error-messages
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.
I have someone of a unique problem. I have 3 tables in the database that I need to populate with data. All tables are in relation to each other. The first table's info will be static and populated from a hash. The second table is the table that is usually targeted with data.
I am having a tough time trying to add data into the second table using strong parameters. I get an error param is missing or the value is empty: entries
Modles:
client.rb
class Client < ActiveRecord::Base
has_many :entries
end
Entry.rb
class Entry < ActiveRecord::Base
belongs_to :client_name
has_many :extra_data
end
extra_data.rb
class ExtraData < ActiveRecord::Base
belongs_to :entries
end
class ClientsController < ApplicationController
before_action :set_client, only: [:show, :update, :destroy, :edit]
# submit for all intended purposes.
#
def new
#entries = Entry.new()
end
def create
#client = Client.new(CLEINT_ATTR)
if #client.save
#entries = Entry.new(submit_params)
redirect_to action: :index
else
flash.alert "you failed at life for today."
redirect_to action: :index
end
end
.
.
.
private
def submit_params
params.require(:entries).permit( :full_name,:email,:opt_in )
end
def set_client
#client = Client.find(params[:id])
end
end
form
<%= simple_form_for(:client, url: {:controller => 'clients', :action => 'create'}) do |f| %>
<%= f.input :full_name %>
<%= f.input :email %>
<%= f.input :opt_in %>
<%= f.button :submit, class: "btn-primary" %>
<% end %>
Routes:
Rails.application.routes.draw do
resources :clients do
resources :entries do
resources :extra_data
end
end
root 'clients#index'
end
In the Database Client data goes in with out a problem. I am having a problem getting the data from the form itself.
This answer is the culmination of a few different parts.
I figured out I was not saving any data into the model. So I needed to make another if statement.
def create
#client = Client.new(CLEINT_ATTR)
if #client.save
#entries = Entry.new(submit_params)
if #entries.save
flash[:alert] = "Failure! everything is working."
redirect_to action: :index
else
flash[:alert] = "Success! at failing."
end
else
flash[:alert] = "you failed at life for today."
redirect_to action: :thanks
end
end
Also changing the form from :entries Helped. I also had a typo in my permit statment. I had :opt_in when I needed to use :optin Thanks #tmc
Im new to rails and am having difficulty creating a profile by a devise user
here is the ProfileController:
class ProfilesController < ApplicationController
before_action :set_profile, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#profiles = Profile.all
end
def new
#profile = current_user.build_profile
end
def create
#profile = current_user.build_profiles(profile_params)
redirect_to profiles_path
end
def show
end
def edit
end
def update
#profile.update(profile_params)
redirect_to(profiles_path(#profile))
end
def destroy
#profile.destroy
redirect_to profiles_path
end
private
def profile_params
params.require(:profile).permit(:university, :degree)
end
def set_profile
#profile = Profile.find(params[:id])
end
end
When i run the rails server i can submit the form but nothing is getting stored in the model 'Profile'
here is the index.html.erb where the data should appear:
<h2>profiles</h2>
<% #profiles.each do |profile| %>
<%= link_to profile do %>
<%= profile.university %>
<% end %>
<%= profile.degree %>
<% end %>
user.rb file:
has_one :profile
and the profile.rb file:
belongs_to :user
nothing seems to be getting saved to the profile model and nothing is getting displayed on the index.html.erb. Also i have created a migration to store user_id in the profile model.
Thank for your help
By far the best way to create a profile for a user is to build it when the User object is created:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :profile
before_create :build_profile
accepts_nested_attributes_for :profile
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
This will build a blank profile each time a new user is created. It means that each user will only ever have one profile, which they'll be able to populate & edit.
In regards your issue, there are several points:
Don't list "profiles" with an index, list users & pull their profile data
If managing a profile, nest it under the associative user model
Here's how to do that:
# config/routes.rb
resources :users, only: :index
resource :profile, only: [:show, :update]
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def show
end
def update
redirect_to :show if current_user.update profile_params
end
private
def profile_params
params.require(:user).permit(profile_attributes: [:name])
end
end
#app/views/profiles/show.html.erb
<%= form_for current_user, url: profile_path do |f| %>
<%= f.fields_for :profile do |p| %>
<%= p.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
Update
My post above is exactly what we do.
The way it works is very simple -- when a User is created (IE they have gone to the trouble of filling out their details), the Rails backend automatically creates a blank Profile object.
This does several things:
Always makes sure you have a Profile for each user (you don't have to go to the bother of making them "create" a profile).
Gives you the ability to validate only inputted data on a created Profile (not having to guess whether it's already been done).
--
If you're getting undefined method build_profile, it means your associations are incorrect.
All singular associations have build_[association] as defined instance methods. The code I provided fires build_profile for a has_one association. The only time it would be "undefined" would be if the association was plural
--
Update
This suggests a routing error.
Considering it appears at root, I think the problem is here:
#app/views/layouts/application.html.erb
<%= link_to "Profile", profile_path %>
You don't have edit_profile_path -- it should just be profile_path
It needs a save call for the profile which you build in crate method.
e.g.
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url
else
render :new
end
end
if condition checks is data save correct or not. And change example's User model to your Profile model
BTW, just careful with the plural, I think it should use 'build_profile' instead 'build_profiles'
How to configure the rails controller so I can have a user post a submission in no matter what contest. When they post their user id and the contest id should be automatically appended to the submission.
I know I can do:
User.first.contests.create => let the user create a contest
Contest.first.submissions.create => create a submission in a contest (not linked to a user)
User.first.submissions.create => create a submission linked to a user but not to a contest
I cannot do User.first.Contest.last.submissions.create => I want to link a submission to a contest and to a submission.
Is there an elegant way to fix this?
The submission controller looks like this:
class SubmissionsController < ApplicationController
before_action :set_submission, only: [:show, :edit, :update, :destroy]
# the current user can only edit, update or destroy if the id of the pin matches the id the user is linked with.
before_action :correct_user, only: [:edit, :update, :destroy]
# the user has to authenticate for every action except index and show.
before_action :authenticate_user!, except: [:index, :show]
respond_to :html
def index
#title = t('submissions.index.title')
#submissions = Submission.all
respond_with(#submissions)
end
def show
#title = t('submissions.show.title')
respond_with(#submission)
end
def new
#title = t('submissions.new.title')
#submission = Submission.new
respond_with(#submission)
end
def edit
#title = t('submissions.edit.title')
end
def create
#title = t('submissions.create.title')
#submission = Submission.new(submission_params)
#submission.save
respond_with(#submission)
end
def update
#title = t('submissions.update.title')
#submission.update(submission_params)
respond_with(#submission)
end
def destroy
#title = t('submissions.destroy.title')
#submission.destroy
respond_with(#submission)
end
private
def set_submission
#submission = Submission.find(params[:id])
end
def submission_params
arams.require(:submission).permit(:reps, :weight, :user_id)
end
def correct_user
#submission = current_user.submissions.find_by(id: params[:id])
redirect_to submissions_path, notice: t('submissions.controller.correct_user') if #submission.nil?
end
end
I have following models:
class Contest < ActiveRecord::Base
has_many :submissions
has_many :users, through: :submissions
class Submission < ActiveRecord::Base
belongs_to :user
belongs_to :contest
class User < ActiveRecord::Base
has_many :submissions
has_many :contests, through: :submissions
I think you're making this a bit complicated.
Submission is POSTED within Contest, Submission needs to know the user_id.
<%= simple_form_for :submission, url: contest_submissions_path(contest) do |f| %>
...
<%= f.submit 'Submit', class: "button" %>
<% end %>
And on your submissions CREATE method
class SubmissionsController < ApplicationController
def create
#contest = Contest.find(params[:contest_id])
#submission = #contest.submissions.new(submission_params)
#submissions.user = current_user
.....
end
The magic happens at #submissions.user = current_user If you are using Devise, it is easy to pass in the current_user.id ANYWHERE in the controller, as I just did in the submissions controller.
Are you able to use #submission = current_user.submissions.new(submission_params) and #contest = Contest.find(params[:contest_id]) in your SubmissionsController#create
EDIT: I've added some details on adding a reference to contest_id in the submissions table.
The best way I've found to tie related things together in Rails (and indeed, any relational database) is to add a reference in the child table to the parent's id. You can do this with a migration in Rails.
rails g migration AddContestToSubmission contest:references
And modify the migration file generated in your db/migrate/<datetime>_add_contest_to_submission to look similar to:
class AddContestToSubmission < ActiveRecord::Migration
def change
add_reference :submissions, :contest, index: true
end
end
Then go ahead and look at your submissions table in your schema.rb. You should notice something like t.integer "contest_id" You should probably also add the user_id in your migration is you want a submission to be tied to one user.