New to Ruby on Rails and been cracking my head on this. I have been following this tutorial here to make this form to save records into my DB - https://human-se.github.io/rails-demos-n-deets-2020/demo-resource-create/
This is my controller:
class ViewaddparamController < ActionController::Base
def view_add_param
newParam = ViewaddparamController.new
respond_to do |format|
format.html{ render :viewaddparam, locals: { newParam: newParam } }
end
end
def add_param
# new object from params
mlParam = ViewaddparamController.new(params.require(:Viewaddparam_Controller).permit(:name, :machine_model, :parameter_1, :parameter_2, :parameter_3, :data_train_set, :start_date, :end_date))
# respond_to block
respond_to do |format|
format.html do
if mlParam.save
# success message
flash[:success] = "ML parameters saved successfully"
# redirect to index
redirect_to model_url
else
# error message
flash.now[:error] = "Error: parameters could not be saved"
# render new
render :viewaddparam, locals: { newParam: newParam }
end
end
end
end
end
My route:
get 'viewparam', to: 'viewaddparam#view_add_param'
post 'add_param', to: 'viewaddparam#add_param', as: 'add_param'
My view:
<%= form_with model: newParam, url: add_param_path, method: :post, local: true, scope: :Viewaddparam_Controller do |f| %>
...
I kept getting this error whenever I try to submit the form
ArgumentError in ViewaddparamController#add_param
Wrong number of arguments (given 1, expected 0)
The error highlighted at my controller class, line 11.
What am I doing wrong here? I looked through the tutorial over and over but I can't see the fault here.
Thanks in advance.
It seems that you’re treating ViewaddparamController as both the controller – which in Rails terms is what responds to user requests - and the data model.
There’s often a one-to-one correlation to controllers and models, especially if you’re following what’s known as the RESTful pattern. So if your model was a Product, you might set it up in routes using a resources directive:
resources :products
That would set the app up to expect to use a ProductsController. And, using a similar coding style to your example, that would look a little like:
class ProductsController < ApplicationController
def new
product = Product.new
render :new, locals: { product: product }
end
def create
product = Product.new(params.require(:product).permit(…))
# etc
end
# etc
end
So you can see from this example that controller and model are related, but are named differently.
By using your controller name where you should be using the model, you’re calling #new on the controller, which is not normally something you need to do – controller instances are managed by the Rails app framework. And their initialiser doesn’t take any arguments, so Ruby complains that an initialiser that takes 0 arguments is being given 1.
Related
I have written a working method to return a random record within the console.
class Model < ApplicationRecord
...
def self.random
Model.limit(1).order("RANDOM()")
end
end
I am having trouble figuring out how to use this method to have the Rails API return that record.
Possibly add a conditional to the show() function in the associated controller? ...
def show
if Model.find(params[:id])
#item = Model.find(params[:id])
else
#item = Model.random
end
end
As you can probably tell, I'm a bit lost. Thanks for pointing me in the right direction.
You could do that in your model. Or, you might try in your controller:
def show
render json: Model.find_by(id: params[:id]) || Model.all.sample
end
Model.find(params[:id]) will throw an error if the record is not found and you'll never reach your else statement.
Model.find_by(id: params[:id]) will return nil if the record isn't found, in which case the or clause will return a random record.
Then you use render json: to return the record in its JSON format.
I suppose this assumes you've done something like:
Rails.application.routes.draw do
namespace :api, defaults: {format: 'json'} do
...
end
end
In config/routes.rb.
Using Devise I would like to display User account information such as profile name, first & last name etc on another page called profile page within my rails application.
I have created a controller called profiles with a view called profile/show
In the controller have added the below code
def show
#user = User.find_by_profile_name(params[:id])
if #user
render action: :show
else
render file: 'public/404', status: 404, formats: [:html]
end
end
end
In the view profiles/show I have the following code
<%= #user.profile_name %>
and the route is get 'profiles/show'.
My issue is when I do all of the above the profile name of the user still does not display on the profile page? There are no errors that come up it just doesn't display. I am not sure what code I am missing. I have checked the console and the user does have a profile name save to that ID and this is also in the devise account settings. So I am not sure how to get this information to display?
In Rails you would usually set it up like follow to take leverage of convention over configuration:
# config/routes.rb
resources :users, only: [:show, :index]
# app/models/user.rb
class User < ActiveRecord::Base
# ...
def self.find_by_uid!(uid)
User.find_by!("profile_name = :p OR id = :p", p: uid)
end
end
# app/controllers/users_controller.rb
class UsersController
# GET /users/:id
def show
#user = User.find_by_uid!(params[:id])
# Rails does the magic.
end
# GET /users
def index
#users = User.all
end
end
<%- # app/views/users/show.html.erb -%>
<h1><%= #user.profile_name %></h1>
The only special part here is that in the user model we create a class method which will query by id or profile_name. The reason that this is important is that it lets you use link_to(#user) and redirect_to(#user) as expected.
Which is also why we use resources :users. When the route name and the model line up the Rails polymorphic route handlers are able to do their job. If you want to use /profiles thats fine but never /profiles/show - including the action in the route defeats the whole purpose of REST.
The show action will render users/show.html.erb by default. So you rarely need to explicitly render in your controller.
render action: :foo
is only used when you want to render a template with the same name as another action, its usually used as follows:
def create
#something = Something.new
if #something.save
redirect_to(#something)
else
render action: :new # renders views/something/new.html.erb
end
end
If you want to explicitly render a template you would do render :foo or render "foo/bar".
And when you use find or find_by! it will raise an exception if the record is not found which by default will render the static 404 template. Reproducing this error handling in your actions is not very desirable since it violates the DRY pinciple.
When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.
I currently have two models School and Course where School has_many courses, and Course belongs_to school. Additionally, School and Course are nested resources, where School is the parent resource, and Course the child.
I have created several test records in the Rails Console so that a query such as when the child calls upon the parent Course.first.school successfully executes and returns all the relevant information of the school Course.first is associated with.
However, when put into a controller function, I would instead get an error "undefined method `school' for nil:NilClass" for the following line:
redirect_to school_course_path(#course.school, #course)
.. as if the .school part wasn't recognized (where as it was in the console). Why is this the case, and how do I get past this error? Thanks!
Edit - as suggested, it could be that my #course instance variable isn't passed from method to method in the controller. I have attempted at passing them through via a private method, but its still giving me the same error. Here is my code (background: the model Question belongs_to Course, with Course having many questions. Course isn't part of the nested routes)
class QuestionsController < ApplicationController
def new
#course = Course.find(params[:course]) #confirmed working
self.current_course = #course #I attempt to set current_course, a private method
#question = Question.new
end
def create
#question = Question.new(params[:question]) #also works, in rails console all the questions confirms to have rails id
if #question.save
redirect_to school_course_path(current_course.school, current_course) #source of my frustrations - continues to returns same error message
else
render 'new'
end
end
private
def current_course=(course)
#current_school = course
end
def current_course
#current_course
end
end
Should work if your relationships are set up the way I think they are:
def create
#question = Question.new(params[:question])
#course = #question.course
if #question.save
redirect_to school_course_path(#course.school, #course)
else
render 'new'
end
end
Make sure you have something like this in your create action:
#course = Course.new(params[:course])
your code is okay, it seems there is problem in your redirect.. redirect it to root_path and check whether it is working??
I want to simply save the token parameter I receive from the JSON request, but I simply cannot get to show if it is actually saved. I've observed that if you do a POST request with JSON parameters rails routes it to the create method. And I set my global variables attribute (in this case token), however when it redirects to the index.html.erb it gives me the error below
<h1>
NoMethodError in
Inits#show
</h1>
<p>
Showing <i>/Users/alioral/Desktop/Rails/testApp2/app/views/inits/show.html.erb</i> where line <b>#3</b> raised:
<pre><code>undefined method `token' for nil:NilClass</code></pre>
</p>
Here is my controller class;
class InitsController < ApplicationController
def index
end
def show
end
def create
#init=Init.new("token"=>params[:token])
#init.save
respond_to do |format|
format.html { redirect_to #init }
format.json { render json: #init }
end
end
end
And just in case here is the model I've generated (without using scaffold);
class Init < ActiveRecord::Base
attr_accessible :token
end
Here is my show.html.erb file;
<h1>Hello, World</h1>
<b>The token is:</b>
<%= #init.token%>
First, create does not redirect to index, it redirects to the new created #init path. Second, it looks like the show haml file contains a reference (in line 3) to #init.token, and since #init is nil you get this error.
The instance variables (the ones that starts with #) doesn't survive redirect. For every request a new instance of controller is made with the #init variable unset.
When you call render ... it's still with the original #init, but when you call redirect, then client issue a new HTTP request and on the server side there is new controller instantiated and your #init in the #show action is nil then.
update: So in your controller you should have something like this in your show method:
def show
#init = Init.find(params[:id])
end