property 0.5.0 → 0.6.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 +8 -3
- data/Rakefile +1 -1
- data/lib/property.rb +1 -1
- data/lib/property/attribute.rb +2 -3
- data/lib/property/column.rb +6 -8
- data/lib/property/declaration.rb +84 -1
- data/lib/property/dirty.rb +0 -6
- data/lib/property/properties.rb +8 -17
- data/property.gemspec +81 -0
- data/test/unit/property/attribute_test.rb +24 -3
- data/test/unit/property/declaration_test.rb +25 -0
- data/test/unit/property/dirty_test.rb +8 -3
- data/test/unit/property/validation_test.rb +7 -7
- metadata +3 -2
data/.gitignore
CHANGED
data/History.txt
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
-
== 0.
|
1
|
+
== 0.6.0 2010-02-11
|
2
2
|
|
3
3
|
* 1 major enhancement
|
4
|
-
*
|
4
|
+
* enabled ruby accessors in model
|
5
5
|
|
6
6
|
== 0.5.0 2010-02-11
|
7
7
|
|
8
8
|
* 2 major enhancement
|
9
9
|
* changed plugin into gem
|
10
|
-
* using Rails columns to handle defaults and type casting
|
10
|
+
* using Rails columns to handle defaults and type casting
|
11
|
+
|
12
|
+
== 0.4.0 2010-02-02
|
13
|
+
|
14
|
+
* 1 major enhancement
|
15
|
+
* initial plugin code
|
data/Rakefile
CHANGED
data/lib/property.rb
CHANGED
data/lib/property/attribute.rb
CHANGED
@@ -50,11 +50,11 @@ module Property
|
|
50
50
|
|
51
51
|
private
|
52
52
|
def attributes_with_properties=(attributes, guard_protected_attributes = true)
|
53
|
-
|
53
|
+
property_columns = self.class.property_columns
|
54
54
|
properties = {}
|
55
55
|
|
56
56
|
attributes.keys.each do |k|
|
57
|
-
if
|
57
|
+
if property_columns.include?(k)
|
58
58
|
properties[k] = attributes.delete(k)
|
59
59
|
end
|
60
60
|
end
|
@@ -74,7 +74,6 @@ module Property
|
|
74
74
|
|
75
75
|
def dump_properties
|
76
76
|
if @properties
|
77
|
-
@properties.compact!
|
78
77
|
if !@properties.empty?
|
79
78
|
write_attribute('properties', encode_properties(@properties))
|
80
79
|
else
|
data/lib/property/column.rb
CHANGED
@@ -6,6 +6,7 @@ module Property
|
|
6
6
|
# such as name, type and options. It is also used to typecast from strings to
|
7
7
|
# the proper type (date, integer, float, etc).
|
8
8
|
class Column < ::ActiveRecord::ConnectionAdapters::Column
|
9
|
+
SAFE_NAMES_REGEXP = %r{\A[a-zA-Z_]+\Z}
|
9
10
|
|
10
11
|
def initialize(name, default, type, options={})
|
11
12
|
name = name.to_s
|
@@ -14,14 +15,11 @@ module Property
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def validate(value, errors)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
nil
|
23
|
-
end
|
24
|
-
end
|
18
|
+
# Do nothing for the moment
|
19
|
+
end
|
20
|
+
|
21
|
+
def should_create_accessors?
|
22
|
+
name =~ SAFE_NAMES_REGEXP
|
25
23
|
end
|
26
24
|
|
27
25
|
def indexed?
|
data/lib/property/declaration.rb
CHANGED
@@ -28,7 +28,9 @@ module Property
|
|
28
28
|
if columns[name.to_s]
|
29
29
|
raise TypeError.new("Property '#{name}' is already defined.")
|
30
30
|
else
|
31
|
-
|
31
|
+
new_column = Property::Column.new(name, default, type, options)
|
32
|
+
own_columns[new_column.name] = new_column
|
33
|
+
@klass.define_property_methods(new_column) if new_column.should_create_accessors?
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
@@ -107,6 +109,87 @@ module Property
|
|
107
109
|
{}
|
108
110
|
end
|
109
111
|
end
|
112
|
+
|
113
|
+
def define_property_methods(column)
|
114
|
+
name = column.name
|
115
|
+
unless instance_method_already_implemented?(name)
|
116
|
+
if create_time_zone_conversion_attribute?(name, column)
|
117
|
+
define_read_property_method_for_time_zone_conversion(name)
|
118
|
+
else
|
119
|
+
define_read_property_method(name.to_sym, name, column)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
unless instance_method_already_implemented?("#{name}=")
|
124
|
+
if create_time_zone_conversion_attribute?(name, column)
|
125
|
+
define_write_property_method_for_time_zone_conversion(name)
|
126
|
+
else
|
127
|
+
define_write_property_method(name.to_sym)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
unless instance_method_already_implemented?("#{name}?")
|
132
|
+
define_question_property_method(name)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
# Define a property reader method. Cope with nil column.
|
138
|
+
def define_read_property_method(symbol, attr_name, column)
|
139
|
+
# Unlike rails, we do not cast on read
|
140
|
+
evaluate_attribute_property_method attr_name, "def #{symbol}; prop['#{attr_name}']; end"
|
141
|
+
end
|
142
|
+
|
143
|
+
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
144
|
+
# This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
|
145
|
+
def define_read_property_method_for_time_zone_conversion(attr_name)
|
146
|
+
method_body = <<-EOV
|
147
|
+
def #{attr_name}(reload = false)
|
148
|
+
cached = @attributes_cache['#{attr_name}']
|
149
|
+
return cached if cached && !reload
|
150
|
+
time = properties['#{attr_name}']
|
151
|
+
@attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
|
152
|
+
end
|
153
|
+
EOV
|
154
|
+
evaluate_attribute_property_method attr_name, method_body
|
155
|
+
end
|
156
|
+
|
157
|
+
# Defines a predicate method <tt>attr_name?</tt>.
|
158
|
+
def define_question_property_method(attr_name)
|
159
|
+
evaluate_attribute_property_method attr_name, "def #{attr_name}?; prop['#{attr_name}']; end", "#{attr_name}?"
|
160
|
+
end
|
161
|
+
|
162
|
+
def define_write_property_method(attr_name)
|
163
|
+
evaluate_attribute_property_method attr_name, "def #{attr_name}=(new_value);prop['#{attr_name}'] = new_value; end", "#{attr_name}="
|
164
|
+
end
|
165
|
+
|
166
|
+
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
167
|
+
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
|
168
|
+
def define_write_property_method_for_time_zone_conversion(attr_name)
|
169
|
+
method_body = <<-EOV
|
170
|
+
def #{attr_name}=(time)
|
171
|
+
unless time.acts_like?(:time)
|
172
|
+
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
|
173
|
+
end
|
174
|
+
time = time.in_time_zone rescue nil if time
|
175
|
+
prop['#{attr_name}'] = time
|
176
|
+
end
|
177
|
+
EOV
|
178
|
+
evaluate_attribute_property_method attr_name, method_body, "#{attr_name}="
|
179
|
+
end
|
180
|
+
|
181
|
+
# Evaluate the definition for an attribute related method
|
182
|
+
def evaluate_attribute_property_method(attr_name, method_definition, method_name=attr_name)
|
183
|
+
begin
|
184
|
+
class_eval(method_definition, __FILE__, __LINE__)
|
185
|
+
rescue SyntaxError => err
|
186
|
+
if logger
|
187
|
+
logger.warn "Exception occurred during method compilation."
|
188
|
+
logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
|
189
|
+
logger.warn err.message
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
110
193
|
end # ClassMethods
|
111
194
|
|
112
195
|
module InstanceMethods
|
data/lib/property/dirty.rb
CHANGED
@@ -47,11 +47,6 @@ module Property
|
|
47
47
|
super
|
48
48
|
end
|
49
49
|
|
50
|
-
def merge!(other_hash)
|
51
|
-
@original_hash ||= self.dup
|
52
|
-
super
|
53
|
-
end
|
54
|
-
|
55
50
|
def changed?
|
56
51
|
!changes.empty?
|
57
52
|
end
|
@@ -62,7 +57,6 @@ module Property
|
|
62
57
|
|
63
58
|
def changes
|
64
59
|
return {} unless @original_hash
|
65
|
-
compact!
|
66
60
|
changes = {}
|
67
61
|
|
68
62
|
# look for updated value
|
data/lib/property/properties.rb
CHANGED
@@ -8,10 +8,7 @@ module Property
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def to_json(*args)
|
11
|
-
{
|
12
|
-
'json_class' => self.class.name,
|
13
|
-
'data' => Hash[self]
|
14
|
-
}.to_json(*args)
|
11
|
+
{ 'json_class' => self.class.name, 'data' => Hash[self] }.to_json(*args)
|
15
12
|
end
|
16
13
|
|
17
14
|
def []=(key, value)
|
@@ -44,8 +41,9 @@ module Property
|
|
44
41
|
errors = @owner.errors
|
45
42
|
no_errors = true
|
46
43
|
|
47
|
-
bad_keys
|
48
|
-
missing_keys
|
44
|
+
bad_keys = keys - column_names
|
45
|
+
missing_keys = column_names - keys
|
46
|
+
keys_to_validate = keys - bad_keys
|
49
47
|
|
50
48
|
bad_keys.each do |key|
|
51
49
|
errors.add("#{key}", 'property is not declared')
|
@@ -58,21 +56,14 @@ module Property
|
|
58
56
|
end
|
59
57
|
end
|
60
58
|
|
61
|
-
|
62
|
-
|
59
|
+
keys_to_validate.each do |key|
|
60
|
+
columns[key].validate(self[key], errors)
|
61
|
+
end
|
63
62
|
|
64
|
-
|
65
|
-
#keys.each do |key|
|
66
|
-
# if self[key].nil?
|
67
|
-
# delete(key)
|
68
|
-
# end
|
69
|
-
#end
|
63
|
+
bad_keys.empty?
|
70
64
|
end
|
71
65
|
|
72
66
|
private
|
73
|
-
def write_attribute(key, value)
|
74
|
-
end
|
75
|
-
|
76
67
|
def columns
|
77
68
|
@columns ||= @owner.class.property_columns
|
78
69
|
end
|
data/property.gemspec
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{property}
|
8
|
+
s.version = "0.6.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Renaud Kern", "Gaspard Bucher"]
|
12
|
+
s.date = %q{2010-02-11}
|
13
|
+
s.description = %q{Wrap model properties into a single database column and declare properties from within the model.}
|
14
|
+
s.email = %q{gaspard@teti.ch}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"History.txt",
|
21
|
+
"MIT-LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"generators/property/property_generator.rb",
|
25
|
+
"lib/property.rb",
|
26
|
+
"lib/property/attribute.rb",
|
27
|
+
"lib/property/column.rb",
|
28
|
+
"lib/property/declaration.rb",
|
29
|
+
"lib/property/dirty.rb",
|
30
|
+
"lib/property/properties.rb",
|
31
|
+
"lib/property/serialization/json.rb",
|
32
|
+
"lib/property/serialization/marshal.rb",
|
33
|
+
"lib/property/serialization/yaml.rb",
|
34
|
+
"property.gemspec",
|
35
|
+
"test/fixtures.rb",
|
36
|
+
"test/shoulda_macros/serialization.rb",
|
37
|
+
"test/test_helper.rb",
|
38
|
+
"test/unit/property/attribute_test.rb",
|
39
|
+
"test/unit/property/declaration_test.rb",
|
40
|
+
"test/unit/property/dirty_test.rb",
|
41
|
+
"test/unit/property/validation_test.rb",
|
42
|
+
"test/unit/serialization/json_test.rb",
|
43
|
+
"test/unit/serialization/marshal_test.rb",
|
44
|
+
"test/unit/serialization/yaml_test.rb"
|
45
|
+
]
|
46
|
+
s.homepage = %q{http://zenadmin.org/635}
|
47
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
48
|
+
s.require_paths = ["lib"]
|
49
|
+
s.rubyforge_project = %q{property}
|
50
|
+
s.rubygems_version = %q{1.3.5}
|
51
|
+
s.summary = %q{model properties wrap into a single database column}
|
52
|
+
s.test_files = [
|
53
|
+
"test/fixtures.rb",
|
54
|
+
"test/shoulda_macros/serialization.rb",
|
55
|
+
"test/test_helper.rb",
|
56
|
+
"test/unit/property/attribute_test.rb",
|
57
|
+
"test/unit/property/declaration_test.rb",
|
58
|
+
"test/unit/property/dirty_test.rb",
|
59
|
+
"test/unit/property/validation_test.rb",
|
60
|
+
"test/unit/serialization/json_test.rb",
|
61
|
+
"test/unit/serialization/marshal_test.rb",
|
62
|
+
"test/unit/serialization/yaml_test.rb"
|
63
|
+
]
|
64
|
+
|
65
|
+
if s.respond_to? :specification_version then
|
66
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
67
|
+
s.specification_version = 3
|
68
|
+
|
69
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
70
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
71
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
|
72
|
+
else
|
73
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
74
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
75
|
+
end
|
76
|
+
else
|
77
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
78
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -59,9 +59,18 @@ class AttributeTest < Test::Unit::TestCase
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
context 'with merge!' do
|
63
|
+
should 'merge new attributes' do
|
64
|
+
subject.properties.merge!({'b'=>'bravo', 'c'=>'charlie'})
|
65
|
+
assert_equal Hash['foo' => 'bar', 'b' => 'bravo', 'c' => 'charlie'], subject.properties
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with native ruby methods' do
|
70
|
+
should 'update current value' do
|
71
|
+
subject.foo = 'floppy disk'
|
72
|
+
assert_equal Hash['foo' => 'floppy disk'], subject.properties
|
73
|
+
end
|
65
74
|
end
|
66
75
|
end
|
67
76
|
|
@@ -95,6 +104,18 @@ class AttributeTest < Test::Unit::TestCase
|
|
95
104
|
assert_equal Hash['foo'=>'bar', :tic=>:tac], subject.properties
|
96
105
|
end
|
97
106
|
|
107
|
+
should 'be accessible with native methods' do
|
108
|
+
assert_equal 'bar', subject.foo
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'respond true on predicate method' do
|
112
|
+
assert subject.foo?
|
113
|
+
end
|
114
|
+
|
115
|
+
should 'not respond true on predicate method if empty' do
|
116
|
+
assert !subject.comment?
|
117
|
+
end
|
118
|
+
|
98
119
|
should 'be a kind of Hash' do
|
99
120
|
assert_kind_of Hash, subject.properties
|
100
121
|
end
|
@@ -64,6 +64,23 @@ class DeclarationTest < Test::Unit::TestCase
|
|
64
64
|
assert_kind_of Property::Column, subject.property_columns['weapon']
|
65
65
|
end
|
66
66
|
|
67
|
+
should 'create ruby accessors' do
|
68
|
+
subject.property.string('weapon')
|
69
|
+
assert subject.instance_methods.include?('weapon')
|
70
|
+
assert subject.instance_methods.include?('weapon=')
|
71
|
+
assert subject.instance_methods.include?('weapon?')
|
72
|
+
end
|
73
|
+
|
74
|
+
should 'not create accessors for illegal ruby names' do
|
75
|
+
bad_names = ['some.thing', 'puts("yo")', '/var/', 'hello darness']
|
76
|
+
assert_nothing_raised { subject.property.string bad_names }
|
77
|
+
bad_names.each do |bad_name|
|
78
|
+
assert !subject.instance_methods.include?(bad_name)
|
79
|
+
assert !subject.instance_methods.include?("#{bad_name}=")
|
80
|
+
assert !subject.instance_methods.include?("#{bad_name}?")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
67
84
|
should 'allow string columns' do
|
68
85
|
subject.property.string('weapon')
|
69
86
|
column = subject.property_columns['weapon']
|
@@ -72,6 +89,14 @@ class DeclarationTest < Test::Unit::TestCase
|
|
72
89
|
assert_equal :string, column.type
|
73
90
|
end
|
74
91
|
|
92
|
+
should 'treat symbol keys as strings' do
|
93
|
+
subject.property.string(:weapon)
|
94
|
+
column = subject.property_columns['weapon']
|
95
|
+
assert_equal 'weapon', column.name
|
96
|
+
assert_equal String, column.klass
|
97
|
+
assert_equal :string, column.type
|
98
|
+
end
|
99
|
+
|
75
100
|
should 'allow integer columns' do
|
76
101
|
subject.property.integer('indestructible')
|
77
102
|
column = subject.property_columns['indestructible']
|
@@ -36,7 +36,8 @@ class DirtyTest < Test::Unit::TestCase
|
|
36
36
|
|
37
37
|
context 'with changed properties' do
|
38
38
|
setup do
|
39
|
-
subject.properties = {'foo'=>'barre'
|
39
|
+
subject.properties = {'foo'=>'barre'}
|
40
|
+
subject.properties.delete('tic')
|
40
41
|
end
|
41
42
|
|
42
43
|
should_behave_nice_after_save('barre')
|
@@ -58,11 +59,11 @@ class DirtyTest < Test::Unit::TestCase
|
|
58
59
|
end
|
59
60
|
|
60
61
|
should 'return property changes with changes' do
|
61
|
-
assert_equal Hash['tic'=>['tac',
|
62
|
+
assert_equal Hash['tic'=>['tac', nil], 'foo'=>['bar', 'barre']], subject.changes
|
62
63
|
end
|
63
64
|
|
64
65
|
should 'return property changes with properties.changes' do
|
65
|
-
assert_equal Hash['tic'=>['tac',
|
66
|
+
assert_equal Hash['tic'=>['tac', nil], 'foo'=>['bar', 'barre']], subject.properties.changes
|
66
67
|
end
|
67
68
|
|
68
69
|
should 'return true on properties.key_changed?' do
|
@@ -152,6 +153,10 @@ class DirtyTest < Test::Unit::TestCase
|
|
152
153
|
should 'return previous value on properties.key_was' do
|
153
154
|
assert_equal 'bar', subject.properties.foo_was
|
154
155
|
end
|
156
|
+
|
157
|
+
should 'raise NoMethodError on other missing methods' do
|
158
|
+
assert_raise(NoMethodError) { subject.properties.sleep }
|
159
|
+
end
|
155
160
|
end
|
156
161
|
end
|
157
162
|
end
|
@@ -14,15 +14,15 @@ class ValidationTest < Test::Unit::TestCase
|
|
14
14
|
subject { Pirate.create }
|
15
15
|
|
16
16
|
context 'without a property column' do
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
should 'render object invalid' do
|
22
|
-
assert subject.invalid?
|
17
|
+
should 'raise ActiveRecord::UnknownAttributeError when using attributes=' do
|
18
|
+
assert_raise(ActiveRecord::UnknownAttributeError) do
|
19
|
+
subject.update_attributes('honest' => 'man')
|
20
|
+
end
|
23
21
|
end
|
24
22
|
|
25
|
-
should 'set an error message on the property' do
|
23
|
+
should 'set an error message on the property when set directly' do
|
24
|
+
subject.prop['honest'] = 'man'
|
25
|
+
assert !subject.save
|
26
26
|
assert_contains subject.errors.full_messages, 'Honest property is not declared'
|
27
27
|
assert_equal subject.errors['honest'], 'property is not declared'
|
28
28
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: property
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renaud Kern
|
@@ -24,7 +24,7 @@ dependencies:
|
|
24
24
|
version: "0"
|
25
25
|
version:
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
27
|
+
name: activerecord
|
28
28
|
type: :runtime
|
29
29
|
version_requirement:
|
30
30
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -57,6 +57,7 @@ files:
|
|
57
57
|
- lib/property/serialization/json.rb
|
58
58
|
- lib/property/serialization/marshal.rb
|
59
59
|
- lib/property/serialization/yaml.rb
|
60
|
+
- property.gemspec
|
60
61
|
- test/fixtures.rb
|
61
62
|
- test/shoulda_macros/serialization.rb
|
62
63
|
- test/test_helper.rb
|