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