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.
Related
Given the following simplified situation (in reality, the scenario is from an ActiveAdmin backed app):
class ShapeController < ApplicationController
def update
(...)
redirect_to
end
end
class CircleController < ShapeController
def update
super
(...)
redirect_to
end
end
Calling CircleController#update will cause the famous "AbstractController::DoubleRenderError" because redirect_to is called twice.
Now, I can't prevent the first call of redirect_to by super, at least not without messing with ActiveAdmin's code. Is there another way to cancel the first redirect_to and overrule it with another one?
Thanks for your hints!
ActiveAdmin is using Inherited Resources to do perform the standard REST actions. The gem provided a way to overwrite the respond_to block. I've never try this before but this might be helpful in your case:
ActiveAdmin.register Circle do
# ...
controller do
def update
update! do |success, failure|
failure.html { redirect_to circle_url(#circle) }
end
end
end
# ...
end
Refer to the IR gem documentation for more options to overwrite the actions(under Overwriting actions section).
I would say it is not possible. The best solution would be to extract the action code in some protected controller method, and call it from the child controller:
class ShapeController < ApplicationController
def update
do_the_update
redirect_to
end
protected
def do_the_update
# your code
end
end
class CircleController < ShapeController
def update
do_the_update
redirect_to
end
end
The rails before action seems useful for setting a variable shared by a number of actions in a controller.
But isn't the default implementation of the set_post that we see commonly on tutorials etc open to an attack by a malicious user?
If we take a controller like this:
PostsController < Application Controller
before_action :set_post , only: [:show,:create,:update]
def show
...
end
def create
...
end
def update
...
end
private
def set_post
#post = Post.find(params[:id])
end
end
When a user is presented the opportunity to update a post for example the form would be generated for them, and on post, params[:id] would contain the ID of the appropiate post - probably owned by the current_user.
However, it would not be difficult for a malicious user to alter the posted :id variable to allow them to actually end up setting the #post variable in the controller, to represent a different post, rather than the original being updated.
I could see this being safer:
private
def set_post
#post = Post.find(params[:id])
if(#post.user_id != current_user.id)
redirect_to homepage, alert: "you can edit your own posts"
end
end
However - that would stop other users viewing other people's posts! So how and where should this kind of check be performed to ensure that only the owner of a particular post can update / edit it. Is that something for the update controller action to handle itself with a check like this :
def update
if #post.user_id != current_user.id
redirect_to homepage, alert: "you can edit your own posts"
end
...
end
You are right, and I actually see that security issue being made very often by newbie Rails programmers. They just generate scaffolds and don't change things to their needs.
I'm using something like the following in my controllers:
before_action :set_post
before_action :check_post_ownership, except: :show
private
def set_post
#post = Post.find(params[:id])
end
def check_post_ownership
redirect_to homepage, alert: "..." unless #post.user_id == current_user.id
end
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.
How do I write this method in RoR?
if item is nil or item.user!='my_value' redirect_to "/"
Want to call it like so:
#item = Item.find(params[:id])
method_described_earlier(#item)
Or other way.
Want to see how rails pro would do it.
You should use a before_filter, which will run before any set of controller actions you indicate. For example:
class SomeController < ApplicationController
before_filter :require_special_item, only: [:myaction, :other_action, ...]
def myaction
# do something with #item
end
private
def require_special_item
#item = Item.find(params[:id])
if #item is nil or #item.user!='my_value'
redirect_to "/"
end
end
end
The method require_special_item will always run before myaction and any other actions you indicate, redirecting the user if the found item doesn't meet your requirements.
Can I use an if statement in my controller or is this bad practice?
In both my create and destroy actions for TracksController, I want to do something like this:
if Product
#product = Product.find(params[:product_id])
#track = #product.tracks.create(params[:track])
eslif Release
#Release = Release.find(params[:release_id])
#track = #release.tracks.create(params[:track])
end
Is there a better way to do this?
I'd do it via a before_filter callback:
class TracksController < AC
before_filter :ensure_track, :only => [ :create, :destroy ]
private
def ensure_track
if Product
#product = Product.find(params[:product_id])
#track = #product.tracks.create(params[:track])
elsif Release
#release = Release.find(params[:release_id])
#track = #release.tracks.create(params[:track])
end
end
end
So with this setup it's ensured that you have a #track instance variable in your create and destroy methods, cause ensure_track gets invoked before those two methods.
I'm not sure though, if the logic you're applying makes sense... Why do you want to test if a constant named Product exists and if not if a constant named Release does? Maybe the question should be if either params[:product_id] or params[:release_id] is present!?
But that's a different question :)
UPDATE: See Rails Action Controller Guide for filters.
I'd go further and suggest a more DRY approach to the before_filter:
class TracksController < ApplicationController
before_filter :get_track_parent, only: [ :create, :destroy ]
def create
#track = #parent.tracks.create(params[:track])
...
redirect_to #parent
end
private
def get_track_parent
if params[:product_id].present?
#parent = Product.find(params[:product_id])
elsif params[:release_id].present?
#parent = Release.find(params[:release_id])
end
end
end
I used parent because we were given a context for the model relationships but I assume there's a better term to describe the commonality between release and product wrt tracks.