property 1.2.0 → 2.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/History.txt +14 -0
- data/README.rdoc +6 -2
- data/lib/property.rb +2 -1
- data/lib/property/attribute.rb +3 -2
- data/lib/property/declaration.rb +65 -17
- data/lib/property/index.rb +21 -1
- data/lib/property/properties.rb +1 -1
- data/lib/property/role.rb +16 -10
- data/lib/property/role_module.rb +83 -196
- data/lib/property/schema.rb +68 -117
- data/lib/property/stored_role.rb +13 -6
- data/lib/property/version.rb +1 -1
- data/property.gemspec +6 -4
- data/test/database.rb +1 -0
- data/test/fixtures.rb +1 -0
- data/test/shoulda_macros/index.rb +6 -3
- data/test/shoulda_macros/role.rb +26 -50
- data/test/unit/property/declaration_test.rb +34 -40
- data/test/unit/property/index_field_test.rb +56 -0
- data/test/unit/property/role_test.rb +6 -16
- data/test/unit/property/stored_role_test.rb +86 -1
- metadata +15 -4
data/lib/property/schema.rb
CHANGED
@@ -1,100 +1,41 @@
|
|
1
|
-
|
2
1
|
module Property
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
include_role @role
|
20
|
-
@roles << @role
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# Return an identifier for the schema to help locate property redefinition errors.
|
25
|
-
def name
|
26
|
-
@role ? @role.name : @binding.to_s
|
27
|
-
end
|
28
|
-
|
29
|
-
# Return true if the current schema has all the roles of the given object, class or role.
|
30
|
-
def has_role?(thing)
|
31
|
-
roles = self.roles.flatten
|
32
|
-
test_roles = thing.class < RoleModule ? [thing] : thing.schema.roles.flatten
|
33
|
-
test_roles.each do |role|
|
34
|
-
return false unless roles.include?(role)
|
35
|
-
end
|
36
|
-
true
|
37
|
-
end
|
38
|
-
|
39
|
-
# If the parameter is a class, the schema will inherit the property definitions
|
40
|
-
# from the class. If the parameter is a Role, the properties from that
|
41
|
-
# role will be included. Any new columns added to a role or any new
|
42
|
-
# roles included in a class will be dynamically added to the sub-classes (just like
|
43
|
-
# Ruby class inheritance, module inclusion works).
|
44
|
-
# If you ...
|
45
|
-
def has_role(thing)
|
46
|
-
if thing.kind_of?(Class)
|
47
|
-
if thing.respond_to?(:schema) && thing.schema.kind_of?(Schema)
|
48
|
-
schema_class = thing.schema.binding
|
49
|
-
if @binding.ancestors.include?(schema_class)
|
50
|
-
check_super_methods = false
|
51
|
-
else
|
52
|
-
check_super_methods = true
|
53
|
-
end
|
54
|
-
thing.schema.roles.flatten.each do |role|
|
55
|
-
include_role role, check_super_methods
|
56
|
-
end
|
57
|
-
self.roles << thing.schema.roles
|
58
|
-
else
|
59
|
-
raise TypeError.new("expected Role or class with schema, found #{thing}")
|
2
|
+
# A schema contains all the property definitions for a given class. If Role is a module,
|
3
|
+
# then schema is a Class.
|
4
|
+
class Schema < Role
|
5
|
+
attr_accessor :roles, :klass
|
6
|
+
|
7
|
+
# Initialize a new schema with a name and the klass linked to the schema.
|
8
|
+
def initialize(name, opts = {})
|
9
|
+
super
|
10
|
+
@klass = opts[:class]
|
11
|
+
|
12
|
+
@roles = [self]
|
13
|
+
|
14
|
+
# Schema inheritance
|
15
|
+
unless superschema = opts[:superschema]
|
16
|
+
if @klass && @klass.superclass.respond_to?(:schema)
|
17
|
+
superschema = @klass.superclass.schema
|
60
18
|
end
|
61
|
-
elsif thing.kind_of?(RoleModule)
|
62
|
-
include_role thing
|
63
|
-
self.roles << thing
|
64
|
-
else
|
65
|
-
raise TypeError.new("expected Role or class with schema, found #{thing.class}")
|
66
19
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Return the list of active roles. The active roles are all the Roles included
|
70
|
-
# in the current object for which properties have been defined (not blank).
|
71
|
-
def used_roles_in(object)
|
72
|
-
roles.flatten.uniq.reject do |role|
|
73
|
-
!role.used_in(object)
|
74
|
-
end
|
75
|
-
end
|
76
20
|
|
77
|
-
|
78
|
-
|
79
|
-
columns.keys
|
80
|
-
end
|
81
|
-
|
82
|
-
# Return true if the schema has a property with the given name.
|
83
|
-
def has_column?(name)
|
84
|
-
name = name.to_s
|
85
|
-
[@roles].flatten.each do |role|
|
86
|
-
return true if role.has_column?(name)
|
21
|
+
if superschema
|
22
|
+
include_role superschema
|
87
23
|
end
|
88
|
-
false
|
89
24
|
end
|
90
25
|
|
91
|
-
#
|
92
|
-
def
|
93
|
-
columns =
|
94
|
-
|
95
|
-
|
26
|
+
# Add a set of property definitions to the schema.
|
27
|
+
def include_role(role)
|
28
|
+
@columns = nil # clear cache
|
29
|
+
if role.kind_of?(Schema)
|
30
|
+
# Superclass inheritance
|
31
|
+
@roles << role.roles
|
32
|
+
elsif role.kind_of?(RoleModule)
|
33
|
+
@roles << role
|
34
|
+
elsif role.respond_to?(:schema) && role.schema.kind_of?(Role)
|
35
|
+
@roles << role.schema.roles
|
36
|
+
else
|
37
|
+
raise TypeError.new("Cannot include role #{role} (invalid type).")
|
96
38
|
end
|
97
|
-
columns
|
98
39
|
end
|
99
40
|
|
100
41
|
# Return a hash with indexed types as keys and index definitions as values.
|
@@ -108,38 +49,48 @@ module Property
|
|
108
49
|
index_groups
|
109
50
|
end
|
110
51
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@binding.send(:include, role.accessor_module)
|
122
|
-
end
|
123
|
-
|
124
|
-
def check_duplicate_property_definitions(role, keys)
|
125
|
-
common_keys = keys & self.columns.keys
|
126
|
-
if !common_keys.empty?
|
127
|
-
raise RedefinedPropertyError.new("Cannot include role '#{role.name}' in '#{name}'. Duplicate definitions: #{common_keys.join(', ')}")
|
52
|
+
# Return a hash with the column definitions defined in the schema and in the included
|
53
|
+
# roles.
|
54
|
+
def columns
|
55
|
+
# FIXME: can we memoize this list on the first call ? Do we need to update properties after such a call ?
|
56
|
+
# @columns ||=
|
57
|
+
begin
|
58
|
+
res = {}
|
59
|
+
@roles.flatten.uniq.each do |role|
|
60
|
+
# TODO: we could check for property redefinitions.
|
61
|
+
res.merge!(role.defined_columns)
|
128
62
|
end
|
63
|
+
res
|
129
64
|
end
|
65
|
+
end
|
130
66
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
67
|
+
# Return the list of active roles. The active roles are all the Roles included
|
68
|
+
# in the current object for which properties have been defined (not blank).
|
69
|
+
def used_roles_in(object)
|
70
|
+
roles.flatten.uniq.select do |role|
|
71
|
+
role.used_in(object)
|
72
|
+
end
|
73
|
+
end
|
138
74
|
|
139
|
-
|
140
|
-
|
141
|
-
|
75
|
+
# Return true if the role has been included or is included in any superclass.
|
76
|
+
def has_role?(role)
|
77
|
+
if role.kind_of?(Schema)
|
78
|
+
role.roles.flatten - @roles.flatten == []
|
79
|
+
elsif role.kind_of?(RoleModule)
|
80
|
+
@roles.flatten.include?(role)
|
81
|
+
elsif role.respond_to?(:schema) && role.schema.kind_of?(Role)
|
82
|
+
has_role?(role.schema)
|
83
|
+
else
|
84
|
+
false
|
142
85
|
end
|
86
|
+
end
|
143
87
|
|
88
|
+
# When a column is added in a Schema: define accessors in related class
|
89
|
+
def add_column(column)
|
90
|
+
super
|
91
|
+
if @klass
|
92
|
+
@klass.define_property_methods(column) if column.should_create_accessors?
|
93
|
+
end
|
94
|
+
end
|
144
95
|
end
|
145
|
-
end
|
96
|
+
end # Property
|
data/lib/property/stored_role.rb
CHANGED
@@ -8,7 +8,7 @@ module Property
|
|
8
8
|
# Once this module is included, you need to set the has_many association to the class that
|
9
9
|
# contains the columns definitions with something like:
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# stored_columns_class NameOfColumnsClass
|
12
12
|
module StoredRole
|
13
13
|
include RoleModule
|
14
14
|
|
@@ -50,16 +50,20 @@ module Property
|
|
50
50
|
end
|
51
51
|
end # included
|
52
52
|
|
53
|
-
#
|
54
|
-
def
|
53
|
+
# Get all property definitions defined for this role
|
54
|
+
def defined_columns
|
55
55
|
load_columns_from_db unless @columns_from_db_loaded
|
56
56
|
super
|
57
57
|
end
|
58
58
|
|
59
59
|
def property
|
60
|
-
initialize_role_module unless @accessor_module
|
61
60
|
super
|
62
61
|
end
|
62
|
+
|
63
|
+
# Overwrite name reader in RoleModule
|
64
|
+
def name
|
65
|
+
self[:name]
|
66
|
+
end
|
63
67
|
|
64
68
|
|
65
69
|
private
|
@@ -74,9 +78,12 @@ module Property
|
|
74
78
|
end
|
75
79
|
|
76
80
|
def update_columns
|
77
|
-
@
|
81
|
+
return unless @defined_columns # no change
|
82
|
+
unless @original_columns
|
83
|
+
load_columns_from_db
|
84
|
+
end
|
78
85
|
stored_column_names = @original_columns.keys
|
79
|
-
defined_column_names =
|
86
|
+
defined_column_names = column_names
|
80
87
|
|
81
88
|
new_columns = defined_column_names - stored_column_names
|
82
89
|
updated_columns = defined_column_names & stored_column_names
|
data/lib/property/version.rb
CHANGED
data/property.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{property}
|
8
|
-
s.version = "
|
8
|
+
s.version = "2.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Renaud Kern", "Gaspard Bucher"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-11-10}
|
13
13
|
s.description = %q{Wrap model properties into a single database column and declare properties from within the model.}
|
14
14
|
s.email = %q{gaspard@teti.ch}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -58,6 +58,7 @@ Gem::Specification.new do |s|
|
|
58
58
|
"test/unit/property/dirty_test.rb",
|
59
59
|
"test/unit/property/index_complex_test.rb",
|
60
60
|
"test/unit/property/index_custom_test.rb",
|
61
|
+
"test/unit/property/index_field_test.rb",
|
61
62
|
"test/unit/property/index_foreign_test.rb",
|
62
63
|
"test/unit/property/index_simple_test.rb",
|
63
64
|
"test/unit/property/role_test.rb",
|
@@ -71,7 +72,7 @@ Gem::Specification.new do |s|
|
|
71
72
|
s.rdoc_options = ["--charset=UTF-8"]
|
72
73
|
s.require_paths = ["lib"]
|
73
74
|
s.rubyforge_project = %q{property}
|
74
|
-
s.rubygems_version = %q{1.3.
|
75
|
+
s.rubygems_version = %q{1.3.7}
|
75
76
|
s.summary = %q{model properties wrap into a single database column}
|
76
77
|
s.test_files = [
|
77
78
|
"test/database.rb",
|
@@ -87,6 +88,7 @@ Gem::Specification.new do |s|
|
|
87
88
|
"test/unit/property/dirty_test.rb",
|
88
89
|
"test/unit/property/index_complex_test.rb",
|
89
90
|
"test/unit/property/index_custom_test.rb",
|
91
|
+
"test/unit/property/index_field_test.rb",
|
90
92
|
"test/unit/property/index_foreign_test.rb",
|
91
93
|
"test/unit/property/index_simple_test.rb",
|
92
94
|
"test/unit/property/role_test.rb",
|
@@ -101,7 +103,7 @@ Gem::Specification.new do |s|
|
|
101
103
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
102
104
|
s.specification_version = 3
|
103
105
|
|
104
|
-
if Gem::Version.new(Gem::
|
106
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
105
107
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
106
108
|
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
|
107
109
|
else
|
data/test/database.rb
CHANGED
data/test/fixtures.rb
CHANGED
@@ -3,7 +3,10 @@ module IndexMacros
|
|
3
3
|
class Client < ActiveRecord::Base
|
4
4
|
set_table_name :employees
|
5
5
|
include Property
|
6
|
-
|
6
|
+
|
7
|
+
def muse
|
8
|
+
'I am your muse'
|
9
|
+
end
|
7
10
|
def index_reader(group_name)
|
8
11
|
if group_name.to_s == 'ml_string'
|
9
12
|
super.merge(:with => {'lang' => ['en', 'fr'], 'site_id' => '123'})
|
@@ -21,7 +24,7 @@ class Test::Unit::TestCase
|
|
21
24
|
context "assigned to an instance of Dummy" do
|
22
25
|
subject do
|
23
26
|
dummy = IndexMacros::Client.new
|
24
|
-
dummy.
|
27
|
+
dummy.include_role @poet
|
25
28
|
dummy
|
26
29
|
end
|
27
30
|
|
@@ -58,7 +61,7 @@ class Test::Unit::TestCase
|
|
58
61
|
context "assigned to an instance of Dummy" do
|
59
62
|
subject do
|
60
63
|
dummy = IndexMacros::Client.new
|
61
|
-
dummy.
|
64
|
+
dummy.include_role @poet
|
62
65
|
dummy
|
63
66
|
end
|
64
67
|
|
data/test/shoulda_macros/role.rb
CHANGED
@@ -77,7 +77,7 @@ class Test::Unit::TestCase
|
|
77
77
|
end
|
78
78
|
end # should_store_property_definitions
|
79
79
|
|
80
|
-
def self.
|
80
|
+
def self.should_insert_properties_on_include_role_poet
|
81
81
|
context 'added' do
|
82
82
|
|
83
83
|
context 'to a parent class' do
|
@@ -86,35 +86,34 @@ class Test::Unit::TestCase
|
|
86
86
|
set_table_name :dummies
|
87
87
|
include Property
|
88
88
|
property.string 'name'
|
89
|
+
|
90
|
+
def muse
|
91
|
+
'I am your muse'
|
92
|
+
end
|
89
93
|
end
|
90
94
|
|
91
95
|
@klass = Class.new(@parent)
|
92
96
|
end
|
93
97
|
|
94
98
|
should 'propagate definitions to child' do
|
95
|
-
@parent.
|
99
|
+
@parent.include_role @poet
|
96
100
|
assert_equal %w{name poem year}, @klass.schema.column_names.sort
|
97
101
|
end
|
98
102
|
|
99
103
|
should 'return true on has_role?' do
|
100
|
-
@parent.
|
104
|
+
@parent.include_role @poet
|
101
105
|
assert @klass.has_role?(@poet)
|
102
106
|
end
|
103
107
|
|
104
|
-
should 'raise an exception if class contains same definitions' do
|
105
|
-
@parent.property.string 'poem'
|
106
|
-
assert_raise(Property::RedefinedPropertyError) { @parent.has_role @poet }
|
107
|
-
end
|
108
|
-
|
109
108
|
should 'not raise an exception on double inclusion' do
|
110
|
-
@parent.
|
111
|
-
assert_nothing_raised { @parent.
|
109
|
+
@parent.include_role @poet
|
110
|
+
assert_nothing_raised { @parent.include_role @poet }
|
112
111
|
end
|
113
112
|
|
114
113
|
should 'add accessor methods to child' do
|
115
114
|
subject = @klass.new
|
116
115
|
assert_raises(NoMethodError) { subject.poem = 'Poe'}
|
117
|
-
@parent.
|
116
|
+
@parent.include_role @poet
|
118
117
|
|
119
118
|
assert_nothing_raised { subject.poem = 'Poe'}
|
120
119
|
end
|
@@ -126,22 +125,26 @@ class Test::Unit::TestCase
|
|
126
125
|
set_table_name :dummies
|
127
126
|
include Property
|
128
127
|
property.string 'name'
|
128
|
+
|
129
|
+
def muse
|
130
|
+
'I am your muse'
|
131
|
+
end
|
129
132
|
end
|
130
133
|
end
|
131
134
|
|
132
135
|
should 'insert definitions' do
|
133
|
-
@klass.
|
136
|
+
@klass.include_role @poet
|
134
137
|
assert_equal %w{name poem year}, @klass.schema.column_names.sort
|
135
138
|
end
|
136
139
|
|
137
140
|
should 'return true on class has_role?' do
|
138
|
-
@klass.
|
141
|
+
@klass.include_role @poet
|
139
142
|
assert @klass.has_role?(@poet)
|
140
143
|
end
|
141
144
|
|
142
145
|
should 'return role from column' do
|
143
|
-
@klass.
|
144
|
-
assert_equal (@poet.kind_of?(Class) ? @poet.schema
|
146
|
+
@klass.include_role @poet
|
147
|
+
assert_equal (@poet.kind_of?(Class) ? @poet.schema : @poet), @klass.schema.columns['poem'].role
|
145
148
|
end
|
146
149
|
end
|
147
150
|
|
@@ -149,7 +152,7 @@ class Test::Unit::TestCase
|
|
149
152
|
subject { Developer.new }
|
150
153
|
|
151
154
|
setup do
|
152
|
-
subject.
|
155
|
+
subject.include_role @poet
|
153
156
|
end
|
154
157
|
|
155
158
|
should 'merge property definitions' do
|
@@ -157,38 +160,7 @@ class Test::Unit::TestCase
|
|
157
160
|
end
|
158
161
|
end
|
159
162
|
end
|
160
|
-
end #
|
161
|
-
|
162
|
-
def self.should_add_role_methods
|
163
|
-
context 'added' do
|
164
|
-
context 'to a parent class' do
|
165
|
-
setup do
|
166
|
-
@parent = Class.new(ActiveRecord::Base) do
|
167
|
-
set_table_name :dummies
|
168
|
-
include Property
|
169
|
-
property.string 'name'
|
170
|
-
end
|
171
|
-
|
172
|
-
@klass = Class.new(@parent)
|
173
|
-
end
|
174
|
-
|
175
|
-
should 'add role methods to child' do
|
176
|
-
subject = @klass.new
|
177
|
-
assert_raises(NoMethodError) { subject.muse }
|
178
|
-
@parent.has_role @poet
|
179
|
-
|
180
|
-
assert_nothing_raised { subject.muse }
|
181
|
-
end
|
182
|
-
|
183
|
-
should 'use role methods for defaults' do
|
184
|
-
subject = @klass.new
|
185
|
-
@parent.has_role @poet
|
186
|
-
assert subject.save
|
187
|
-
assert_equal 'I am your muse', subject.poem
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end # should_add_role_methods
|
163
|
+
end # should_insert_properties_on_include_role_poet
|
192
164
|
|
193
165
|
def self.should_take_part_in_used_list(has_defaults = true)
|
194
166
|
|
@@ -199,9 +171,13 @@ class Test::Unit::TestCase
|
|
199
171
|
set_table_name :dummies
|
200
172
|
include Property
|
201
173
|
property.string 'name'
|
174
|
+
|
175
|
+
def muse
|
176
|
+
'I am your muse'
|
177
|
+
end
|
202
178
|
end
|
203
179
|
|
204
|
-
@klass.
|
180
|
+
@klass.include_role @poet
|
205
181
|
end
|
206
182
|
|
207
183
|
subject do
|
@@ -214,7 +190,7 @@ class Test::Unit::TestCase
|
|
214
190
|
|
215
191
|
should 'not return role without corresponding attributes' do
|
216
192
|
subject.attributes = {'name' => 'hello'}
|
217
|
-
assert_equal [@klass.schema
|
193
|
+
assert_equal [@klass.schema], subject.used_roles
|
218
194
|
end
|
219
195
|
|
220
196
|
should 'return role with corresponding attributes' do
|