I'm new to RoR. Wanted to try if my next web app should be RoR based. Started out following this trail: https://guides.rubyonrails.org/getting_started.html. Worked like a charm in the beginning, but i'm unable to get the darn thing to create new records. Any hint as to what i'm missing is appreciated.
The error i'm getting is this:
D, [2020-12-18T09:59:56.917197 #132399] DEBUG -- : Createevent
F, [2020-12-18T09:59:56.917893 #132399] FATAL -- :
ActionController::ParameterMissing (param is missing or the value is empty: Event):
app/controllers/event_controller.rb:33:in `event_params'
My routing looks like this:
Rails.application.routes.draw do
post 'event/new', to: 'event#create'
resources :event
end
(I'm baffled by the need for specifying the POST above, but without it the create is never fired. ).
The eventcontroller looks like this:
class EventController < ApplicationController
def index
#events = Event.all
end
def show
#event = Event.find(params[:id])
end
def new
logger = Rails.logger
logger.info 'NewEvent'
#event = Event.new
end
def create
logger = Rails.logger
logger.debug 'Createevent'
#event = Event.new(event_params)
logger.debug 'Eventcreated'
if #event.save
redirect_to event_path
else
render :new
end
end
private
def event_params
params.require(:Event).permit(:EventName, :Description, :EventStart, :EventEnd, :Maxparticipants, :Waitlist )
end
end
Index and show works fine.
The new.html.erb looks like this:
<h1>New Event</h1>
dsfsdfds
<%= form_with model: #Event do |form| %>
<div>
<%= form.label :eventname %><br>
<%= form.text_field :EventName %>
<%= #event.errors.full_messages_for(:EventName).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :Description %><br>
<%= form.text_field :Description %>
<%= #event.errors.full_messages_for(:description).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :EventStart %><br>
<%= form.text_field :EventStart %>
<%= #event.errors.full_messages_for(:eventstart).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :Eventend %><br>
<%= form.text_field :Eventend %>
<%= #event.errors.full_messages_for(:eventend).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :Maxparticipants %><br>
<%= form.text_field :Maxparticipants %>
<%= #event.errors.full_messages_for(:Maxparticipants).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :Waitlist %><br>
<%= form.text_field :Waitlist %>
<%= #event.errors.full_messages_for(:waitlist).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<%= submit_tag "Create" %>
<% end %>
Routes:
Prefix Verb URI Pattern Controller#Action
event_new POST /event/new(.:format) event#create
event_index GET /event(.:format) event#index
POST /event(.:format) event#create
new_event GET /event/new(.:format) event#new
edit_event GET /event/:id/edit(.:format) event#edit
event GET /event/:id(.:format) event#show
PATCH /event/:id(.:format) event#update
PUT /event/:id(.:format) event#update
DELETE /event/:id(.:format) event#destroy
Things created using:
bin/rails generate model Event EventName:string Description:string EventStart:datetime EventEnd:datetime Maxparticipants:integer WaitList:integer
bin/rails generate controller Event index
Version:
About your application's environment
Rails version 6.0.3.4
Ruby version ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
RubyGems version 3.1.4
Rack version 2.2.3
Thanks to Hackman & nathanvda for clarifying. I scratched everything and started over and i finally got it working. Still way too much woodo and black magic for my taste though. The error message part got me baffled for three consecutive hours.
As stated i followed the guide and therefore ended up using (in new)
<%= form_with model: #event do |form| %>
<% if #event.errors.any? %>
<h2>Errors</h2>
<ul>
<% #event.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
...
Which works (well, sort of). The record gets saved if ok, the validations rules gets fired
class Event < ApplicationRecord
validates :eventname, presence: true
validates :description, presence: true, length: { minimum: 20 }
end
and if violated no record gets written to the database, but no error messages either. Nil. Nothing. After poking around on the internet i ended up changing to
<%= form_for #event do |form| %>
and then error reporting works. Only problem with this solution is that the use of form_for is discouraged as it is being rendered obsolete.
Final version ended up being:
<%= form_with model: #event, local: true do |form| %>
Which does the trick.
Next step in my evaluation will be the use of natural keys as the use of surrogate keys is not an option for some of the data structures needed in this project. (during my poking around i got the impression that natural keys are some kind of a sore tooth in RoR, but time will show.
To start out, your resources in the routes should be pluralized. So resources :event should be resources :events
Also the controller name should be pluralized. So EventController would become EventsController.
Now the needed routes should work fine and you can get rid of the specified POST in your routes.rb
Now inside your controller you have the event_params method. There it is preferred to downcase/snake_case the names like this:
def event_params
params.require(:event).permit(:event_name #etc)
end
If your column names in DB are EventName etc, I would advice to rename them.
Last thing: In your form you got #Event with uppercase while in the controller#new action you defined #event with lowercase. Use lowercase everywhere.
So if you had started as follows:
bin/rails generate model Event event_name:string description:string event\-start:datetime event_end:datetime max_participants:integer wait_list:integer
bin/rails generate controller events index
Then the generated code would work a lot better.
A few tips to clarify:
in ruby we only write classes with a capital, for variables we use snake case (everything lowercase and words connected with underscores). So by extension when generating a model all attributes should be snake cased
a controller in general uses the plural form, since it "controls" all the events (not just one).
Related
I created a button where users can input stuff in a field and then press the button to update the database (put request) which can be seen here in show.html.erb:
<% provide(:title, #user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
<br>
<%= #user.email %>
<% if #errors %>
<p>THE FORM COULD NOT BE SAVED </p>
<ul id='errors'>
<% #errors.each do |error| %>
<li><%= error %></li>
<% end %>
</ul>
<% end %>
<br>
<% if is_admin? %>
<% if !#user.admin %>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#user) do |f| %>
<%= f.label :wistia_project_id %>
<%= f.text_field :wistia_project_id, class: 'form-control' %>
<%= f.submit "Save", :action => "set_wistia_project_ID", :method => :patch, :form_class => "form-control" %>
<% end %>
</div>
</div>
<% end %>
<% end %>
</h1>
</section>
</aside>
</div>
The function is in user_controller.rb:
# Sets wistia_project_ID.
def set_wistia_project_ID
#user = User.find(params[:id])
#user.set_project_id
unless #user.valid?
#errors = #user.errors.full_messages
render :show
end
end
That function calls another function, just to separate things more clearly. This other function lives in user.rb:
# Sets the wistia_project_ID.
def set_project_id!(val)
self.wistia_project_ID = val # self is necessary here
save # or self.save, but the self is unnecessary here
end
My routes.rb:
.
.
.
resources :users do
member do
patch 'set_wistia_project_ID'
end
end
My problem is that right now, when you press the button, it says: Completed 500 Internal Server Error in 26ms (ActiveRecord: 0.7ms)
and
NoMethodError (undefined method `set_project_id' for #<User:0x000055b1a0914ab8>
2019-06-26T14:46:34.940086+00:00 app[web.1]: Did you mean? wistia_project_id):
Zavitoski got it right. I suggest, however, that you're doing a number of things more fundamentally wrong. Given that you're early in your rails journey, I hope you don't mind if I point a few things out.
First, and to be nit-picky, yes, you created a button. But, it is not a button "where users can input stuff in a field and then press the button to update the database". You created a button on a form. And you created a field on that form. The user can input stuff into the field. And when clicked, the button submits the form which includes the information in the field.
Now, on that form, you did:
<%= form_for(#user) do |f| %>
<%= f.label :wistia_project_id %>
<%= f.text_field :wistia_project_id, class: 'form-control' %>
<%= f.submit "Save", :action => "set_wistia_project_ID", :method => :patch, :form_class => "form-control" %>
<% end %>
There are a few things wrong with:
:action => "set_wistia_project_ID"
First, set_wisteria_project_ID is not a very ruby-ish action name. set_wistia_project_id would be more like it. Also, you're using old-form key-value formatting. And, you can use a symbol instead of a string for your action name so your code is prettier. Something, perhaps, like:
<%= f.submit "Save", action: :set_wistia_project_id, method: :patch, form_class: "form-control" %>
But, that's a mistake, too. Because you don't need a set_wistia_project_id action. (It's an action or a method, not a function.) You already have the update action. And form_for is smart enough to submit to this action if #user is an instance of User. So, really, you should do:
<%= form_for #user do |f| %>
<%= f.label :wistia_project_id %>
<%= f.text_field :wistia_project_id, class: 'form-control' %>
<%= f.submit "Save", form_class: "form-control" %>
<% end %>
I'm not sure what form_class is, but I'll trust that it's correct.
Now, in your UsersController, just do:
class UsersController < ApplicationController
def update
#user = User.find(params[:id])
if user.update(user_params)
# do something successful
else
# do something unsuccessful
end
end
private
def user_params
# NOTE: You'll probably want to permit other stuff here, too.
params.require(:user).permit(:wistia_project_id)
end
end
Get rid of this:
class User < ApplicationRecord
# Sets the wistia_project_ID.
def set_project_id!(val)
self.wistia_project_ID = val # self is necessary here
save # or self.save, but the self is unnecessary here
end
end
Because you're just duplicating the update method. And, you probably want that attribute to be wistia_project_id, not wistia_project_ID. (Again, you never see _ID as the suffix in rails core and you might as well be conventional.) And, if you make sure you have your association set up correctly, ActiveRecord should make sure that wistia_project_id is actually a valid value.
And write your routes.rb like this:
resources :users
Because you don't need all that set_wistia_project_id business.
It appears that you are not calling the function by the name you defined, neither passing the parameter (project_id) needed.
def set_wistia_project_ID
#user = User.find(params[:id])
#user.set_project_id!(params[:wistia_project_id])
unless #user.valid?
#errors = #user.errors.full_messages
render :show
end
end
This should use the function you created and pass the parameter from the form.
I'm new to rails and trying to create a new page with a form, fairly simple, but it keeps coming back with this error (listed below) on the <%= form_for #whiteboard do |f| %>line. I've looked at similar posts where people are having the same issue as me but none of the solutions seem to work for me. Would greatly appreciate some help, thanks!
Error Message:
NoMethodError in Whiteboard#new
undefined method `whiteboards_path' for #<#<Class:0x37014d8>:0x36b2d10>
Did you mean? whiteboard_path
<%= form_for #whiteboard do |f| %>
<div>
<% f.label :title %>
<% f.text_field :title %>
</div>
My Controller:
class WhiteboardController < ApplicationController
def index
#whiteboards = Whiteboard.all;
end
def show
#whiteboard = Whiteboard.find(params[:id])
end
def new
#whiteboard = Whiteboard.new
end
end
My View (whiteboard/new.html.erb):
<%= form_for #whiteboard do |f| %>
<div>
<% f.label :title %>
<% f.text_field :title %>
</div>
<div>
<% f.label :description %>
<% f.text_field :description %>
</div>
<div>
<% f.submit :title %>
</div>
<% end %>
My routes:
Rails.application.routes.draw do
root to: 'home#index'
resources :whiteboard
get 'whiteboard/wbpage'
get 'home/index'
get 'home/info'
devise_for :users
end
This is a simple pluralization error.
resources :whiteboards
In Rails resources should always be plural. On the rare occasion that you have a real singular resource you should use resource instead.
My Suggestion please mind it
when you create controller use pluralization
when you create Model use Singular (first letter must be camel case)
For Example
rails g controller articles
rails g model Article
I have been hitting my head against a brick wall so it is time to seek smarter people.
I am trying to create multiple records of one model using form_tag and fields_for. I have been following all the help/issues/guides I can find but it doesn't seem to work for me. I am wondering if it something that changed going to Rails 5 but more likely it is me.
Basically I want a new/create version of the task system listed at the bottom of the api page, similar to this guys puppy creator.
The "new" page loads fine with as many records as I like, so that part is ok but it doesn't seem to be creating a collection to send through, it is just overriding and thus sending through the last set of params so only creating one record.
What I have.
# routes
resources :container_returns
controller
# container returns controller
def new
#containers = Container.where(id: params[:container_ids])
#container_returns = []
#containers.each do |container|
#container_returns << ContainerReturn.new(
{
container_id: container.id,
quantity: container.amount,
uom: container.uom,
material_restriction_id: container.material_restriction_id
}
)
end
end
view
# new.html.erb
<%= form_tag container_returns_path, method: :post do %>
<% #container_returns.each do |container_return| %>
<%= fields_for 'returns[]', container_return, hidden_field_id: true do |cr| %>
<div class="field">
<%= cr.label :container_id %>
<%= cr.number_field :container_id %>
</div>
<div class="field">
<%= cr.label :material_restriction_id %>
<%= cr.number_field :material_restriction_id %>
</div>
<div class="field">
<%= cr.label :quantity %>
<%= cr.text_field :quantity %>
</div>
<div class="field">
<%= cr.label :uom %>
<%= cr.text_field :uom %>
</div>
<% end %>
<% end %>
<%= submit_tag "lots of returns" %>
<% end %>
which submits
# params submitted
Started POST "/container_returns" for 127.0.0.1 at 2018-10-19 11:00:48 +0200
Processing by ContainerReturnsController#create as HTML
Parameters: {
"utf8"=>"✓", "authenticity_token"=>[removed],
"returns"=>{"container_id"=>"405", "material_restriction_id"=>"", "quantity"=>"100.0", "uom"=>"kg"}, "commit"=>"lots of returns"
}
hopefully it is just something stupid that I missed.
UPDATE:
if I add an index to the form it now believes me that my objects are different and sends through all the params I need.
<% #container_returns.each_with_index do |container_return, index| %>
<%= fields_for 'returns[]', container_return, index: index do |cr| %>
[...]
as mentioned in the update, if I add an ID to the initial create it builds the correct array that I was expecting. What I also found was if I send through an index position that also works.
<% #container_returns.each_with_index do |container_return, index| %>
<%= fields_for 'returns[]', container_return, index: index do |cr| %>
[...]
gives me what I was expecting
Parameters: {
"returns"=>{"0"=>{"container_id"=>"400",...},
"1"=>{"container_id"=>"401",...},
etc.
},
"commit"=>"lots of returns"
}
I have a parent model that accepts child attributes.
class User < ActiveRecord::Base
accepts_nested_attributes_for :spec
attr_accessible :name, :spec_attributes
In the view I have a form that gets information for 3 models. I use a generic form_tag.
<% form_tag(action) do %>
.
.
.
<% fields_for "user[spec_attributes]" do |spec_form|%>
<%= spec_form.check_box :alert_greeting %>
<%= spec_form.label :alert_greeting, "Email me when new greetings are posted" %>
<% end %>
<% end %>
In the Controller
#user = User.find(session[:user_id])
if #user.update_attributes(params[:user])
do something.
end
The database is getting updated and the all seems to be working.
However when I go back to the form to edit again, even though the value for the checkbox is showing 1 the check box is not checked.
Any ideas as to how to show the checkbox as being checked when it is supposed to be?
Thanks a lot in advance.
You need to reference the specific spec record that is within the user you're calling. Try changing
<% fields_for "user[spec_attributes]" do |spec_form|%>
to
<% fields_for #user.spec do |spec_form|%>
You'll need to make sure that you have a non-nil spec object built for the user (but not necessarily saved) in your edit controller action.
You can do attributes and nested attributes using two fields_for calls like this:
<%= form_tag(action) do %>
... other form tags ...
<%= fields_for :user, #user do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= f.fields_for :spec do |s| %>
<%= s.check_box :alert_greeting %>
<%= s.label :alert_greeting, "Email me when new greetings are posted" %>
<% end %>
<% end %>
<% end %>
Rails 3.0 deprecated f.error_messages and now requires a plugin to work correctly - I however want to learn how to display error messages the (new) native way. I am following the getting started guide, which uses the deprecated method when implementing the comments form. For example:
<h2>Add a comment:</h2>
<%= form_for([#post, #post.comments.build]) do |f| %>
<%= f.error_messages %>
<div class="field">
<% f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here is the correct way to do it (as generated by the scaffold):
<%= form_for(#post) do |f| %>
<% if #post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
. . .
I understand that I use the #post variable in the latter example, but what variable do I reference in the former to get the error messages for comment creation?
The best and clean way to implement error_messages in your form is by implementing error_messages in a FormBuilder.
For example, here is the error_messages method I've implemented for my last project.
By implemeting your own FormBuilder you can follow the rules and styles of your webdesigner...
Here is an example that will output the errors list in ul/li's with some custom styles :
class StandardBuilder < ActionView::Helpers::FormBuilder
def error_messages
return unless object.respond_to?(:errors) && object.errors.any?
errors_list = ""
errors_list << #template.content_tag(:span, "There are errors!", :class => "title-error")
errors_list << object.errors.full_messages.map { |message| #template.content_tag(:li, message) }.join("\n")
#template.content_tag(:ul, errors_list.html_safe, :class => "error-recap round-border")
end
end
Then in my forms :
= f.error_messages
And that's all.
I'm pretty sure all you'd need to do is reference #post.comments
So you could do something like:
<% #post.comments.each do |comment| %>
<% if comment.errors.any? %>
<% comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
<% end %>
<% end %>
Or just pull all the errors out out:
comment_errors = #post.comments.map(&:errors)
and then loop through them in your display logic to output each of the comment errors.
This functionality exists as a standalone gem dynamic_form.
Add the the following to your Gemfile
gem 'dynamic_form'
From the github page:
DynamicForm holds a few helpers method to help you deal with your Rails3 models, they are:
input(record, method, options = {})
form(record, options = {})
error_message_on(object, method, options={})
error_messages_for(record, options={})
It also adds f.error_messages and f.error_message_on to your form builders.
Here is my solution to the whole error scene.
I created a partial which simply uses a model variable which one would pass when rendering it:
<%# app/views/errors/_error.html.erb %>
<%= content_for :message do %>
<% if model.errors.any? %>
<ul>
<% model.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<% end %>
You can easily add dynamic html class and/or id names based on the name of the model, as well as generic ones.
I have things setup where my error messages render in all the same place in a layout file:
<%# app/views/layouts/application.html.erb %>
<%= yield :message %>
If one didn't want that functionality, removing the content_for in the partial would do the trick.
Then in really any view you want you can simply write:
<%= render 'errors/error', model: #some_model %>
One could further expand this by creating a partial which takes a collection and leverages the error partial above:
<%# app/views/errors/_collection.html.erb %>
<% collection.each do |model| %>
<%= render 'errors/error', model: model %>
<% end %>
Render it with:
<%= render 'errors/collection', collection: #some_model.some_has_many_association %>
I like this way. It is simple, easy to manage/maintain, and incredibly tweakable.
I hope this helps!
EDIT: Everything in HAML
-# app/views/errors/_error.html.haml
= content_for :message do
- if model.errors.any?
%ul
- model.errors.full_messages.each do |msg|
%li= msg
-# app/views/layouts/application.html.haml
= yield :message
= render 'errors/error', model: #some_model
-# app/views/errors/_collection.html.haml
- collection.each do |model|
= render 'errors/errors', model: #some_model
= render 'errors/_collection', collection: #some_model.some_has_many_association
I guess that the [#post, #post.comments.build] array is just passed to polymorphic_path inside form_for. This generates a sub-resource path for comments (like /posts/1/comments in this case). So it looks like your first example uses comments as sub-resources to posts, right?.
So actually the controller that will be called here is the CommentsController. The reason why Lukas' solution doesn't work for you might be that you actually don't use #post.comments.build inside the controller when creating the comment (it doesn't matter that you use it in the view when calling form_for). The CommentsController#create method should look like this (more or less):
def create
#post = Post.find(params[:post_id]
#comment = #post.comments.build(params[:comment])
if(#comment.save)
# you would probably redirect to #post
else
# you would probably render post#show or wherever you have the form
end
end
Then you can use the code generated by scaffolding, only replace #post instance variable with #comment in all the lines except form_for call.
I think it may also be a good idea to add the #comment = #post.comment.build to the controller method that displays this form and use form_for([#post, #comment], ...) to keep the form contents displayed in the form if there're errors.
If this doesn't work and you're not able to figure it out, please add your CommentsController#create method to the question.
I just looked into the docrails github issues, and they've decided to remove f.error_messages instead of explaining how to do validation for comments.
a rather simple implementation can be achieved with
class ActionView::Helpers::FormBuilder
def error_message(method)
return unless object.respond_to?(:errors) && object.errors.any?
#template.content_tag(:div, object.errors.full_messages_for(:"#{method}").first, class: 'error')
end
end
which allows one to use
<%= form.label :first_name %>
<%= form.text_field :first_name %>
<%= form.error_message :first_name %>
and with the following sass
#import variables
.error
padding: $margin-s
margin-left: $size-xl
color: red
.field_with_errors
input
border: 1px red solid
input:focus
outline-color: red
it looks like
using simple form gives you quite similiar functionality with more advanced functionality.
For example check out their examples with bootstrap