mass_assignment 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +129 -0
- data/Rakefile +23 -0
- data/lib/mass_assignment.rb +91 -0
- data/rails/init.rb +1 -0
- data/test/mass_assignment_test.rb +146 -0
- data/test/test_helper.rb +12 -0
- metadata +87 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Lance Ivy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
h2. MassAssignment
|
2
|
+
|
3
|
+
Copyright (c) 2009 Lance Ivy, released under the MIT license
|
4
|
+
|
5
|
+
From initial discussion at http://groups.google.com/group/rubyonrails-core/browse_thread/thread/3b6818496d0d07f1
|
6
|
+
|
7
|
+
h3. What It Is
|
8
|
+
|
9
|
+
A robust mass assignment method with a small and obvious syntax.
|
10
|
+
|
11
|
+
The normal mass assignment protection comes from attr_protected and attr_accessible. There are a few problems with this approach:
|
12
|
+
|
13
|
+
* Often never implemented, leaving a wide-open system. And once implemented, easy to forget when adding new attributes, leading to bugs (in an attr_accessible system) or security holes (in an attr_protected system).
|
14
|
+
* Restricts coding syntax. You can't easily use update_attributes() or attributes= because your whitelist/blacklist gets in your own way.
|
15
|
+
* Not contextual. The list of allowed attributes can't change to accomodate different user permissions or situations.
|
16
|
+
|
17
|
+
This plugin's solution is to let you specify an obvious list of allowed attributes when you mass assign attributes.
|
18
|
+
|
19
|
+
* The list of allowed attributes is in your controller at calltime, so it's easier to remember and update (it's not a hidden, magical system).
|
20
|
+
* The list of allowed attributes is optional, so it doesn't get in your way.
|
21
|
+
* Your controller can easily enforce permissions by evaluating the current user (Admin Controller) or the current situation (creates vs updates).
|
22
|
+
|
23
|
+
And as a bonus, permission plugins have a much easier time of things. The list of allowed attributes may be pulled from a permissions table without any awkward User.current class or thread variables.
|
24
|
+
|
25
|
+
For those who would still like attr_protected- and attr_accessible-like functionality, this plugin offers mass assignment policies. You may choose to a default mass assignment protection as open or closed as you like using familiar :only/:except syntax. But you may also specify regular expressions such as /_id$/ to reject all id fields by default, and since these policies inherit, you may set them globally on ActiveRecord::Base. See the examples below.
|
26
|
+
|
27
|
+
h3. Example
|
28
|
+
|
29
|
+
Let's take a very plausible situation where you would want three separate lists of allowed attributes. You have users that sign up to your application. But after they have signed up, they may not change their username. Admins, however, may manually change a username as needed.
|
30
|
+
|
31
|
+
<pre><code>
|
32
|
+
class UsersController < ApplicationController
|
33
|
+
def create
|
34
|
+
@user = User.new
|
35
|
+
# during signup a user may pick a username
|
36
|
+
@user.assign(params[:user], [:username, :email, :password, :password_confirmation])
|
37
|
+
@user.save!
|
38
|
+
...
|
39
|
+
end
|
40
|
+
|
41
|
+
def update
|
42
|
+
@user = User.find(params[:id])
|
43
|
+
# username is no longer accepted later
|
44
|
+
@user.assign(params[:user], [:email, :password, :password_confirmation])
|
45
|
+
@user.save!
|
46
|
+
...
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Admin::UsersController < ApplicationController
|
51
|
+
before_filter :admin_required
|
52
|
+
|
53
|
+
def update
|
54
|
+
@user = User.find(params[:id])
|
55
|
+
# admins, on the other hand, may change the username as needed, but may not set passwords
|
56
|
+
@user.assign(:params[:user], [:username, :email])
|
57
|
+
@user.save!
|
58
|
+
...
|
59
|
+
end
|
60
|
+
end
|
61
|
+
</code></pre>
|
62
|
+
|
63
|
+
If you don't always want to set attribute lists, you may use the mass_assignment_policy API to configure defaults whitelists or blacklists.
|
64
|
+
|
65
|
+
<pre><code>
|
66
|
+
class User < ActiveRecord::Base
|
67
|
+
# The boring usage. You may as well specify attributes at calltime.
|
68
|
+
mass_assignment_policy :only => [:email, :username]
|
69
|
+
|
70
|
+
# More interesting. No id fields!
|
71
|
+
mass_assignment_policy :except => /_id$/
|
72
|
+
end
|
73
|
+
|
74
|
+
# Hardcore. Disables mass assignment globally unless overridden!
|
75
|
+
ActiveRecord::Base.mass_assignment_policy :except => :all
|
76
|
+
</code></pre>
|
77
|
+
|
78
|
+
Note that mass_assignment_policy only applies to usage of methods supplied in this plugin.
|
79
|
+
|
80
|
+
h3. Nested Assignment
|
81
|
+
|
82
|
+
Nested assignment is supported by passing hashes and arrays for the whitelist.
|
83
|
+
|
84
|
+
<pre><code>
|
85
|
+
class Pirate < ActiveRecord::Base
|
86
|
+
accepts_nested_attributes_for :ships
|
87
|
+
end
|
88
|
+
|
89
|
+
class PiratesController < ApplicationController
|
90
|
+
def update
|
91
|
+
@pirate = Pirate.find(params[:id])
|
92
|
+
@pirate.assign(params[:pirate], [:name, :eyepatch, {:ships_attributes => [:id, :_destroy, :name, :cannons, :capacity]}])
|
93
|
+
@pirate.save!
|
94
|
+
...
|
95
|
+
end
|
96
|
+
end
|
97
|
+
</code></pre>
|
98
|
+
|
99
|
+
As you can see, the punctuation gets messy. I'm interested in better options.
|
100
|
+
|
101
|
+
h3. Deep Assignment
|
102
|
+
|
103
|
+
Sometimes nested assignment isn't completely appropriate but you still need to assign attributes to associated objects. Try the block syntax:
|
104
|
+
|
105
|
+
<pre><code>
|
106
|
+
class ShipsController < ApplicationController
|
107
|
+
def update
|
108
|
+
@ship = Ship.find(params[:id])
|
109
|
+
@ship.assign(params[:ship], [:name, :cannons, :capacity]) do |ship_params|
|
110
|
+
@ship.pirate.assign(ship_params[:pirate], [:name, :eyepatch])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
</code></pre>
|
115
|
+
|
116
|
+
The major benefit here is that the block won't yield unless the params exist, so you don't need to check 'if params[:ship] && params[:ship][:pirate]'.
|
117
|
+
|
118
|
+
h3. Feedback
|
119
|
+
|
120
|
+
I can think of a couple alternate implementations for this API. Consider:
|
121
|
+
|
122
|
+
@user.assign(params[:user], [:username, :email])
|
123
|
+
|
124
|
+
vs
|
125
|
+
|
126
|
+
@user.assign(params[:user], :only => [:username, :email])
|
127
|
+
@user.assign(params[:user], :except => [:admin])
|
128
|
+
|
129
|
+
I personally prefer the former because I think that blacklists are inherently less safe than whitelists, with no compensating advantage. But I'd love to hear some other use cases and get feedback on this! Fork this repo and send pull requests or just contact me (github.com/cainlevy). Let's talk.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the mass_assignment plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the mass_assignment plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'MassAssignment'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module MassAssignment
|
2
|
+
VERSION = '1.0.0'
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do extend ClassMethods end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Basic Example:
|
9
|
+
#
|
10
|
+
# @user = User.new
|
11
|
+
# @user.assign(params[:user], [:username, :email, :password, :password_confirmation])
|
12
|
+
# @user.save!
|
13
|
+
#
|
14
|
+
# Nested Assignment:
|
15
|
+
#
|
16
|
+
# @user = User.find_by_id(params[:id])
|
17
|
+
# @user.assign(params[:user], [:username, :email, {:dog_attributes => [:id, :_destroy, :name, :color]}])
|
18
|
+
# @user.save!
|
19
|
+
#
|
20
|
+
# Deep Assignment:
|
21
|
+
#
|
22
|
+
# @user = User.find_by_id(params[:id])
|
23
|
+
# @user.assign(params[:user], [:username, :email]) do |user_params|
|
24
|
+
# @user.dog.assign(user_params[:dog], [:name, :color])
|
25
|
+
# end
|
26
|
+
# @user.save!
|
27
|
+
def assign(attributes, allowed_attributes = nil, &block)
|
28
|
+
return unless attributes and attributes.is_a? Hash
|
29
|
+
|
30
|
+
if allowed_attributes
|
31
|
+
safe_attributes = filter_attributes(attributes, :only => allowed_attributes)
|
32
|
+
yield attributes if block_given?
|
33
|
+
self.send("attributes=", safe_attributes, false)
|
34
|
+
else
|
35
|
+
if policy = self.class.get_mass_assignment_policy
|
36
|
+
safe_attributes = filter_attributes(attributes, policy)
|
37
|
+
self.send("attributes=", safe_attributes, false)
|
38
|
+
else
|
39
|
+
# backwards compatibility. use attr_protected and attr_accessible.
|
40
|
+
self.attributes = attributes
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def filter_attributes(attributes, options = {}) # could surely be refactored.
|
48
|
+
attributes = attributes.stringify_keys
|
49
|
+
|
50
|
+
if options[:only]
|
51
|
+
if options[:only].is_a? Regexp
|
52
|
+
attributes.reject { |k, v| !k.gsub(/\(.+/, "").match(options[:only]) }
|
53
|
+
elsif options[:only] == :all
|
54
|
+
attributes
|
55
|
+
else
|
56
|
+
whitelist = options[:only].map{|i| i.is_a?(Hash) ? i.keys.first.to_s : i.to_s}
|
57
|
+
options[:only].each do |i|
|
58
|
+
next unless i.is_a? Hash
|
59
|
+
name = i.keys.first.to_s
|
60
|
+
next unless attributes[name].is_a? Hash
|
61
|
+
attributes[name] = filter_attributes(attributes[name], :only => i.values.first)
|
62
|
+
end
|
63
|
+
attributes.reject { |k, v| !whitelist.include?(k.gsub(/\(.+/, "")) }
|
64
|
+
end
|
65
|
+
elsif options[:except]
|
66
|
+
if options[:except].is_a? Regexp
|
67
|
+
attributes.reject { |k, v| k.gsub(/\(.+/, "").match(options[:except]) }
|
68
|
+
elsif options[:except] == :all
|
69
|
+
{}
|
70
|
+
else
|
71
|
+
blacklist = options[:except].map(&:to_s)
|
72
|
+
attributes.reject { |k, v| blacklist.include?(k.gsub(/\(.+/, "")) }
|
73
|
+
end
|
74
|
+
else
|
75
|
+
attributes
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module ClassMethods
|
80
|
+
# sets a default mass assignment policy for your model's attributes. you may choose to start from a
|
81
|
+
# closed state that allows no mass assignment, an open state that allows any mass assignment (this is
|
82
|
+
# activerecord's default), or somewhere inbetween.
|
83
|
+
def mass_assignment_policy(val)
|
84
|
+
write_inheritable_attribute :mass_assignment_policy, val
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_mass_assignment_policy
|
88
|
+
read_inheritable_attribute :mass_assignment_policy
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ActiveRecord::Base.class_eval do include MassAssignment end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class User < ActiveRecord::Base
|
4
|
+
class << self
|
5
|
+
def columns
|
6
|
+
@columns ||= [
|
7
|
+
ActiveRecord::ConnectionAdapters::Column.new("name", nil, "varchar(100)", true),
|
8
|
+
ActiveRecord::ConnectionAdapters::Column.new("email", nil, "varchar(100)", true),
|
9
|
+
ActiveRecord::ConnectionAdapters::Column.new("role_id", nil, "integer(11)", false)
|
10
|
+
]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def friend
|
15
|
+
@friend ||= User.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def friend_attributes=(attributes)
|
19
|
+
friend.attributes = attributes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ProtectedUser < User
|
24
|
+
attr_protected :role_id
|
25
|
+
end
|
26
|
+
|
27
|
+
class ClosedUser < User
|
28
|
+
mass_assignment_policy :except => :all
|
29
|
+
end
|
30
|
+
|
31
|
+
class SemiClosedUser < User
|
32
|
+
mass_assignment_policy :except => /_id$/
|
33
|
+
end
|
34
|
+
|
35
|
+
class OpenUser < User
|
36
|
+
mass_assignment_policy :only => :all
|
37
|
+
end
|
38
|
+
|
39
|
+
class MassAssignmentTest < ActiveSupport::TestCase
|
40
|
+
def setup
|
41
|
+
@user = ProtectedUser.new
|
42
|
+
@attributes = {"name" => "Bob", "email" => "bob@example.com"}
|
43
|
+
ActiveRecord::Base.logger = stub('debug' => true)
|
44
|
+
end
|
45
|
+
|
46
|
+
test "assigning nothing" do
|
47
|
+
params = {}
|
48
|
+
assert_nothing_raised do
|
49
|
+
@user.assign(params[:user])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
test "assigning a string" do
|
54
|
+
params = {:user => "well this is embarrassing"}
|
55
|
+
assert_nothing_raised do
|
56
|
+
@user.assign(params[:user])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
test "assigning attributes" do
|
61
|
+
@user.assign(@attributes)
|
62
|
+
assert_equal "Bob", @user.name
|
63
|
+
assert_equal "bob@example.com", @user.email
|
64
|
+
end
|
65
|
+
|
66
|
+
test "assigning protected attributes" do
|
67
|
+
@user.assign(@attributes.merge(:role_id => 1))
|
68
|
+
assert_equal "Bob", @user.name
|
69
|
+
assert_nil @user.role_id
|
70
|
+
end
|
71
|
+
|
72
|
+
test "overriding protected attributes" do
|
73
|
+
@user.assign(@attributes.merge(:role_id => 1), [:name, :email, :role_id])
|
74
|
+
assert_equal "Bob", @user.name
|
75
|
+
assert_equal 1, @user.role_id
|
76
|
+
end
|
77
|
+
|
78
|
+
test "assigning unallowed attributes" do
|
79
|
+
@user.assign(@attributes.merge(:role_id => 1), [:name, :email])
|
80
|
+
assert_equal "Bob", @user.name
|
81
|
+
assert_nil @user.role_id
|
82
|
+
end
|
83
|
+
|
84
|
+
test "nested assignment" do
|
85
|
+
@user.assign(@attributes.merge(:friend_attributes => {:name => 'Joe', :role_id => 1}), [:name, :role_id, {:friend_attributes => [:name]}])
|
86
|
+
assert_equal "Joe", @user.friend.name
|
87
|
+
assert_nil @user.friend.role_id
|
88
|
+
end
|
89
|
+
|
90
|
+
test "deep assignment" do
|
91
|
+
@user.assign(@attributes.merge(:friend => {:name => 'Joe', :role_id => 1}), [:name, :role_id]) do |params|
|
92
|
+
@user.friend.assign(params[:friend], [:name])
|
93
|
+
end
|
94
|
+
assert_equal "Joe", @user.friend.name
|
95
|
+
assert_nil @user.friend.role_id
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class MassAssignmentPolicyTest < ActiveSupport::TestCase
|
100
|
+
def setup
|
101
|
+
@attributes = {"name" => "Bob", "role_id" => 1}
|
102
|
+
ActiveRecord::Base.logger = stub('debug' => true)
|
103
|
+
end
|
104
|
+
|
105
|
+
test "an open policy" do
|
106
|
+
@user = OpenUser.new
|
107
|
+
@user.assign(@attributes)
|
108
|
+
assert_equal "Bob", @user.name
|
109
|
+
assert_equal 1, @user.role_id
|
110
|
+
end
|
111
|
+
|
112
|
+
test "an overridden open policy" do
|
113
|
+
@user = OpenUser.new
|
114
|
+
@user.assign(@attributes, [:name])
|
115
|
+
assert_equal "Bob", @user.name
|
116
|
+
assert_nil @user.role_id
|
117
|
+
end
|
118
|
+
|
119
|
+
test "a closed policy" do
|
120
|
+
@user = ClosedUser.new
|
121
|
+
@user.assign(@attributes)
|
122
|
+
assert_nil @user.name
|
123
|
+
assert_nil @user.role_id
|
124
|
+
end
|
125
|
+
|
126
|
+
test "an overridden closed policy" do
|
127
|
+
@user = ClosedUser.new
|
128
|
+
@user.assign(@attributes, [:name, :role_id])
|
129
|
+
assert_equal "Bob", @user.name
|
130
|
+
assert_equal 1, @user.role_id
|
131
|
+
end
|
132
|
+
|
133
|
+
test "a semi-closed policy" do
|
134
|
+
@user = SemiClosedUser.new
|
135
|
+
@user.assign(@attributes)
|
136
|
+
assert_equal "Bob", @user.name
|
137
|
+
assert_nil @user.role_id
|
138
|
+
end
|
139
|
+
|
140
|
+
test "an overridden semi-closed policy" do
|
141
|
+
@user = SemiClosedUser.new
|
142
|
+
@user.assign(@attributes, [:name, :role_id])
|
143
|
+
assert_equal "Bob", @user.name
|
144
|
+
assert_equal 1, @user.role_id
|
145
|
+
end
|
146
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
gem 'rails', '3.0.6'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/test_case'
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
PLUGIN_ROOT = File.dirname(__FILE__) + '/../'
|
9
|
+
ActiveSupport::Dependencies.autoload_paths << File.join(PLUGIN_ROOT, 'lib')
|
10
|
+
require 'rails/init'
|
11
|
+
|
12
|
+
require 'mocha'
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mass_assignment
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Lance Ivy
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-13 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: mocha
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: An alternative to attr_protected that supports a simpler, more secure params assignment mindset while also encouraging obviousness.
|
36
|
+
email: lance@cainlevy.net
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/mass_assignment.rb
|
45
|
+
- LICENSE
|
46
|
+
- README.textile
|
47
|
+
- Rakefile
|
48
|
+
- rails/init.rb
|
49
|
+
- test/test_helper.rb
|
50
|
+
- test/mass_assignment_test.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/cainlevy/mass_assignment
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.7
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Simple and secure params assignment for ActiveRecord
|
85
|
+
test_files:
|
86
|
+
- test/test_helper.rb
|
87
|
+
- test/mass_assignment_test.rb
|