Callbacks & params.require -- Can someone explain these to me? - ruby-on-rails

I apologize if this is a silly but I have no idea what the following piece of code does
private
# Use callbacks to share common setup or constraints between actions.
def set_article
#article = Article.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def article_params
params.require(:article).permit(:title, :post, :user_id)
end
end
I'm new to rails and I have gotten a fairly good idea of MVC and how it all connects, but I just can't wrap my head around the code above found in the controller files. What's a callback and why would I use one? What is Article.find(params[:id]) doing? What does .permit do in this case? Does it mean that it'll only accept the title, post & user_id parameters and not anything else? And on top of all, why are both the methods private? Thanks in advance!

Callbacks are methods that are called at some points of object's lifecycle. set_article is a callback, because it is called before particular controller actions(usually listed on top of the file). The reason for this is that you need to find an article from the database and assign it to an instance variable in all those actions. This method is meant to keep the code DRY(Do not Repeat Yourself), because if you don't have a private method for this, you would have to copy/paste the line inside the method for each controller action. That is fine as long as you don't need to change it EVER. That is why it is good practice to use callbacks for these kinds of methods.
As mentioned before #article = Article.find(params[:id]) finds an article in the database with a particular ID and assigns it to an instance variable, so you can later render it in a view (using, say, #article.name etc).
What does article_params method do? It takes ALL the params that are passed to the request. then in checks for the construction of the params. In this case params.require(:article).permit(:title, :post, :user_id) it is waiting for params that look like article: { title: "smth", param2: "val2" } etc. That is what .require(:article) does. What permit does, is drop all other params except the ones you have whitelisted.
An example:
def article_params
params.require(:article).permit(:title, :post, :user_id)
end
And let's say a hacker is trying to change forbidden attributes and sends a request like this article: { title: "smth", post: "Hacked", user_id: 1, admin_id: 1(hackers user_id) }
If you do Article.find(params[:id]).update_attributes(params[:article]) it would try to write to the database ALLL the params that have been passed.
If you have the article_params method and you use that instead of raw params: Article.find(params[:id]).update_attributes(article_params)
It will automatically DROP all non-whitelisted params, meaning that article_params will only contain article: { title: "smth", post: "Hacked", user_id: 1}
I hope This is clear now.
Private methods In general private methods are methods that you cannot call outside the object itself. For example if Article has a private method write You cannot do:
article = Article.new
article.write
But if You use the same method when defining some public method for Article
class Article
def needs_write
#Do other stuff
write
end
private
def write
puts "private"
end
end
Then calling article.needs_write would work, and would also call the write method.
It is a bit tricky to understand at first... + to make you more confused - you can, actually, call a private method directly on object via send method, but you shouldn't worry about that at first

these actions are private because we dont want to access these actions outside the class.
def set_article
#article = Article.find(params[:id])
end
this action runs before show and edit you can see at top before_action
it is same as
def show
#article = Article.find(params[:id])
end
we use same thing in edit and show. so we dont want to repeat same thing agian and again. so we put it in an action.
def article_params
params.require(:article).permit(:title, :post, :user_id)
end
this action is accept strong parameters. only these parameter will allow to store in databse

Related

Overwrite permited_params if rails controller has been inherited

Here an example:
class Base<ApplicationController
private
def permited_params
params.require(:object_name).permit(:name, :description)
end
end
class Post<Base
private
def permited_params
params.require(:post).permit(:name, :description, :owner)
end
end
I'm getting an error ActiveModel::ForbiddenAttributesError when call action create. How I can overwrite this permited_params
Params, in general, have a good reason to exist and make sure that not everything can be saved into your database. However, if you want to permit all params you can call
params.require(:post).permit!
In case you just want to change the params you can change the attribute names.
params.require(:post).permit(:name, :description, :some_you_want, some_more ) etc.
In general, you should add all params you want to save into the list of permitted params. So you make sure that all the attributes you want to save will be stored and no more. You can have permitted_params in every controller. You do not need to call it permitted params. For instance you can call it like this in your posts_controller:
def create
#post = Post.new(post_params)
#.... your code
end
private
def post_params
params.require(:post).permit(:name, :description, :owner)
end
This also works for inherited controllers.
Instead of params.require(:post).permit(...
you can use whatever params you want, like params.require(:reply).permit(...
The required param will throw an error if it is not available. So you need to make sure it exists for example by
#post = Post.new
Other params are optional and will not cause an error by default.

Disable strong parameters for a specific action

I have a serious problem with strong parameters. Its working pretty well in my about 200 actions but in one it doesn't because I'm working very dynamic with the parameters there and I also cant change it because of the applications design.
So I want to disable strong parameters validation in just this specific action. Is there a way to do this?
Strong parameters overrides the params method in ActionController::Base. You can simply override it and set it back to what you want yourself.
So this:
class MyController < ApplicationController
def params
request.parameters
end
end
Will effectively disable strong parameters for all actions in your controller. You only wanted to disable it for a particular action though so you could do that with:
class MyController < ApplicationController
before_action :use_unsafe_params, only: [:particular_action]
def params
#_dangerous_params || super
end
def particular_action
# My params is unsafe
end
def normal_action
# my params is safe
end
private
def use_unsafe_params
#_dangerous_params = request.parameters
end
end
Not too sure if this is best practice but for Rails 5 I just use request.params instead of params anytime I want to skip strong params.
So instead of something like:
post = Post.new(params[:post])
I use:
post = Post.new(request.params[:post])
You can use .permit! to whitelist any keys in a hash.
params.require(:something).permit!
However this should be treated as an extreme code smell and a security risk.
Nested hashes can be whitelisted with this trick:
params.require(:product).permit(:name, data: params[:product][:data].try(:keys))

Having multiple instance variables in rails controller action? (Rails best practices)

Say for example I have two models, posts and category. Now say I want to make it so the from the category show page you can create a new post using the form_for method. To do this, you will obviously need access to the #category variable and a new instance of a post (#post). Is this acceptable code in the controller?
#app/controllers/categories_controller.rb
def show
#category = Category.find(params[:id])
#post = Post.new
end
Or is it bad practice to have two instance variables defined in the one controller action - and if it is, what would be the best practice for a case like this?
I usually do something like:
#app/controllers/categories_controller.rb
helper_method :category
helper_method :post
def show
end
private
def category
#_category ||= params[:id] ? Category.find(params[:id]) : Category.new(params[:category])
end
def post
#_post ||= Post.new(params[:post])
end
Then, in your views, just refer to post or category (not #post or #_post). The nice thing is you can remove the same logic from your new, delete, etc methods...
Actions related to posts should be in the PostsController as much as possible.
Let's say the user is looking at all posts under the category "rails": /categories/rails
There's a button on that page to create a new post under the "rails" category, href: /posts/new?category=rails
This takes you to PostsController#new where you instantiate a new Post, validate the category param and build a view. This view could either be a new page, or a modal popping up.

RoR: instances variables within controller methods

My question is about controller methods (possibly included from an outside class) that work with instance variables. I frequently use a before_filter in controllers to set up certain variables, e.g.:
class DocumentController < ApplicationController
before_filter :fetch_document
def action
#document.do_something
end
private
def fetch_document
#document = Document.find(params[:id])
end
end
I've been working on a project in which a few controllers will share some functionality, say, document editing. My first thought was to extract the relevant methods, and get them from application_controller.rb or a separate module. But then I noticed I was writing code that looks like this:
def fetch_document
#document = Document.find(params[:id])
end
def do_something_to_document
#document.do_something
end
This sets off warning bells: do_something_to_document is essentially assuming the existence of #document, rather than taking it as an argument. Is this, in your sage opinions, a bad coding practice? Or am I being paranoid?
Assuming it is an issue, I see two general approaches to deal with it:
Check for the instance var and bail unless it's set:
def do_something_to_document
raise "no doc!" unless #document
[...]
end
Call the action with the instance var as an argument:
def do_something_to_document(document)
[...]
end
2 looks better, because it hides the context of the calling object. But do_something_to_doc will only be called by controllers that have already set up #document, and taking #document as a method argument incurs the overhead of object creation. (Right?) 1 seems hackish, but should cover all of the cases.
I'm inclined to go with 1 (assuming I'm right about the performance issue), even though seeing a list of methods referencing mysterious instance vars gives me hives. Thoughts? Let me know if I can be more clear. (And of course, if this is answered somewhere I didn't see it, just point me in the right direction...)
Thanks,
-Erik
If you really need document in different controllers, I'd do something like this:
class ApplicationController < ActionController::Base
private
def document
#document ||= Document.find(params[:document_id])
end
end
class FooController < ApplicationController
before_filter :ensure_document, :only => [:foo]
def foo
document.do_something
end
private
# TODO: not sure if controller_name/action_name still exists
def ensure_document
raise "#{controller_name}##{action_name} needs a document" unless document
end
end
As #variable are session/instance variable you will get a Nil exception in do_something_to_document method.
The first code is fine, because before_filter will always load your #document.
I suggest you to write something like that
def fetch_document(doc_id)
#document ||= Document.find(doc_id)
end
def do_something_to_document
my_doc = fetch_document(params[:id])
end
where do_something_to_document is in the controller (if not, dont use params[:id], even if you know you can access this global, use another explicit parameter). The ||= thing, will asssure that you call the base only once by request.

Can I have an action which runs several methods?

Lets say I have this in my controller:
def something
end
def email
end
def house
end
I want to create an action that runs all of the action something, email and house
def runall
Run email, something and house
end
How is it done?
def runall
[:something, :email, :house].each{|a| send(a)}
end
Using Object#send
This is all assuming these are some sort of private helpers, rather than actual controller actions that are called to render a view etc. Otherwise I don't suggest doing this.
Just for vocabulary's sake:
you've had great answers to the following question: "can I have an action which runs several methods"
an action is linked to a view so it's non-sense to execute several actions at once

Resources