protected_attributes 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|