Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

strong parameter support refined to allow standard Rails 4 notation #286

Merged
merged 1 commit into from
@prijutme4ty

This commit allows one to use standard rails 4 params.require().permit() notation.
It's done in such a way that this doesn't clash with permitted_params if it's already defined. But if it doesn't, then default permitted_params falls back to a permitting method with resource-specific name.
Method user should define is the same as scaffold generates and thus the same as other gems such as cancan will try to handle.

@travisp

I think it's a good idea, but the tests should probably test the "required" support. Some quick testing seems to show that it works fine for the "update" controller action.. but it fails for me on the "new" controller action: a 'Required parameter missing' error occurs on the new action, which isn't normal rails behavior. The new action builds a resource and calls resource_params.

@prijutme4ty

@travisp Thank you for a response! Actually I didn't tested it with strong_parameters, and did only testing with stubs. I'll try to fix it.

@prijutme4ty

@travisp Can you please show code which caused the failure? In my tests everything works.

@travisp

@prijutme4ty Your code testing the new action should be "get :new, {}" because the new action typically does not have any parameters passed to it.

This test fails:

  def test_resource_params_from_new
    get :new, {}
    assert_response :success
    assert assigns(:widget)
  end

Results in: "Expected response to be a <:success>, but was <400>" and response.body after the get request contains "Required parameter missing: hotel"

@prijutme4ty

@travisp Understood the problem. I believe, it's due to inherited_resources using strong_parameter sanitization on new action, while it's not a common practice. I'll try to distinguish new and create action when call #build_resource

@rafaelgoulart

I'm having the same problem with the new action.

@travisp

@rafaelgoulart The code still hasn't been updated to deal with it. I might take a shot in a few weeks if nobody else gets to it: it would be very nice if inherited_resources could work with standard strong_parameters syntax and would also allow the use of "requires".

@taavo

I've come to the conclusion that the API I used in #260 was stupid, and this one is much better. I'll help out if my schedule opens up.

@dim

Any news? Would love to see this merged.

@rolftimmermans

This proposed API makes so much more sense. The current strong parameters solution is pretty awkward to use correctly.

@strangeement

It would be pretty helpful to add a note to the README meanwhile.

As I understand it (after some testing and debugging inside IR), permitted_params must not use params.require(:model).permit(...), but rather params.permit(...). When require is set, IR receives empty parameters from BaseHelpers.build_resource_params so the model is empty.

@aaronchi

+1

To address the new issue, you can skip building params if request.get?

@onemanstartup onemanstartup referenced this pull request
Merged

Update README.md #335

@abernardes

:up:

Any chance of merge for this PR?

@joelmoss

If @prijutme4ty can make the PR merge cleanly, I'd be happy to merge it.

@prijutme4ty

@travisp @joelmoss Tried to resolve issue with new action by rescuing an exception. More detailed explanation in a comment before #permitted_params method. Is it acceptable?
Everybody, sorry for such a long delay.

@joelmoss joelmoss merged commit 1e18617 into from
@joelmoss

LGTM!

@prijutme4ty prijutme4ty deleted the branch
@seanlinsley seanlinsley referenced this pull request in activeadmin/activeadmin
Closed

Inherited Resources 1.5.0 #3189

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 4, 2014
  1. @prijutme4ty
This page is out of date. Refresh to see the latest.
View
4 Gemfile
@@ -5,3 +5,7 @@ gemspec
gem 'rails', '>= 3.2', '< 5'
gem 'mocha'
gem 'turn'
+
+group :test do
+ gem 'strong_parameters'
+end
View
5 Gemfile.lock
@@ -80,6 +80,10 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (~> 2.8)
+ strong_parameters (0.1.4)
+ actionpack (>= 3.2.0)
+ activemodel (>= 3.2.0)
+ railties (>= 3.2.0)
thor (0.18.1)
thread_safe (0.1.2)
atomic
@@ -98,4 +102,5 @@ DEPENDENCIES
inherited_resources!
mocha
rails (>= 3.2, < 5)
+ strong_parameters
turn
View
10 README.md
@@ -706,6 +706,16 @@ def build_resource_params
end
```
+
+Instead you can stick to a standard Rails 4 notation (as rails scaffold generates) and write:
+
+ def widget_params
+ params.require(:widget).permit(:permitted_field, :other_permitted_field)
+ end
+
+In such case you should remove #permitted_params method because it has greater priority.
+
+
## Bugs and Feedback
If you discover any bugs, please describe it in the issues tracker, including Rails and InheritedResources versions.
View
37 lib/inherited_resources/base_helpers.rb
@@ -316,9 +316,44 @@ def resource_params
@resource_params ||= build_resource_params
end
+ def resource_params_method_name
+ "#{resource_instance_name}_params"
+ end
+
+ # Returns hash of sanitized params in a form like
+ # `{:project => {:project_attribute => 'value'}}`
+ #
+ # This method makes use of `project_params` (or `smth_else_params`) which
+ # is a default Rails controller method for strong parameters definition.
+ #
+ # `permitted_params` is usually fired by method :new, :create, :update
+ # actions. Action :new usually has no parameters so strong parameters
+ # `require` directive raises a +ActionController::ParameterMissing+
+ # exception. `#permitted_params` rescues such exceptions in :new and
+ # returns an empty hash of parameters (which is reasonable default).
+ # If for any reasons you need something more specific, you can redefine
+ # this method in a way previous `inherited_resources` versions did:
+ #
+ # # Unnecessary redefinition
+ # def permitted_params
+ # params.permit(:project => [:project_attribute])
+ # end
+ #
+ def permitted_params
+ return nil unless respond_to?(resource_params_method_name, true)
+ {resource_request_name => send(resource_params_method_name)}
+ rescue ActionController::ParameterMissing
+ # typically :new action
+ if params[:action].to_s == 'new'
+ {resource_request_name => {}}
+ else
+ raise
+ end
+ end
+
# extract attributes from params
def build_resource_params
- parameters = respond_to?(:permitted_params, true) ? permitted_params : params
+ parameters = permitted_params || params
rparams = [parameters[resource_request_name] || parameters[resource_instance_name] || {}]
if without_protection_given?
rparams << without_protection
View
85 test/strong_parameters_test.rb
@@ -1,5 +1,9 @@
require File.expand_path('test_helper', File.dirname(__FILE__))
+if ActionPack::VERSION::MAJOR == 3
+ require 'strong_parameters'
+end
+
class Widget
extend ActiveModel::Naming
end
@@ -7,6 +11,7 @@ class Widget
class WidgetsController < InheritedResources::Base
end
+# test usage of `permitted_params`
class StrongParametersTest < ActionController::TestCase
def setup
@controller = WidgetsController.new
@@ -34,4 +39,82 @@ def test_permitted_params_from_update
Widget.expects(:find).with('42').returns(mock_widget)
put :update, :id => '42', :widget => {:permitted => 'param', :prohibited => 'param'}
end
-end
+
+ # `permitted_params` has greater priority than `widget_params`
+ def test_with_permitted_and_resource_methods
+ @controller.stubs(:widget_params).returns(:permitted => 'another_param')
+ class << @controller
+ private :widget_params
+ end
+ Widget.expects(:new).with(:permitted => 'param')
+ get :new, :widget => { :permitted => 'param', :prohibited => 'param' }
+ end
+end
+
+# test usage of `widget_params`
+class StrongParametersWithoutPermittedParamsTest < ActionController::TestCase
+ def setup
+ @controller = WidgetsController.new
+ @controller.stubs(:widget_url).returns("/")
+ @controller.stubs(:widget_params).returns(:permitted => 'param')
+ class << @controller
+ private :widget_params
+ end
+ end
+
+ def test_permitted_params_from_new
+ Widget.expects(:new).with(:permitted => 'param')
+ get :new, :widget => { :permitted => 'param', :prohibited => 'param' }
+ end
+
+ def test_permitted_params_from_create
+ Widget.expects(:new).with(:permitted => 'param').returns(mock(:save => true))
+ post :create, :widget => { :permitted => 'param', :prohibited => 'param' }
+ end
+
+ def test_permitted_params_from_update
+ mock_widget = mock
+ mock_widget.stubs(:class).returns(Widget)
+ mock_widget.expects(:update_attributes).with(:permitted => 'param')
+ Widget.expects(:find).with('42').returns(mock_widget)
+ put :update, :id => '42', :widget => {:permitted => 'param', :prohibited => 'param'}
+ end
+end
+
+# test usage of `widget_params` integrated with strong parameters (not using stubs)
+class StrongParametersIntegrationTest < ActionController::TestCase
+ def setup
+ @controller = WidgetsController.new
+ @controller.stubs(:widget_url).returns("/")
+
+ class << @controller
+ define_method :widget_params do
+ params.require(:widget).permit(:permitted)
+ end
+ private :widget_params
+ end
+ end
+
+ def test_permitted_empty_params_from_new
+ Widget.expects(:new).with({})
+ get :new, {}
+ end
+
+ def test_permitted_params_from_new
+ Widget.expects(:new).with('permitted' => 'param')
+ get :new, :widget => { :permitted => 'param', :prohibited => 'param' }
+ end
+
+ def test_permitted_params_from_create
+ Widget.expects(:new).with('permitted' => 'param').returns(mock(:save => true))
+ post :create, :widget => { :permitted => 'param', :prohibited => 'param' }
+ end
+
+ def test_permitted_params_from_update
+ mock_widget = mock
+ mock_widget.stubs(:class).returns(Widget)
+ mock_widget.expects(:update_attributes).with('permitted' => 'param')
+ Widget.expects(:find).with('42').returns(mock_widget)
+ put :update, :id => '42', :widget => {:permitted => 'param', :prohibited => 'param'}
+ end
+end
Something went wrong with that request. Please try again.