property 0.9.1 → 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 +1 -0
- data/History.txt +19 -3
- data/README.rdoc +51 -6
- data/lib/property.rb +4 -2
- data/lib/property/attribute.rb +14 -7
- data/lib/property/base.rb +16 -0
- data/lib/property/column.rb +13 -1
- data/lib/property/declaration.rb +48 -26
- data/lib/property/error.rb +4 -0
- data/lib/property/index.rb +104 -29
- data/lib/property/properties.rb +7 -1
- data/lib/property/redefined_method_error.rb +8 -0
- data/lib/property/redefined_property_error.rb +8 -0
- data/lib/property/role.rb +30 -0
- data/lib/property/{behavior.rb → role_module.rb} +67 -35
- data/lib/property/schema.rb +76 -32
- data/lib/property/stored_column.rb +30 -0
- data/lib/property/stored_role.rb +101 -0
- data/property.gemspec +20 -5
- data/test/fixtures.rb +39 -1
- data/test/shoulda_macros/index.rb +95 -0
- data/test/shoulda_macros/role.rb +237 -0
- data/test/shoulda_macros/serialization.rb +1 -1
- data/test/test_helper.rb +2 -0
- data/test/unit/property/base_test.rb +80 -0
- data/test/unit/property/declaration_test.rb +89 -30
- data/test/unit/property/index_foreign_test.rb +2 -2
- data/test/unit/property/index_simple_test.rb +17 -4
- data/test/unit/property/role_test.rb +78 -0
- data/test/unit/property/stored_role_test.rb +84 -0
- data/test/unit/property/validation_test.rb +26 -4
- metadata +22 -7
- data/test/unit/property/behavior_test.rb +0 -146
@@ -19,7 +19,7 @@ class Test::Unit::TestCase
|
|
19
19
|
context 'with Properties' do
|
20
20
|
setup do
|
21
21
|
@properties = Property::Properties[
|
22
|
-
'string' =>
|
22
|
+
'string' => "one\ntwo",
|
23
23
|
'serialized' => Dog.new('Pavlov', 'Freud'),
|
24
24
|
'datetime' => Time.utc(2010, 02, 12, 21, 31, 25),
|
25
25
|
'float' => 4.3432,
|
data/test/test_helper.rb
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'fixtures'
|
3
|
+
|
4
|
+
# including Property::Base is like including Property but without hooks
|
5
|
+
class BaseTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context 'An external storage including property base' do
|
8
|
+
class Version < ActiveRecord::Base
|
9
|
+
include Property::Base
|
10
|
+
belongs_to :contact, :class_name => 'BaseTest::Contact',
|
11
|
+
:foreign_key => 'employee_id'
|
12
|
+
property do |p|
|
13
|
+
p.string 'first_name'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Contact < ActiveRecord::Base
|
18
|
+
set_table_name :employees
|
19
|
+
has_many :versions, :class_name => 'BaseTest::Version'
|
20
|
+
|
21
|
+
include Property
|
22
|
+
store_properties_in :version
|
23
|
+
|
24
|
+
property do |p|
|
25
|
+
p.string 'first_name'
|
26
|
+
p.string 'name'
|
27
|
+
end
|
28
|
+
|
29
|
+
def version
|
30
|
+
@version ||= begin
|
31
|
+
if new_record?
|
32
|
+
versions.build
|
33
|
+
else
|
34
|
+
Version.first(:conditions => ['employee_id = ?', self.id]) || versions.build
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with properties' do
|
41
|
+
setup do
|
42
|
+
contact = Contact.create('first_name' => 'Angela', 'name' => 'Davis')
|
43
|
+
@version = Version.find(contact.version.id)
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'unpack and read properties' do
|
47
|
+
assert_equal 'Angela', @version.first_name
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'not execute save hooks' do
|
51
|
+
@version.prop['first_name'] = 'Angel'
|
52
|
+
assert @version.save
|
53
|
+
@version = Version.find(@version)
|
54
|
+
assert_equal 'Angela', @version.first_name
|
55
|
+
end
|
56
|
+
|
57
|
+
should 'not only have accessors for properties defined in self' do
|
58
|
+
assert_raise(ActiveRecord::UnknownAttributeError) do
|
59
|
+
@version.update_attributes('name' => 'Crenshaw')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
@@ -6,52 +6,72 @@ class DeclarationTest < Test::Unit::TestCase
|
|
6
6
|
context 'A sub-class' do
|
7
7
|
context 'from a class with property columns' do
|
8
8
|
setup do
|
9
|
-
@
|
9
|
+
@ParentClass = Class.new(ActiveRecord::Base) do
|
10
|
+
include Property
|
11
|
+
property.string 'name'
|
12
|
+
def method_in_parent
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
subject do
|
18
|
+
Class.new(@ParentClass) do
|
19
|
+
property.integer 'age'
|
20
|
+
def method_in_self
|
21
|
+
end
|
22
|
+
end
|
10
23
|
end
|
11
24
|
|
12
25
|
should 'inherit property columns from parent class' do
|
13
|
-
assert_equal %w{age
|
26
|
+
assert_equal %w{age name}, subject.schema.column_names.sort
|
14
27
|
end
|
15
28
|
|
16
29
|
should 'see its property columns in schema' do
|
17
|
-
assert
|
30
|
+
assert subject.schema.has_column?('age')
|
18
31
|
end
|
19
32
|
|
20
33
|
should 'not back-propagate definitions to parent' do
|
21
|
-
assert
|
34
|
+
assert !subject.superclass.schema.has_column?('age')
|
22
35
|
end
|
23
36
|
|
24
37
|
should 'inherit new definitions in parent' do
|
25
|
-
|
26
|
-
include Property
|
27
|
-
property.string 'name'
|
28
|
-
end
|
29
|
-
|
30
|
-
@klass = Class.new(ParentClass) do
|
31
|
-
property.integer 'age'
|
32
|
-
end
|
33
|
-
|
34
|
-
assert_equal %w{age name}, @klass.schema.column_names.sort
|
35
|
-
|
36
|
-
ParentClass.class_eval do
|
38
|
+
@ParentClass.class_eval do
|
37
39
|
property.string 'first_name'
|
38
40
|
end
|
39
41
|
|
40
|
-
assert_equal %w{age first_name name},
|
42
|
+
assert_equal %w{age first_name name}, subject.schema.column_names.sort
|
41
43
|
end
|
42
44
|
|
43
45
|
should 'not be allowed to overwrite a property from the parent class' do
|
44
|
-
assert_raise(
|
45
|
-
|
46
|
-
property.string '
|
46
|
+
assert_raise(Property::RedefinedPropertyError) do
|
47
|
+
subject.class_eval do
|
48
|
+
property.string 'name'
|
47
49
|
end
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
53
|
should 'not be allowed to overwrite a property from the current class' do
|
52
|
-
assert_raise(
|
53
|
-
|
54
|
-
property.string '
|
54
|
+
assert_raise(Property::RedefinedPropertyError) do
|
55
|
+
subject.class_eval do
|
56
|
+
property.string 'age'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# This is because we include a module and the module would hide the method
|
62
|
+
should 'not be allowed to define a property with the name of a method in the parent class' do
|
63
|
+
assert_raise(Property::RedefinedMethodError) do
|
64
|
+
subject.class_eval do
|
65
|
+
property.string 'method_in_parent'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# This is ok because it can be useful and the module inclusion would not hide it
|
71
|
+
should 'be allowed to define a property with the same name as a method in the current class' do
|
72
|
+
assert_nothing_raised do
|
73
|
+
subject.class_eval do
|
74
|
+
property.string 'method_in_self'
|
55
75
|
end
|
56
76
|
end
|
57
77
|
end
|
@@ -135,6 +155,16 @@ class DeclarationTest < Test::Unit::TestCase
|
|
135
155
|
assert_equal Time, column.klass
|
136
156
|
assert_equal :datetime, column.type
|
137
157
|
end
|
158
|
+
|
159
|
+
should 'allow multiple declarations in one go' do
|
160
|
+
subject.property.string 'foo', 'bar', 'baz'
|
161
|
+
assert_equal %w{bar baz foo}, subject.schema.column_names.sort
|
162
|
+
end
|
163
|
+
|
164
|
+
should 'allow multiple declarations in an Array' do
|
165
|
+
subject.property.string ['foo', 'bar', 'baz']
|
166
|
+
assert_equal %w{bar baz foo}, subject.schema.column_names.sort
|
167
|
+
end
|
138
168
|
|
139
169
|
should 'allow serialized columns' do
|
140
170
|
Dog = Struct.new(:name, :toy) do
|
@@ -167,15 +197,15 @@ class DeclarationTest < Test::Unit::TestCase
|
|
167
197
|
assert column.indexed?
|
168
198
|
end
|
169
199
|
|
170
|
-
context 'through a
|
200
|
+
context 'through a Role on an instance' do
|
171
201
|
setup do
|
172
202
|
@instance = subject.new
|
173
|
-
@poet = Property::
|
203
|
+
@poet = Property::Role.new('Poet')
|
174
204
|
@poet.property do |p|
|
175
205
|
p.string 'poem'
|
176
206
|
end
|
177
207
|
|
178
|
-
@instance.
|
208
|
+
@instance.has_role @poet
|
179
209
|
end
|
180
210
|
|
181
211
|
should 'behave like any other property column' do
|
@@ -211,15 +241,44 @@ class DeclarationTest < Test::Unit::TestCase
|
|
211
241
|
end
|
212
242
|
end
|
213
243
|
|
214
|
-
context '
|
215
|
-
subject { Developer }
|
244
|
+
context 'A class with a schema' do
|
245
|
+
subject { Class.new(Developer) }
|
216
246
|
|
217
247
|
should 'raise an exception if we ask to behave like a class without schema' do
|
218
|
-
assert_raise(TypeError) { subject.
|
248
|
+
assert_raise(TypeError) { subject.has_role String }
|
219
249
|
end
|
220
250
|
|
221
251
|
should 'raise an exception if we ask to behave like an object' do
|
222
|
-
assert_raise(TypeError) { subject.
|
252
|
+
assert_raise(TypeError) { subject.has_role 'me' }
|
253
|
+
end
|
254
|
+
|
255
|
+
should 'raise an exception if the role redefines properties' do
|
256
|
+
@emp = Property::Role.new('empi')
|
257
|
+
@emp.property.string 'first_name'
|
258
|
+
assert_raise(Property::RedefinedPropertyError) do
|
259
|
+
subject.has_role @emp
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
should 'raise an exception if the role contains superclass methods' do
|
264
|
+
@emp = Property::Role.new('empi')
|
265
|
+
@emp.property.string 'method_in_parent'
|
266
|
+
assert_raise(Property::RedefinedMethodError) do
|
267
|
+
subject.has_role @emp
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
should 'inherit properties when asking to behave like a class' do
|
272
|
+
@class = Class.new(ActiveRecord::Base) do
|
273
|
+
include Property
|
274
|
+
property do |p|
|
275
|
+
p.string 'hop'
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
subject.has_role @class
|
280
|
+
assert_equal %w{language last_name hop age first_name}, subject.schema.column_names
|
281
|
+
assert subject.has_role?(@class)
|
223
282
|
end
|
224
283
|
end
|
225
284
|
|
@@ -57,12 +57,12 @@ class IndexForeignTest < ActiveSupport::TestCase
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
def index_reader
|
60
|
+
def index_reader(group_name)
|
61
61
|
{'version_id' => version.id}
|
62
62
|
end
|
63
63
|
|
64
64
|
# Foreign index: we store the 'employee_id' in the index to get back directly to non-versioned class Contact (through employee_id key).
|
65
|
-
def index_writer
|
65
|
+
def index_writer(group_name)
|
66
66
|
{'version_id' => version.id, 'employee_id' => self.id}
|
67
67
|
end
|
68
68
|
end
|
@@ -3,7 +3,7 @@ require 'fixtures'
|
|
3
3
|
|
4
4
|
class IndexSimpleTest < ActiveSupport::TestCase
|
5
5
|
class IndexedStringEmp < ActiveRecord::Base
|
6
|
-
set_table_name :
|
6
|
+
set_table_name :i_special_employees
|
7
7
|
end
|
8
8
|
|
9
9
|
class IndexedIntegerEmp < ActiveRecord::Base
|
@@ -25,7 +25,7 @@ class IndexSimpleTest < ActiveSupport::TestCase
|
|
25
25
|
alias_method_chain :save, :raise
|
26
26
|
|
27
27
|
property do |p|
|
28
|
-
p.string 'name', :index =>
|
28
|
+
p.string 'name', :index => :special
|
29
29
|
p.integer 'age', :indexed => true # synonym
|
30
30
|
end
|
31
31
|
end
|
@@ -40,7 +40,7 @@ class IndexSimpleTest < ActiveSupport::TestCase
|
|
40
40
|
end
|
41
41
|
|
42
42
|
should 'group indices by type' do
|
43
|
-
assert_equal %w{integer
|
43
|
+
assert_equal %w{integer special}, subject.index_groups.keys.map(&:to_s).sort
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -49,6 +49,14 @@ class IndexSimpleTest < ActiveSupport::TestCase
|
|
49
49
|
Dog
|
50
50
|
end
|
51
51
|
|
52
|
+
class Mongrel < Dog
|
53
|
+
attr_accessor :last_index_group_name
|
54
|
+
def create_indices(group_name, new_keys, cur_indices)
|
55
|
+
@last_index_group_name = group_name
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
52
60
|
context 'on record creation' do
|
53
61
|
should 'create index entries' do
|
54
62
|
assert_difference('IndexedStringEmp.count', 1) do
|
@@ -56,6 +64,11 @@ class IndexSimpleTest < ActiveSupport::TestCase
|
|
56
64
|
end
|
57
65
|
end
|
58
66
|
|
67
|
+
should 'call create_indices to create index entries' do
|
68
|
+
m = Mongrel.create('name' => 'Zed')
|
69
|
+
assert_equal 'special', m.last_index_group_name
|
70
|
+
end
|
71
|
+
|
59
72
|
should 'not create index entries for blank values' do
|
60
73
|
assert_difference('IndexedIntegerEmp.count', 0) do
|
61
74
|
Dog.create('name' => 'Pavlov')
|
@@ -91,7 +104,7 @@ class IndexSimpleTest < ActiveSupport::TestCase
|
|
91
104
|
@dog.update_attributes('name' => 'Médor')
|
92
105
|
end
|
93
106
|
end
|
94
|
-
|
107
|
+
|
95
108
|
should 'remove blank values' do
|
96
109
|
assert_difference('IndexedStringEmp.count', -1) do
|
97
110
|
@dog.update_attributes('name' => '')
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'fixtures'
|
3
|
+
|
4
|
+
class RoleTest < ActiveSupport::TestCase
|
5
|
+
should_store_property_definitions(Property::Role)
|
6
|
+
|
7
|
+
|
8
|
+
context 'A Poet role' do
|
9
|
+
setup do
|
10
|
+
@poet = Property::Role.new('Poet') do |p|
|
11
|
+
p.string 'poem', :default => :muse
|
12
|
+
p.integer 'year'
|
13
|
+
|
14
|
+
p.actions do
|
15
|
+
def muse
|
16
|
+
'I am your muse'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
should_insert_properties_on_has_role_poet
|
23
|
+
should_add_role_methods
|
24
|
+
should_take_part_in_used_list
|
25
|
+
should_not_maintain_indices # no indexed column defined
|
26
|
+
|
27
|
+
should 'return name on name' do
|
28
|
+
assert_equal 'Poet', @poet.name
|
29
|
+
end
|
30
|
+
end # A Poet role
|
31
|
+
|
32
|
+
context 'A Poet role with indices' do
|
33
|
+
setup do
|
34
|
+
@poet = Property::Role.new('Poet') do |p|
|
35
|
+
p.string 'poem', :index => :ml_string
|
36
|
+
p.integer 'year', :index => true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
should_maintain_indices
|
41
|
+
end # A Poet role with indices
|
42
|
+
|
43
|
+
|
44
|
+
context 'A class used as role' do
|
45
|
+
class Foo < ActiveRecord::Base
|
46
|
+
include Property
|
47
|
+
|
48
|
+
property do |p|
|
49
|
+
p.string 'poem', :default => :muse
|
50
|
+
p.integer 'year'
|
51
|
+
|
52
|
+
p.actions do
|
53
|
+
def muse
|
54
|
+
'I am your muse'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
setup do
|
61
|
+
@poet = Foo
|
62
|
+
end
|
63
|
+
|
64
|
+
should_insert_properties_on_has_role_poet
|
65
|
+
should_add_role_methods
|
66
|
+
should_take_part_in_used_list
|
67
|
+
|
68
|
+
context 'set on a sub-class instance' do
|
69
|
+
subject do
|
70
|
+
Employee.new
|
71
|
+
end
|
72
|
+
|
73
|
+
should 'not raise an exception' do
|
74
|
+
assert_nothing_raised { subject.has_role Developer }
|
75
|
+
end
|
76
|
+
end # set on a sub-class instance
|
77
|
+
end # A class used as role
|
78
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'fixtures'
|
3
|
+
|
4
|
+
class StoredRoleTest < ActiveSupport::TestCase
|
5
|
+
class Column < ActiveRecord::Base
|
6
|
+
include Property::StoredColumn
|
7
|
+
end
|
8
|
+
|
9
|
+
class Role < ActiveRecord::Base
|
10
|
+
include Property::StoredRole
|
11
|
+
stored_columns_class 'StoredRoleTest::Column'
|
12
|
+
end
|
13
|
+
|
14
|
+
should_store_property_definitions(Role)
|
15
|
+
|
16
|
+
context 'A stored Role' do
|
17
|
+
|
18
|
+
context 'with column definitions' do
|
19
|
+
setup do
|
20
|
+
role = Role.create(:name => 'Poet')
|
21
|
+
role.stored_columns << Column.new(:ptype => 'string', :name => 'poem')
|
22
|
+
role.stored_columns << Column.new(:ptype => 'integer', :name => 'year')
|
23
|
+
role.save!
|
24
|
+
@poet = Role.find(role.id)
|
25
|
+
end
|
26
|
+
|
27
|
+
should_insert_properties_on_has_role_poet
|
28
|
+
should_take_part_in_used_list(false)
|
29
|
+
should_not_maintain_indices # no indexed column defined
|
30
|
+
|
31
|
+
should 'create new role columns on save' do
|
32
|
+
@poet.property do |p|
|
33
|
+
p.string 'original_language'
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_difference('Role.count', 0) do
|
37
|
+
assert_difference('Column.count', 1) do
|
38
|
+
@poet.save
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'return name on name' do
|
44
|
+
assert_equal 'Poet', @poet.name
|
45
|
+
end
|
46
|
+
end # with column definitions
|
47
|
+
end # A stored Role
|
48
|
+
|
49
|
+
context 'A stored Role' do
|
50
|
+
|
51
|
+
context 'with indexed column definitions' do
|
52
|
+
setup do
|
53
|
+
role = Role.create(:name => 'Poet')
|
54
|
+
role.stored_columns << Column.new(:ptype => 'string', :name => 'poem', :index => :ml_string)
|
55
|
+
role.stored_columns << Column.new(:ptype => 'integer', :name => 'year', :index => true)
|
56
|
+
role.save!
|
57
|
+
@poet = Role.find(role.id)
|
58
|
+
end
|
59
|
+
|
60
|
+
should_maintain_indices
|
61
|
+
end # with column definitions
|
62
|
+
end # A stored Role
|
63
|
+
|
64
|
+
context 'A new Role' do
|
65
|
+
subject do
|
66
|
+
Role.new('Poet') do |p|
|
67
|
+
p.string 'name'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
should 'define properties with a block' do
|
72
|
+
assert_equal %w{name}, subject.column_names
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'create role columns on save' do
|
76
|
+
role = subject
|
77
|
+
assert_difference('Role.count', 1) do
|
78
|
+
assert_difference('Column.count', 1) do
|
79
|
+
assert role.save
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end # A new Role
|
84
|
+
end
|