protected_attributes 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.travis.yml +17 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +111 -0
- data/Rakefile +11 -0
- data/lib/action_controller/accessible_params_wrapper.rb +29 -0
- data/lib/active_model/mass_assignment_security.rb +353 -0
- data/lib/active_model/mass_assignment_security/permission_set.rb +40 -0
- data/lib/active_model/mass_assignment_security/sanitizer.rb +74 -0
- data/lib/active_record/mass_assignment_security.rb +23 -0
- data/lib/active_record/mass_assignment_security/associations.rb +116 -0
- data/lib/active_record/mass_assignment_security/attribute_assignment.rb +88 -0
- data/lib/active_record/mass_assignment_security/core.rb +27 -0
- data/lib/active_record/mass_assignment_security/inheritance.rb +18 -0
- data/lib/active_record/mass_assignment_security/nested_attributes.rb +148 -0
- data/lib/active_record/mass_assignment_security/persistence.rb +81 -0
- data/lib/active_record/mass_assignment_security/reflection.rb +9 -0
- data/lib/active_record/mass_assignment_security/relation.rb +47 -0
- data/lib/active_record/mass_assignment_security/validations.rb +24 -0
- data/lib/protected_attributes.rb +14 -0
- data/lib/protected_attributes/railtie.rb +18 -0
- data/lib/protected_attributes/version.rb +3 -0
- data/protected_attributes.gemspec +26 -0
- data/test/abstract_unit.rb +156 -0
- data/test/accessible_params_wrapper_test.rb +76 -0
- data/test/ar_helper.rb +67 -0
- data/test/attribute_sanitization_test.rb +929 -0
- data/test/mass_assignment_security/black_list_test.rb +20 -0
- data/test/mass_assignment_security/permission_set_test.rb +36 -0
- data/test/mass_assignment_security/sanitizer_test.rb +50 -0
- data/test/mass_assignment_security/white_list_test.rb +19 -0
- data/test/mass_assignment_security_test.rb +118 -0
- data/test/models/company.rb +105 -0
- data/test/models/keyboard.rb +3 -0
- data/test/models/mass_assignment_specific.rb +76 -0
- data/test/models/person.rb +82 -0
- data/test/models/subscriber.rb +5 -0
- data/test/models/task.rb +5 -0
- data/test/test_helper.rb +3 -0
- metadata +199 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class BlackListTest < ActiveModel::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@black_list = ActiveModel::MassAssignmentSecurity::BlackList.new
|
7
|
+
@included_key = 'admin'
|
8
|
+
@black_list += [ @included_key ]
|
9
|
+
end
|
10
|
+
|
11
|
+
test "deny? is true for included items" do
|
12
|
+
assert_equal true, @black_list.deny?(@included_key)
|
13
|
+
end
|
14
|
+
|
15
|
+
test "deny? is false for non-included items" do
|
16
|
+
assert_equal false, @black_list.deny?('first_name')
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class PermissionSetTest < ActiveModel::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@permission_list = ActiveModel::MassAssignmentSecurity::PermissionSet.new
|
7
|
+
end
|
8
|
+
|
9
|
+
test "+ stringifies added collection values" do
|
10
|
+
symbol_collection = [ :admin ]
|
11
|
+
new_list = @permission_list += symbol_collection
|
12
|
+
|
13
|
+
assert new_list.include?('admin'), "did not add collection to #{@permission_list.inspect}}"
|
14
|
+
end
|
15
|
+
|
16
|
+
test "+ compacts added collection values" do
|
17
|
+
added_collection = [ nil ]
|
18
|
+
new_list = @permission_list + added_collection
|
19
|
+
assert_equal new_list, @permission_list, "did not add collection to #{@permission_list.inspect}}"
|
20
|
+
end
|
21
|
+
|
22
|
+
test "include? normalizes multi-parameter keys" do
|
23
|
+
multi_param_key = 'admin(1)'
|
24
|
+
new_list = @permission_list += [ 'admin' ]
|
25
|
+
|
26
|
+
assert new_list.include?(multi_param_key), "#{multi_param_key} not found in #{@permission_list.inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
test "include? normal keys" do
|
30
|
+
normal_key = 'admin'
|
31
|
+
new_list = @permission_list += [ normal_key ]
|
32
|
+
|
33
|
+
assert new_list.include?(normal_key), "#{normal_key} not found in #{@permission_list.inspect}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require 'active_support/logger'
|
3
|
+
|
4
|
+
class SanitizerTest < ActiveModel::TestCase
|
5
|
+
attr_accessor :logger
|
6
|
+
|
7
|
+
class Authorizer < ActiveModel::MassAssignmentSecurity::PermissionSet
|
8
|
+
def deny?(key)
|
9
|
+
['admin', 'id'].include?(key)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@logger_sanitizer = ActiveModel::MassAssignmentSecurity::LoggerSanitizer.new(self)
|
15
|
+
@strict_sanitizer = ActiveModel::MassAssignmentSecurity::StrictSanitizer.new(self)
|
16
|
+
@authorizer = Authorizer.new
|
17
|
+
end
|
18
|
+
|
19
|
+
test "sanitize attributes" do
|
20
|
+
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
|
21
|
+
attributes = @logger_sanitizer.sanitize(self.class, original_attributes, @authorizer)
|
22
|
+
|
23
|
+
assert attributes.key?('first_name'), "Allowed key shouldn't be rejected"
|
24
|
+
assert !attributes.key?('admin'), "Denied key should be rejected"
|
25
|
+
end
|
26
|
+
|
27
|
+
test "debug mass assignment removal with LoggerSanitizer" do
|
28
|
+
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
|
29
|
+
log = StringIO.new
|
30
|
+
self.logger = ActiveSupport::Logger.new(log)
|
31
|
+
@logger_sanitizer.sanitize(self.class, original_attributes, @authorizer)
|
32
|
+
assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}")
|
33
|
+
end
|
34
|
+
|
35
|
+
test "debug mass assignment removal with StrictSanitizer" do
|
36
|
+
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
|
37
|
+
assert_raise ActiveModel::MassAssignmentSecurity::Error do
|
38
|
+
@strict_sanitizer.sanitize(self.class, original_attributes, @authorizer)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
test "mass assignment insensitive attributes" do
|
43
|
+
original_attributes = {'id' => 1, 'first_name' => 'allowed'}
|
44
|
+
|
45
|
+
assert_nothing_raised do
|
46
|
+
@strict_sanitizer.sanitize(self.class, original_attributes, @authorizer)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class WhiteListTest < ActiveModel::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@white_list = ActiveModel::MassAssignmentSecurity::WhiteList.new
|
7
|
+
@included_key = 'first_name'
|
8
|
+
@white_list += [ @included_key ]
|
9
|
+
end
|
10
|
+
|
11
|
+
test "deny? is false for included items" do
|
12
|
+
assert_equal false, @white_list.deny?(@included_key)
|
13
|
+
end
|
14
|
+
|
15
|
+
test "deny? is true for non-included items" do
|
16
|
+
assert_equal true, @white_list.deny?('admin')
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_model/mass_assignment_security'
|
3
|
+
require 'models/mass_assignment_specific'
|
4
|
+
|
5
|
+
class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer
|
6
|
+
|
7
|
+
def process_removed_attributes(klass, attrs)
|
8
|
+
raise StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
class MassAssignmentSecurityTest < ActiveModel::TestCase
|
14
|
+
def test_attribute_protection
|
15
|
+
user = User.new
|
16
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
17
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
|
18
|
+
assert_equal expected, sanitized
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_attribute_protection_when_role_is_nil
|
22
|
+
user = User.new
|
23
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
24
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), nil)
|
25
|
+
assert_equal expected, sanitized
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_only_moderator_role_attribute_accessible
|
29
|
+
user = SpecialUser.new
|
30
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
31
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator)
|
32
|
+
assert_equal expected, sanitized
|
33
|
+
|
34
|
+
sanitized = user.sanitize_for_mass_assignment({ "name" => "John Smith", "email" => "john@smith.com", "admin" => true })
|
35
|
+
assert_equal({}, sanitized)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_attributes_accessible
|
39
|
+
user = Person.new
|
40
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
41
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
|
42
|
+
assert_equal expected, sanitized
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_attributes_accessible_with_admin_role
|
46
|
+
user = Person.new
|
47
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true }
|
48
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin)
|
49
|
+
assert_equal expected, sanitized
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_attributes_accessible_with_roles_given_as_array
|
53
|
+
user = Account.new
|
54
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
55
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
|
56
|
+
assert_equal expected, sanitized
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_attributes_accessible_with_admin_role_when_roles_given_as_array
|
60
|
+
user = Account.new
|
61
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true }
|
62
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin)
|
63
|
+
assert_equal expected, sanitized
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_attributes_protected_by_default
|
67
|
+
firm = Firm.new
|
68
|
+
expected = { }
|
69
|
+
sanitized = firm.sanitize_for_mass_assignment({ "type" => "Client" })
|
70
|
+
assert_equal expected, sanitized
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_mass_assignment_protection_inheritance
|
74
|
+
assert SpecialLoosePerson.accessible_attributes.blank?
|
75
|
+
assert_equal Set.new(['credit_rating', 'administrator']), SpecialLoosePerson.protected_attributes
|
76
|
+
|
77
|
+
assert SpecialLoosePerson.accessible_attributes.blank?
|
78
|
+
assert_equal Set.new(['credit_rating']), SpecialLoosePerson.protected_attributes(:admin)
|
79
|
+
|
80
|
+
assert LooseDescendant.accessible_attributes.blank?
|
81
|
+
assert_equal Set.new(['credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes
|
82
|
+
|
83
|
+
assert LooseDescendantSecond.accessible_attributes.blank?
|
84
|
+
assert_equal Set.new(['credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
|
85
|
+
'Running attr_protected twice in one class should merge the protections'
|
86
|
+
|
87
|
+
assert((SpecialTightPerson.protected_attributes - SpecialTightPerson.attributes_protected_by_default).blank?)
|
88
|
+
assert_equal Set.new(['name', 'address']), SpecialTightPerson.accessible_attributes
|
89
|
+
|
90
|
+
assert((SpecialTightPerson.protected_attributes(:admin) - SpecialTightPerson.attributes_protected_by_default).blank?)
|
91
|
+
assert_equal Set.new(['name', 'address', 'admin']), SpecialTightPerson.accessible_attributes(:admin)
|
92
|
+
|
93
|
+
assert((TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default).blank?)
|
94
|
+
assert_equal Set.new(['name', 'address', 'phone_number']), TightDescendant.accessible_attributes
|
95
|
+
|
96
|
+
assert((TightDescendant.protected_attributes(:admin) - TightDescendant.attributes_protected_by_default).blank?)
|
97
|
+
assert_equal Set.new(['name', 'address', 'admin', 'super_powers']), TightDescendant.accessible_attributes(:admin)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_mass_assignment_multiparameter_protector
|
101
|
+
task = Task.new
|
102
|
+
attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
|
103
|
+
sanitized = task.sanitize_for_mass_assignment(attributes)
|
104
|
+
assert_equal sanitized, { }
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_custom_sanitizer
|
108
|
+
old_sanitizer = User._mass_assignment_sanitizer
|
109
|
+
|
110
|
+
user = User.new
|
111
|
+
User.mass_assignment_sanitizer = CustomSanitizer.new
|
112
|
+
assert_raise StandardError do
|
113
|
+
user.sanitize_for_mass_assignment("admin" => true)
|
114
|
+
end
|
115
|
+
ensure
|
116
|
+
User.mass_assignment_sanitizer = old_sanitizer
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class AbstractCompany < ActiveRecord::Base
|
2
|
+
self.abstract_class = true
|
3
|
+
end
|
4
|
+
|
5
|
+
class Company < AbstractCompany
|
6
|
+
attr_protected :rating
|
7
|
+
self.sequence_name = :companies_nonstd_seq
|
8
|
+
|
9
|
+
validates_presence_of :name
|
10
|
+
|
11
|
+
has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account"
|
12
|
+
has_many :contracts
|
13
|
+
has_many :developers, :through => :contracts
|
14
|
+
|
15
|
+
def arbitrary_method
|
16
|
+
"I am Jack's profound disappointment"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def private_method
|
22
|
+
"I am Jack's innermost fears and aspirations"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Firm < Company
|
27
|
+
ActiveSupport::Deprecation.silence do
|
28
|
+
has_many :clients, -> { order "id" }, :dependent => :destroy, :counter_sql =>
|
29
|
+
"SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
|
30
|
+
"AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )",
|
31
|
+
:before_remove => :log_before_remove,
|
32
|
+
:after_remove => :log_after_remove
|
33
|
+
end
|
34
|
+
has_many :unsorted_clients, :class_name => "Client"
|
35
|
+
has_many :unsorted_clients_with_symbol, :class_name => :Client
|
36
|
+
has_many :clients_sorted_desc, -> { order "id DESC" }, :class_name => "Client"
|
37
|
+
has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client"
|
38
|
+
has_many :clients_ordered_by_name, -> { order "name" }, :class_name => "Client"
|
39
|
+
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
|
40
|
+
has_many :dependent_clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :dependent => :destroy
|
41
|
+
has_many :exclusively_dependent_clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all
|
42
|
+
has_many :limited_clients, -> { limit 1 }, :class_name => "Client"
|
43
|
+
has_many :clients_with_interpolated_conditions, ->(firm) { where "rating > #{firm.rating}" }, :class_name => "Client"
|
44
|
+
has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client"
|
45
|
+
has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client"
|
46
|
+
ActiveSupport::Deprecation.silence do
|
47
|
+
has_many :clients_using_sql, :class_name => "Client", :finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id}" }
|
48
|
+
has_many :clients_using_counter_sql, :class_name => "Client",
|
49
|
+
:finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id} " },
|
50
|
+
:counter_sql => proc { "SELECT COUNT(*) FROM companies WHERE client_of = #{id}" }
|
51
|
+
has_many :clients_using_zero_counter_sql, :class_name => "Client",
|
52
|
+
:finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id}" },
|
53
|
+
:counter_sql => proc { "SELECT 0 FROM companies WHERE client_of = #{id}" }
|
54
|
+
has_many :no_clients_using_counter_sql, :class_name => "Client",
|
55
|
+
:finder_sql => 'SELECT * FROM companies WHERE client_of = 1000',
|
56
|
+
:counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = 1000'
|
57
|
+
has_many :clients_using_finder_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE 1=1'
|
58
|
+
end
|
59
|
+
has_many :plain_clients, :class_name => 'Client'
|
60
|
+
has_many :readonly_clients, -> { readonly }, :class_name => 'Client'
|
61
|
+
has_many :clients_using_primary_key, :class_name => 'Client',
|
62
|
+
:primary_key => 'name', :foreign_key => 'firm_name'
|
63
|
+
has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client',
|
64
|
+
:primary_key => 'name', :foreign_key => 'firm_name', :dependent => :delete_all
|
65
|
+
has_many :clients_grouped_by_firm_id, -> { group("firm_id").select("firm_id") }, :class_name => "Client"
|
66
|
+
has_many :clients_grouped_by_name, -> { group("name").select("name") }, :class_name => "Client"
|
67
|
+
|
68
|
+
has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true
|
69
|
+
has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false
|
70
|
+
has_one :account_with_select, -> { select("id, firm_id") }, :foreign_key => "firm_id", :class_name=>'Account'
|
71
|
+
has_one :readonly_account, -> { readonly }, :foreign_key => "firm_id", :class_name => "Account"
|
72
|
+
# added order by id as in fixtures there are two accounts for Rails Core
|
73
|
+
# Oracle tests were failing because of that as the second fixture was selected
|
74
|
+
has_one :account_using_primary_key, -> { order('id') }, :primary_key => "firm_id", :class_name => "Account"
|
75
|
+
has_one :account_using_foreign_and_primary_keys, :foreign_key => "firm_name", :primary_key => "name", :class_name => "Account"
|
76
|
+
has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete
|
77
|
+
|
78
|
+
has_one :account_limit_500_with_hash_conditions, -> { where :credit_limit => 500 }, :foreign_key => "firm_id", :class_name => "Account"
|
79
|
+
|
80
|
+
has_one :unautosaved_account, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
|
81
|
+
has_many :accounts
|
82
|
+
has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
|
83
|
+
|
84
|
+
has_many :association_with_references, -> { references(:foo) }, :class_name => 'Client'
|
85
|
+
|
86
|
+
def log
|
87
|
+
@log ||= []
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def log_before_remove(record)
|
92
|
+
log << "before_remove#{record.id}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def log_after_remove(record)
|
96
|
+
log << "after_remove#{record.id}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Corporation < Company
|
101
|
+
attr_accessible :type, :name, :description
|
102
|
+
end
|
103
|
+
|
104
|
+
class SpecialCorporation < Corporation
|
105
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class User
|
2
|
+
include ActiveModel::MassAssignmentSecurity
|
3
|
+
attr_protected :admin
|
4
|
+
|
5
|
+
public :sanitize_for_mass_assignment
|
6
|
+
end
|
7
|
+
|
8
|
+
class SpecialUser
|
9
|
+
include ActiveModel::MassAssignmentSecurity
|
10
|
+
attr_accessible :name, :email, :as => :moderator
|
11
|
+
|
12
|
+
public :sanitize_for_mass_assignment
|
13
|
+
end
|
14
|
+
|
15
|
+
class Person
|
16
|
+
include ActiveModel::MassAssignmentSecurity
|
17
|
+
attr_accessible :name, :email
|
18
|
+
attr_accessible :name, :email, :admin, :as => :admin
|
19
|
+
|
20
|
+
public :sanitize_for_mass_assignment
|
21
|
+
end
|
22
|
+
|
23
|
+
class Account
|
24
|
+
include ActiveModel::MassAssignmentSecurity
|
25
|
+
attr_accessible :name, :email, :as => [:default, :admin]
|
26
|
+
attr_accessible :admin, :as => :admin
|
27
|
+
|
28
|
+
public :sanitize_for_mass_assignment
|
29
|
+
end
|
30
|
+
|
31
|
+
class Firm
|
32
|
+
include ActiveModel::MassAssignmentSecurity
|
33
|
+
|
34
|
+
public :sanitize_for_mass_assignment
|
35
|
+
|
36
|
+
def self.attributes_protected_by_default
|
37
|
+
["type"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Task
|
42
|
+
include ActiveModel::MassAssignmentSecurity
|
43
|
+
attr_protected :starting
|
44
|
+
|
45
|
+
public :sanitize_for_mass_assignment
|
46
|
+
end
|
47
|
+
|
48
|
+
class SpecialLoosePerson
|
49
|
+
include ActiveModel::MassAssignmentSecurity
|
50
|
+
attr_protected :credit_rating, :administrator
|
51
|
+
attr_protected :credit_rating, :as => :admin
|
52
|
+
end
|
53
|
+
|
54
|
+
class LooseDescendant < SpecialLoosePerson
|
55
|
+
attr_protected :phone_number
|
56
|
+
end
|
57
|
+
|
58
|
+
class LooseDescendantSecond< SpecialLoosePerson
|
59
|
+
attr_protected :phone_number
|
60
|
+
attr_protected :name
|
61
|
+
end
|
62
|
+
|
63
|
+
class SpecialTightPerson
|
64
|
+
include ActiveModel::MassAssignmentSecurity
|
65
|
+
attr_accessible :name, :address
|
66
|
+
attr_accessible :name, :address, :admin, :as => :admin
|
67
|
+
|
68
|
+
def self.attributes_protected_by_default
|
69
|
+
["mobile_number"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class TightDescendant < SpecialTightPerson
|
74
|
+
attr_accessible :phone_number
|
75
|
+
attr_accessible :super_powers, :as => :admin
|
76
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class Person < ActiveRecord::Base
|
2
|
+
has_many :readers
|
3
|
+
has_many :secure_readers
|
4
|
+
has_one :reader
|
5
|
+
|
6
|
+
has_many :posts, :through => :readers
|
7
|
+
has_many :secure_posts, :through => :secure_readers
|
8
|
+
has_many :posts_with_no_comments, -> { includes(:comments).where('comments.id is null').references(:comments) },
|
9
|
+
:through => :readers, :source => :post
|
10
|
+
|
11
|
+
has_many :followers, foreign_key: 'friend_id', class_name: 'Friendship'
|
12
|
+
|
13
|
+
has_many :references
|
14
|
+
has_many :bad_references
|
15
|
+
has_many :fixed_bad_references, -> { where :favourite => true }, :class_name => 'BadReference'
|
16
|
+
has_one :favourite_reference, -> { where 'favourite=?', true }, :class_name => 'Reference'
|
17
|
+
has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order('comments.id') }, :through => :readers, :source => :post
|
18
|
+
|
19
|
+
has_many :jobs, :through => :references
|
20
|
+
has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy
|
21
|
+
has_many :jobs_with_dependent_delete_all, :source => :job, :through => :references, :dependent => :delete_all
|
22
|
+
has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify
|
23
|
+
|
24
|
+
belongs_to :primary_contact, :class_name => 'Person'
|
25
|
+
has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id'
|
26
|
+
has_many :agents_of_agents, :through => :agents, :source => :agents
|
27
|
+
belongs_to :number1_fan, :class_name => 'Person'
|
28
|
+
|
29
|
+
has_many :agents_posts, :through => :agents, :source => :posts
|
30
|
+
has_many :agents_posts_authors, :through => :agents_posts, :source => :author
|
31
|
+
|
32
|
+
scope :males, -> { where(:gender => 'M') }
|
33
|
+
scope :females, -> { where(:gender => 'F') }
|
34
|
+
end
|
35
|
+
|
36
|
+
class LoosePerson < ActiveRecord::Base
|
37
|
+
self.table_name = 'people'
|
38
|
+
self.abstract_class = true
|
39
|
+
|
40
|
+
attr_protected :comments, :best_friend_id, :best_friend_of_id
|
41
|
+
attr_protected :as => :admin
|
42
|
+
|
43
|
+
has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
|
44
|
+
belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id
|
45
|
+
has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
|
46
|
+
|
47
|
+
accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends
|
48
|
+
end
|
49
|
+
|
50
|
+
class TightPerson < ActiveRecord::Base
|
51
|
+
self.table_name = 'people'
|
52
|
+
|
53
|
+
attr_accessible :first_name, :gender
|
54
|
+
attr_accessible :first_name, :gender, :comments, :as => :admin
|
55
|
+
attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes
|
56
|
+
attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes, :as => :admin
|
57
|
+
|
58
|
+
has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id
|
59
|
+
belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id
|
60
|
+
has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id
|
61
|
+
|
62
|
+
accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends
|
63
|
+
end
|
64
|
+
|
65
|
+
class NestedPerson < ActiveRecord::Base
|
66
|
+
self.table_name = 'people'
|
67
|
+
|
68
|
+
attr_accessible :first_name, :best_friend_first_name, :best_friend_attributes
|
69
|
+
attr_accessible :first_name, :gender, :comments, :as => :admin
|
70
|
+
attr_accessible :best_friend_attributes, :best_friend_first_name, :as => :admin
|
71
|
+
|
72
|
+
has_one :best_friend, :class_name => 'NestedPerson', :foreign_key => :best_friend_id
|
73
|
+
accepts_nested_attributes_for :best_friend, :update_only => true
|
74
|
+
|
75
|
+
def comments=(new_comments)
|
76
|
+
raise RuntimeError
|
77
|
+
end
|
78
|
+
|
79
|
+
def best_friend_first_name=(new_name)
|
80
|
+
assign_attributes({ :best_friend_attributes => { :first_name => new_name } })
|
81
|
+
end
|
82
|
+
end
|