Note: I'm using Rails 3.2
I'm trying to implement a simple_form by following this sample code: https://github.com/rafaelfranca/simple_form-bootstrap/blob/master/app/controllers/articles_controller.rb. I have a Summary model instead of an Article model.
I understand most of what's going on, except for the two private methods. I tried basically copying the code except for the two private methods, and what it does is it creates a new Summary, but the attributes are all nil.
1) How are attributes saved? What's the difference between .new and .save?
2) What is available in the create action? When you reach the create action, you've just filled out a form, so something must be available, but I don't know what it is, or how it becomes available.
3) What's going on with the before_action and the two private methods?
Summaries Controller
class SummariesController < ApplicationController
before_filter :set_summary, only: [:show, :edit, :update, :destroy]
def index
#summaries = Summary.all
end
def show
end
def new
#summary = Summary.new
end
def edit
end
def create
#summary = Summary.new(params[:summary])
if #summary.save
redirect_to #summary, notice: 'Summary created.'
else
render :new
end
end
def update
if #summary.update(params[:summary])
redirect_to #summary, notice: 'Summary updated.'
else
render :edit
end
end
def destroy
#summary.destroy
redirect_to summaries_url, notice: 'Summary destroyed'
end
private
# sets #summary to make available for show, edit, update
# and destroy actions so code isn't repetitive
def set_summary
#summary = Summary.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
# def summary_params
# params[:summary].permit!
#end
end
To answer your questions:
1) new is a Ruby method for initializing a new instance of an object. So when you call Article.new, you get a new instance of the Article class.
save is a Rails method for saving a record to the database. If your object instance is instantiated with new, it calls create under the hood. If you loaded the object through a finder (find, all, where, etc.), then it will call update under the hood instead of create.
2) A hash named params is available in all actions (and in the view). In the case of the create action, params[:article], which is the data posted by the form. At the top of create, try calling raise params[:article].to_yaml to see what's going on in there.
Heck, even try raise params.to_yaml to see what's in there.
3) The call to before_action runs the set_article private method before the show, edit, update, and destroy actions. Without that, you'd need to manually call #article = Article.find(params[:id]) in every single one of those actions. So this eliminates quite a bit of repetition!
You'll notice that the first line of the create action calls the article_params method. This is a common way of implementing what are called strong parameters in Rails. Strong parameters are new in Rails 4 but can be added to earlier versions of Rails via the strong_parameters gem.
Related
As the title says. I know that strong parameters is to prevent other unauthorized attributes to be included when updating or creating new objects. I've seen codes that doesn't have strong parameters. For example in Hartl's tutorial relationships controller:
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
#user = User.find(params[:followed_id])
current_user.follow(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
and others have it included such as creating new post or user etc. so my question is, when is the practice to use strong parameters?
Its ideal to use strong parameters when mass assigning values. Like creating new post or user. It may allows attackers to set any database column’s value.
Check out Rails official guide for it.
Doing like these is fine, as long as you know and mentioning model attributes.
#user = User.find(params[:followed_id])
#user = Relationship.find(params[:id]).followed
I would say - use strong params for any actions where you use mass-assignment. This means that, actions like create or update must employ strong params.
For example, instead of having:
#object.update_attributes(params[:object])
Just have a:
#object.update_attributes(object_params)
Which will whitelist params for you. Also, it allows you to pass-through different params for different actions, with methods like object_update_params and object_create_params which will whitelist params for update and params for create, respectively.
Yes, not using strong parameters will raise ActiveModel::ForbiddenAttributesError so it's not optional unless you manage to override this behavior.
In the above example he is just retrieving a record and then creating a relationship with that id in the model.
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
end
My QuizzesController#index action looks like this:
def index
#user = current_user
#quiz = Quiz.create(user_id: current_user.id)
end
My view draws the quiz form fine. It goes to the results/index view as intended. BUT the various attributes of the quiz are NOT updated on the Quiz instance which is pulled from the database, in the QuizzesContoller#update action:
def update
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
('update' is called in this case because the Quiz instance already exists, having been created in the 'index' action).
So, I tried changing the 'update' action to:
def update
#quiz.save
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
But this triggers the error:
undefined method 'save' for nil:NilClass
Why is that? Shouldn't my QuizzesController have access to the #quiz variable as set up in the 'index' action? Can anyone explain what the problem is there?
Others have answered this question, so I thought I would explain why the answer is what it is. In Ruby, variables that begin with the # symbol are instance variables. This means that they are created when a new instance of their parent object is instantiated and are unique to that instance of the object.
Rails based web apps, for the most part, are stateless, meaning that state is not persisted between http requests. In layman terms, the app treats each and every request independent of all other requests. Due to this, the controllers are instanced classes. Every request instantiates a new instance of the controller class.
EDIT:
More I look at your code, you aren't following proper conventions
class QuizzesController < ApplicationController
# GET index: for displaying a list of quizzes
def index
#quizzes = Quiz.where(user_id: current_user.id)
end
# GET show: for getting a single quiz record
def show
#quiz = Quiz.find(params[:id])
end
# GET new: for initializing a new quiz record
def new
#quiz = Quiz.new
end
# POST create: for saving a new quiz record
def create
#quiz = current_user.quizzes.create(quiz_params)
if #quiz.errors
render :new
else
redirect_to #quiz #or whereever
end
end
# GET edit: for initializing existing quiz for update
def edit
#quiz = Quiz.find(params[:id)
end
# PUT/PATCH update: for updating an existing quiz record
def update
#quiz = Quiz.find(params[:id])
if #quiz.update(quiz_params)
redirect_to #quiz # or whereever
else
render :edit
end
# DELETE destroy: for deleting a quiz record
def destroy
Quiz.find(params[:id]).destroy
redirect_to :index # or whereever
end
end
You have not #quiz variable in your update action. Actions in the controller does not have access to variables in other actions.
The QuizzesController instance is not persisted between requests. The real reason instance variables are used in controllers is to pass that variable to the view.
A normal update action would look something like:
def update
#quiz = current_user.quiz # I'm assuming a user has one quiz?
#quiz.update(quiz_params) # Where quiz params takes the posted parameters from your update form
if #quiz.errors.any?
render :edit
else
redirect_to results_path
end
The key is you need to reassign #quiz with each request
how to make this code clean in rails?
profiles_controller.rb :
class ProfilesController < ApplicationController
before_action :find_profile, only: [:edit, :update]
def index
#profiles = Profile.all
end
def new
#profile = Profile.new
end
def create
profile, message = Profile.create_object(params["profile"], current_user)
flash[:notice] = message
redirect_to profile_url
end
def edit
end
def update
profile, message = #profile.update_object(params["profile"])
flash[:notice] = message
redirect_to profile_url
end
private
def find_profile
#profile = Profile.friendly.find(params["id"])
end
end
i look flash[:notice] and redirct_to profile_url is duplicate in my code, how to make the code to clean and dry?
How about moving the repetitive code to a separate method and call that method inside the actions.
def flash_redirect # you can come up with a better name
flash[:notice] = message
redirect_to profile_url
end
then in update action:
def update
profile, message = #profile.update_object(params["profile"])
flash_redirect
end
do the same thing for create action
UPDATE:
in case you are wondering about usingafter_action, you can't use it to redirect as the call-back is appended after the action runs out its course. see this answer
Take a look at Inherited Resources. It's based on the fact that many CRUD controllers in Rails have the exact same general structure. It does most of the work for you and is fully customisable in case things are done a little different in your controllers.
Using this gem, your code would look like this:
class ProfilesController < InheritedResources::Base
def create
redirect_to_profile(*Profile.create_object(params[:profile], current_user))
end
def update
redirect_to_profile(*#profile.update_object(params[:profile]))
end
private
def redirect_to_profile(profile, message)
redirect_to(profile_url, notice: message)
end
def resource
#profile ||= Profile.friendly.find(params[:id])
end
end
The create and update methods return multiple values, so I used the splat operator to DRY this up.
create_object and update_object don't follow the Rails default, so we need to implement those actions for Inherited Resources instead. Currently they don't seem to be handling validation errors. If you can, refactor them to use ActiveRecord's save and update, it would make everything even easier and DRYer.
I currently have two views (new.html.erb and retirement_accounts_new.html.erb) in the Accounts both using the same create and update methods.
Here's how they're defined in the controller:
# GET /accounts/new
def new
#account = current_user.accounts.build
end
# GET /retirement/accounts/new
def retirement_accounts_new
#account = current_user.accounts.build
end
And here's the same create method they share:
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to accounts_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
Is there a way to make that redirect_to accounts_path conditional based on which view is rendering the form?
I would like retirement_accounts_new on save/update to redirect_to retirement_accounts
It sounds like this might be a design issue. Are Accounts and RetirementAccounts significantly different? Will they share much of the same logic, but not all? If so, I think I would avoid using conditional logic in the controller and solve it using inheritance.
The idea here is that retirement_accounts would be considered a new resource in your routes file:
resources :retirement_accounts
Then you manually create a new controller for it (skip the rails generate... command). Save this file as app/controllers/retirement_accounts_controller.rb:
class RetirementAccountsController < AccountsController
end
Notice how it inherits from AccountsController instead of ApplicationController. Even in this empty state, RetirementAccountsController shares all of the logic of AccountsController, including the new and create methods, plus all of the view files to which they refer. To make the necessary modifications for the retirement accounts, you simply need to override the appropriate actions and views.
You can delete your retirement_accounts_new action, since it is identical to the new action. Move the view for retirement_accounts_new to app/views/retirement_accounts/new.html.erb, so that template will be rendered when new is called on the RetirementAccountsController.
As for the conditional create method, you can make a private method on both controllers that will determine where the post-create redirect should point:
class AccountsController < ApplicationController
# ...
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to post_create_redirect_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
private
def post_create_redirect_path
accounts_path
end
end
class RetirementAccountsController < AccountsController
private
def post_create_redirect_path
retirement_accounts_path
end
end
If RetirementAccount < Account as a single table inheritance model then the thing you are asking would happen by default,
plan B would be to use explicit url_for in the redirect such as:
redirect_to url_for(controller: params[:controller], action: :show, id: #account.id), notice: 'Account was successfully created.'
Looking at the api doc this should work too:
redirect_to :action => "show", :id => #account.id,notice: 'Account was successfully created.'
Check out http://apidock.com/rails/ActionController/Base/redirect_to - there's probably an answer for you there somewhere :)
PS I have assumed that the the retirement account and account actions are in different controllers. If they're not in different controllers and not different model classes then you can put a hidden tag in the new form - but this is bad&ugly
Best solution is probably STI model and 2 separate resources for the 2 classes and everything will work out of the box. If this isn't an option, at least separate the controllers and make things clean that way, it's much better to reuse views then to reuse controllers
Using Devise, I know how to protect controller actions from non-signed-in users through:
before_filter :authenticate_user!
In order to illustrate what I am trying to achieve, please see an example:
I have the following controller: (a project belongs to a user)
projects_controller.rb
def create
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
What I am looking for is a way that users can interact more with the website before having to sign up/sign in. Something like:
after_validation :authenticate_user!
if the user is not signed in, and redirect him after success (sign up/sign in) to the "project" show page.
Things I thought:
1.) Change the controller in order to accept a project object without user_id, ask for authentication if the user is not signed in, then update attributes with the user_id
I try to do it like this first and it results to a very ugly code. (Moreover authenticate_user! doesn't redirect to the #project which lead to more customization)
2.) Create a wizard with nested_attributes (project form and nested new registration form and session form)
3.) Something better? (a custom method?)
It seems authologic manages this more easily. I'm not sure it is a reason to switch so I would like to have your idea/answer on this. Thanks!
EDIT
references: Peter Ehrlich answer comment
CONTROLLER WITH VALIDATIONS LOGIC
projects_controller.rb
def create
unless current_user
#project = Project.new(params[:project]) # create a project variable used only for testing validation (this variable will change in resume project method just before being saved)
if #project.valid? # test if validations pass
session['new_project'] = params[:project]
redirect_to '/users/sign_up'
else
render :action => 'new'
end
else
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
end
def resume_project
#project = current_user.projects.new(session.delete('new_project')) # changes the #project variable
#project.save
redirect_to #project
end
routes
get "/resume_project", :controller => 'projects', :action => 'resume_project'
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(resource)
return '/resume_project' if session['new_project'].present?
super
end
Something like this should work:
def create
unless current_user
session['new_project'] = params[:project]
redirect_to '/register'
return
end
# and on to normal stuff
# in your devise controller
def after_sign_in_path
return '/resume_project' if session['new_project'].present?
super
end
# back in projects_controller now
def resume_project
#project.create(session.delete('new_project'))
# you know the drill from here
# I'd also put in a check to make an error if the session is not set- in case they reload or some such
Keep in mind that session is a cookie in the browser, and thus has a size limit (4kb). If you're posting images or other media, you'll have to store them temporarily server-side.
Another option would be to create a userless project, and use a similar technique to allow them to claim it as their own. This would be nice if you wanted unclaimed projects displayed to all to be available as a flow.
I haven't tested it out, but it should be possible to store the action the user was going to, I.e. create, with the params hash that was submitted and redirect to it upon successful login. It would then handle the error cases as normal.
Have you tried that?