property 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|