Why is the strong_parameters method private? - ruby-on-rails

The rails scaffolds give you the resource_params method as private by default:
private
def person_params
params.require(:person).permit(:name, :age)
end
I understand why strong_parameters is a good thing. I also understand that this prevents the method from being accessed outside the controller, but are there any real dangers to making this method public, or what is the reasoning behind having this as a private method? It would be nice to be able to send that method to the controller from a gem that extends ActionController.
In other words, why not access the method outside of the controller? For example, if I have a separate controller that handles authorization and I want to pass an instance variable back to the original controller that contains the initialized object.

Because this method is not called from any external objects.
Mass assignment protection is not connected with 'person params' method visibility, it is just best practice for application design
Controllers using to handless only one request by app design. You should not call methods from one controller in another. If you want to share methods for several controllers, you can use inheritance, mixins, or service objects
inheritance
class BaseController < ApplicationController
private
def shared_method
end
end
class UsersController < BaseController
def index
shared_method
end
end
mixin
module SomeMixin
extend ActiveSupport::Concern
included do
def shared_method
end
end
end
class UsersController < ApplicationController
include SomeMixin
def index
shared_method
end
end
service object
class SomeService
def shared_method(params)
# process params
end
end
class UsersController < ApplicationController
include SomeMixin
def index
SomeService.new.shared_method(params)
end
end

It wasn't always this way and it's there to protect you. Check out this great blog post on the subject.
Here are some relevant snippets:
Problem with Mass-Assignment: Security vulnerability
Mass-assignment saves us the need to assign values to each attribute
of the model, but it can create problems. Since we aren’t restricting
which attributes can be set nor are we checking the valus of these
attributes, a malicious hacker could assign any value to any
attribute. In our example, he could set the value of admin true,
making himself a super user.
Here is what the url might look like
http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
Users are able to exploit this if they know even a little bit about your models and cause issues.

In most languages there are classifications in a class for methods, attributes or whatever else it may contain.
Depending on language or inheritence, default behaviour might be public, private...
In ruby, classes by default contain public methods and attributes. You need to specify if a method is private.
The public method or attribute:
This is accessible from out of the class or the instance of the class. So, if you have class:
class Foo
def my_id
10
end
private
def my_class
"Bar"
end
public
def my_friend
"Zonk"
end
end
Then:
2.0.0p247 :001 > #foo = Foo.new
2.0.0p247 :002 > #foo.my_id
=> 10
2.0.0p247 :003 > #foo.my_class
NoMethodError: undefined method `my_class' for #<Foo:0x000000045a53f8>
2.0.0p247 :004 > #foo.my_friend
=> "Zonk"
You see than you can change from private to public as you wish, though maybe not a very good idea.

Related

How can I pass in a variable defined in a class into a Rails form?

If I have a controller
class MyController < ApplicationController
vals = [...]
def new
...
end
def create
if save
...
else
render 'new'
end
end
how can I make the "vals" variable accessible to both methods? In my "new" view I want to use the "vals" variable for a drop-down menu, but rails is giving me errors. Of course, I could just copy the variable twice, but this solution is inelegant.
As Sebastion mentions a before_ hook / callback is one way to go about it, however as you mentioned it is for a dropdown menu, I am guessing it is a non-changing list, if so I would suggest perhaps using a Constant to define the values, perhaps in the model they are specific to, or if it is to be used in many places a PORO would do nicely to keep things DRY. This will then also allow you to easily access it anywhere, for example in models for a validation check, or to set the options of the dropdown menu in the view, or in the controller if you so wish:
class ExampleModel
DROPDOWN_VALUES = [...].freeze
validates :some_attr, inclusion: { in: DROPDOWN_VALUES }
end
class SomeController < ApplicationController
def new
# can call ExampleModel::DROPDOWN_VALUES here
end
def create
# also here, anywhere actually
end
end
You could use a before_* callback, e.g a before_action, this way you sets your vals variable as an instance one and make it to be available for your both new and create methods, something like:
class SomeController < ApplicationController
before_action :set_vals, only: [:new, :create]
def new
...
# #vals is available here
end
def create
if save
...
# and here
else
render 'new'
end
end
private
def set_vals
#vals = [...]
end
end
A different way from the ones before (although probably just having the instance method is preferred as in Sebastian's solution) is, take advantage of the fact that functions and local variables are called in the same way in ruby and just write:
def vals
#vals ||= [...]
end
and you should be able to access it on the controllers (not the views). If you want it on your views as well you can call at the beginning of the controller
helper_method :vals
If you want to be able to modify vals using vals="some value"
def vals= vals_value
#vals = vals_value
end
Take into account that probably using the intance variable as in Sebastian's solution is preferred, but if you, for whatever reason, are settled on being able to call "vals" instead of "#vals" on the view (for example if you are using send or try), then this should be able to do it for you.
Define in corresponding model
Eg :
class User < ActiveRecord::Base
TYPES = %w{ type1 type2 type3 }
end
and use in ur form like
User::TYPES
=> ["type1", "type2", "type3"]
You can reuse this anywhere in the application.

Rails Model needs to validate includes? class_name

I have a model called ToolFilter with a column of 'tool_type'. The string here refers to a class for a tool. I put a method in my application_controller called tools_list that gets the descendants of Tool.This works nicely in my frontend, but ToolFilter is complaining about the method tools_list.
class ToolFilter < ActiveRecord::Base
validate :existence_of_tool
def existence_of_tool
unless tools_list.include? tool_type
errors.add(:tool_type, "Invalid tool_type {{tool_type}}, use 'tools_list' to see a list of valid tool_object_types")
end
end
class ApplicationController < ActionController::Base
helper_method :tools_list
def tools_list
Rails.application.eager_load!
Tool.descendants
end
It's a bit strange to tell a model about other classes in the file system, but I need to validate that it is one of these. Should I put tools_list is a module and include it in ToolFilter? Any suggestions?
Write this to include helper in your model
ApplicationController.helpers.tool_list
Though I will not recommend calling helper in model.
And checking tools with classes is damm bad idea.
I ended up creating a module called ToolExtention which has these helper methods in them. I then included this module in my controllers wherever it was needed and moved my logic from the views into the controller which I believe is better practice.
module ToolExtension
def self.tools_list
Rails.application.eager_load!
Tool.descendants
end
...
class ProjectsController < ApplicationController
include ToolExtension
...
ToolExtension.tools_list

How does ActionController::Base in Rails know what class its model is?

I'm doing a bit of metaprogramming in Ruby. I'm writing a library to meta-define some methods for me, specifically in the controller (automate some find_by methods that I have to write for my applications).
Currently I generate these methods by having to pass the name of the model for a particular controller into my meta-programming method. Is there a method in a controller that is tied to an ActiveRecord model.
So, here is a poor example
module AwesomeGem
module ClassMethods
def write_some_methods_for(model)
raise "Class #{model.class} does not inherit ActiveRecord::Base" unless model < ActiveRecord::Base
define_method "money_remaining" do |id=nil|
moolah = id ? model.find(id).money : model.find(params[:id]).money
render text: moolah
end
define_method "money_remaining_poller" do |id=nil|
obj = id ? model.find(id) : model.find(params[:id])
# composes some ajax
render js: moneyjs
moneyjs
end
end
end
end
So, to use this method, I plan to
GamblerController < ApplicationController
write_some_methods_for Gambler
end
Again, how could I make it so I don't have to pass the Gambler class to my method? Is there some sort of method or attribute that I could just call the model directly? eg. self.send(:model)
A simple question with a complex explanation.
Controllers are not tied to a particular model by default. You can have a controller playing with several different models, or even a controller using no model at all.
If you still want your code to work automatically in "classic" cases, you could look at the controller's name, and look for a model with the same name (following rails naming conventions).

Call a controller's method in other controllers (staying DRY)

I'm slightly new to Rails (i.e. stupid and need some teachin').
I have a controller (call it ControllerFoo) that performs a particular task (theMethod) which could be useful in other controllers (say, from within ControllerBar). So, of course, the method is defined as self.theMethod in ControllerFoo (which means it's a class method, right?), and access in ControllerBar as ControllerFoo.theMethod. Confused yet?
Here's the problem: the ControllerFoo.theMethod uses session data, and when called from ControllerBar, session is nil. In fact, it seems that session is also nil when being called from itself. I guess what I'm saying is class methods can't access session data?
<rant>I hate how session data can't simply be accessed anywhere like in PHP</rant>
So for now, since I'm not smart enough to know how to do this correctly, I've just duplicated the logic in several places throughout my app. But this is not DRY at all, and I hate it.
So how can I create a method in a controller that's accessible to other controllers and can also access session data?
class ControllerFoo < ApplicationController
def self.theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerBar < ApplicationController
def show
ControllerFoo.theMethod("Hello,")
end
end
Couple of options...
Put the shared method in the shared parent ApplicationController
Create a module that both ControllerFoo and ControllerBar will include
e.g.
module SharedModule
def theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerFoo < ApplicationController
include SharedModule
end
class ControllerBar < ApplicationController
include SharedModule
def show
theMethod("Hello,")
end
end
The way you would do this is Ruby would be to create a module containing the class (or instance) methods you wish to share and include it in the classes you need to have those methods defined in.

How can I programatically determine which methods have been declared as "helper" methods by a controller in Rails?

I'm writing a plugin that adds a method to controllers and declares it as a helper method. If it were done statically (rather than through the plugin), it would look something like this:
# in RAILS_ROOT/app/controllers/stuffed_animals_controller.rb
class StuffedAnimalsController < ActionController::Base
private
def bear
'Teddy Bear'
end
helper_method :bear
end
# in RAILS_ROOT/app/views/stuffed_animals/index.html.erb:
<%= bear -%>
It works just fine. I want to test that :some_helper_method is actually a helper method, though. I tried this:
def test_declared_bear_as_helper_method
assert StuffedAnimalsController.helper_methods.include?(:bear)
end
Unfortunately, ActionController::Base does not have a :helper_methods class method. Anyone know where I can get the list of things a class exposes via :helper_method?
Got it!
def test_declared_bear_as_helper_method
helper = Object.new
helper.extend StuffedAnimalsController.master_helper_module
assert helper.respond_to?(:bear)
end

Resources