property 0.5.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 +2 -0
- data/History.txt +10 -0
- data/MIT-LICENSE +19 -0
- data/README.rdoc +48 -0
- data/Rakefile +51 -0
- data/generators/property/property_generator.rb +12 -0
- data/lib/property.rb +16 -0
- data/lib/property/attribute.rb +89 -0
- data/lib/property/column.rb +35 -0
- data/lib/property/declaration.rb +120 -0
- data/lib/property/dirty.rb +98 -0
- data/lib/property/properties.rb +80 -0
- data/lib/property/serialization/json.rb +38 -0
- data/lib/property/serialization/marshal.rb +35 -0
- data/lib/property/serialization/yaml.rb +29 -0
- data/test/fixtures.rb +57 -0
- data/test/shoulda_macros/serialization.rb +71 -0
- data/test/test_helper.rb +19 -0
- data/test/unit/property/attribute_test.rb +334 -0
- data/test/unit/property/declaration_test.rb +127 -0
- data/test/unit/property/dirty_test.rb +157 -0
- data/test/unit/property/validation_test.rb +97 -0
- data/test/unit/serialization/json_test.rb +12 -0
- data/test/unit/serialization/marshal_test.rb +12 -0
- data/test/unit/serialization/yaml_test.rb +12 -0
- metadata +108 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Property
|
2
|
+
class Properties < Hash
|
3
|
+
attr_accessor :owner
|
4
|
+
include Property::DirtyProperties
|
5
|
+
|
6
|
+
def self.json_create(serialized)
|
7
|
+
self[serialized['data']]
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_json(*args)
|
11
|
+
{
|
12
|
+
'json_class' => self.class.name,
|
13
|
+
'data' => Hash[self]
|
14
|
+
}.to_json(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
if column = columns[key]
|
19
|
+
if value.blank?
|
20
|
+
if default = column.default
|
21
|
+
super(key, default)
|
22
|
+
else
|
23
|
+
delete(key)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
super(key, column.type_cast(value.to_s))
|
27
|
+
end
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# We need to write our own merge so that typecasting is called
|
34
|
+
def merge!(attributes)
|
35
|
+
raise TypeError.new("can't convert #{attributes.class} into Hash") unless attributes.kind_of?(Hash)
|
36
|
+
attributes.each do |key, value|
|
37
|
+
self[key] = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate
|
42
|
+
columns = @owner.class.property_columns
|
43
|
+
column_names = @owner.class.property_column_names
|
44
|
+
errors = @owner.errors
|
45
|
+
no_errors = true
|
46
|
+
|
47
|
+
bad_keys = keys - column_names
|
48
|
+
missing_keys = column_names - keys
|
49
|
+
|
50
|
+
bad_keys.each do |key|
|
51
|
+
errors.add("#{key}", 'property is not declared')
|
52
|
+
end
|
53
|
+
|
54
|
+
missing_keys.each do |key|
|
55
|
+
column = columns[key]
|
56
|
+
if column.has_default?
|
57
|
+
self[key] = column.default
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
bad_keys.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def compact!
|
65
|
+
#keys.each do |key|
|
66
|
+
# if self[key].nil?
|
67
|
+
# delete(key)
|
68
|
+
# end
|
69
|
+
#end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def write_attribute(key, value)
|
74
|
+
end
|
75
|
+
|
76
|
+
def columns
|
77
|
+
@columns ||= @owner.class.property_columns
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Property
|
2
|
+
module Serialization
|
3
|
+
# Use JSON to encode properties. This is the serialization best option. It's
|
4
|
+
# the fastest and does not have any binary format issues. You just have to
|
5
|
+
# provide 'self.create_json' and 'to_json' methods for the classes you want
|
6
|
+
# to serialize.
|
7
|
+
module JSON
|
8
|
+
module ClassMethods
|
9
|
+
NATIVE_TYPES = [Hash, Array, Integer, Float, String, TrueClass, FalseClass, NilClass]
|
10
|
+
|
11
|
+
# Returns true if the given class can be serialized with JSON
|
12
|
+
def validate_property_class(klass)
|
13
|
+
if NATIVE_TYPES.include?(klass) ||
|
14
|
+
(klass.respond_to?('json_create') && klass.instance_methods.include?('to_json'))
|
15
|
+
true
|
16
|
+
else
|
17
|
+
raise TypeError.new("Cannot serialize #{klass}. Missing 'self.create_json' and 'to_json' methods.")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.included(base)
|
23
|
+
base.extend ClassMethods
|
24
|
+
end
|
25
|
+
|
26
|
+
# Encode properties with Marhsal
|
27
|
+
def encode_properties(properties)
|
28
|
+
properties.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
# Decode Marshal encoded properties
|
32
|
+
def decode_properties(string)
|
33
|
+
::JSON.parse(string)
|
34
|
+
end
|
35
|
+
|
36
|
+
end # JSON
|
37
|
+
end # Serialization
|
38
|
+
end # Property
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Property
|
2
|
+
module Serialization
|
3
|
+
# Use Marhsal to encode properties. Unless you have very good reasons
|
4
|
+
# to use Marshal, you should use the JSON serialization instead:
|
5
|
+
# * it's faster at reading text/date based objects
|
6
|
+
# * it's human readable
|
7
|
+
# * no corruption risk if the version of Marshal changes
|
8
|
+
# * it can be accessed by other languages then ruby
|
9
|
+
module Marshal
|
10
|
+
module ClassMethods
|
11
|
+
# Returns true if the given class can be serialized with Marshal
|
12
|
+
def validate_property_class(klass)
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
# Encode properties with Marhsal
|
22
|
+
def encode_properties(properties)
|
23
|
+
# we limit dump depth to 0 (object only: no instance variables)
|
24
|
+
# we have to protect Marshal from serializing instance variables by making a copy
|
25
|
+
[::Marshal::dump(Properties[properties])].pack('m*')
|
26
|
+
end
|
27
|
+
|
28
|
+
# Decode Marshal encoded properties
|
29
|
+
def decode_properties(string)
|
30
|
+
::Marshal::load(string.unpack('m')[0])
|
31
|
+
end
|
32
|
+
|
33
|
+
end # Marshal
|
34
|
+
end # Serialization
|
35
|
+
end # Property
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Property
|
2
|
+
module Serialization
|
3
|
+
# Use YAML to encode properties. This method is the slowest of all
|
4
|
+
# and you should use JSON if you haven't got good reasons not to.
|
5
|
+
module YAML
|
6
|
+
module ClassMethods
|
7
|
+
# Returns true if the given class can be serialized with YAML
|
8
|
+
def validate_property_class(klass)
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
# Encode properties with YAML
|
18
|
+
def encode_properties(properties)
|
19
|
+
::YAML.dump(properties)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Decode properties from YAML
|
23
|
+
def decode_properties(string)
|
24
|
+
::YAML::load(string)
|
25
|
+
end
|
26
|
+
|
27
|
+
end # Yaml
|
28
|
+
end # Serialization
|
29
|
+
end # Property
|
data/test/fixtures.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
class Employee < ActiveRecord::Base
|
3
|
+
include Property
|
4
|
+
property.string 'first_name', :default => '', :indexed => true
|
5
|
+
property.string 'last_name', :default => '', :indexed => true
|
6
|
+
property.float 'age'
|
7
|
+
end
|
8
|
+
|
9
|
+
class Developer < Employee
|
10
|
+
property.string 'language'
|
11
|
+
end
|
12
|
+
|
13
|
+
class WebDeveloper < Developer
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class Version < ActiveRecord::Base
|
18
|
+
attr_accessor :backup
|
19
|
+
include Property
|
20
|
+
property.string 'foo'
|
21
|
+
# Other way to declare a string
|
22
|
+
property do |p|
|
23
|
+
p.string 'tic', 'comment'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
class PropertyMigration < ActiveRecord::Migration
|
29
|
+
def self.down
|
30
|
+
drop_table "employees"
|
31
|
+
drop_table "versions"
|
32
|
+
end
|
33
|
+
def self.up
|
34
|
+
create_table "employees" do |t|
|
35
|
+
t.string "type"
|
36
|
+
t.text "properties"
|
37
|
+
end
|
38
|
+
|
39
|
+
create_table "versions" do |t|
|
40
|
+
t.string "properties"
|
41
|
+
t.string "title"
|
42
|
+
t.string "comment"
|
43
|
+
t.timestamps
|
44
|
+
end
|
45
|
+
|
46
|
+
create_table "dummies" do |t|
|
47
|
+
t.text "properties"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ActiveRecord::Base.establish_connection(:adapter=>'sqlite3', :database=>':memory:')
|
53
|
+
ActiveRecord::Migration.verbose = false
|
54
|
+
#PropertyMigration.migrate(:down)
|
55
|
+
PropertyMigration.migrate(:up)
|
56
|
+
ActiveRecord::Migration.verbose = true
|
57
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Test::Unit::TestCase
|
2
|
+
|
3
|
+
def self.should_encode_and_decode_properties
|
4
|
+
klass = self.name.gsub(/Test$/,'').constantize
|
5
|
+
context klass do
|
6
|
+
should 'respond to validate_property_class' do
|
7
|
+
assert klass.respond_to? :validate_property_class
|
8
|
+
end
|
9
|
+
|
10
|
+
[Property::Properties, String, Integer, Float].each do |a_class|
|
11
|
+
should "accept to serialize #{a_class}" do
|
12
|
+
assert klass.validate_property_class(a_class)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "Instance of #{klass}" do
|
18
|
+
setup do
|
19
|
+
@obj = klass.new
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'respond to :encode_properties' do
|
23
|
+
assert @obj.respond_to? :encode_properties
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'respond to :decode_properties' do
|
27
|
+
assert @obj.respond_to? :decode_properties
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with Properties' do
|
31
|
+
setup do
|
32
|
+
@properties = Property::Properties['foo' => 'bar']
|
33
|
+
end
|
34
|
+
|
35
|
+
should 'encode Properties in string' do
|
36
|
+
assert_kind_of String, @obj.encode_properties(@properties)
|
37
|
+
end
|
38
|
+
|
39
|
+
should 'restore Properties from string' do
|
40
|
+
string = @obj.encode_properties(@properties)
|
41
|
+
properties = @obj.decode_properties(string)
|
42
|
+
assert_equal Property::Properties, properties.class
|
43
|
+
assert_equal @properties, properties
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'not include instance variables' do
|
47
|
+
@properties.instance_eval do
|
48
|
+
@baz = 'some data'
|
49
|
+
@owner = klass.new
|
50
|
+
end
|
51
|
+
prop = @obj.decode_properties(@obj.encode_properties(@properties))
|
52
|
+
assert_nil prop.instance_variable_get(:@baz)
|
53
|
+
assert_nil prop.instance_variable_get(:@owner)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with empty Properties' do
|
58
|
+
setup do
|
59
|
+
@properties = Property::Properties.new
|
60
|
+
end
|
61
|
+
|
62
|
+
should 'encode and decode' do
|
63
|
+
string = @obj.encode_properties(@properties)
|
64
|
+
properties = @obj.decode_properties(string)
|
65
|
+
assert_equal Property::Properties, properties.class
|
66
|
+
assert_equal @properties, properties
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
$LOAD_PATH.unshift((Pathname(__FILE__).dirname + '..' + 'lib').expand_path)
|
3
|
+
require 'rubygems'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'shoulda'
|
6
|
+
require 'active_record'
|
7
|
+
require 'property'
|
8
|
+
require 'shoulda_macros/serialization'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
|
12
|
+
def assert_attribute(value, attr_name, object=subject)
|
13
|
+
assert_equal value, object.send(attr_name)
|
14
|
+
assert_equal value, object[attr_name]
|
15
|
+
assert_equal value, object.attributes[attr_name]
|
16
|
+
assert_equal value, object.properties=erties[attr_name]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'fixtures'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
class AttributeTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
ActiveRecord::Base.default_timezone = :utc
|
8
|
+
ENV['TZ'] = 'UTC'
|
9
|
+
|
10
|
+
context 'When including Property' do
|
11
|
+
should 'include Property::Attribute' do
|
12
|
+
assert Version.include?(Property::Attribute)
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'include Property::Serialization::JSON' do
|
16
|
+
assert Version.include?(Property::Serialization::JSON)
|
17
|
+
end
|
18
|
+
|
19
|
+
should 'include Property::Dirty' do
|
20
|
+
assert Version.include?(Property::Dirty)
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'include Property::Declaration' do
|
24
|
+
assert Version.include?(Property::Declaration)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'When defining new properties' do
|
29
|
+
should 'not allow symbols as keys' do
|
30
|
+
assert_raise(ArgumentError) do
|
31
|
+
Class.new(ActiveRecord::Base) do
|
32
|
+
include Property
|
33
|
+
property :foo, String
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'When writing properties' do
|
40
|
+
subject { Version.new }
|
41
|
+
|
42
|
+
setup do
|
43
|
+
subject.properties = {'foo'=>'bar'}
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with properties=' do
|
47
|
+
should 'merge hash in current content' do
|
48
|
+
subject.properties = {'other' => 'value'}
|
49
|
+
assert_equal Hash['foo' => 'bar', 'other' => 'value'], subject.properties
|
50
|
+
end
|
51
|
+
|
52
|
+
should 'replace current values' do
|
53
|
+
subject.properties = {'foo' => 'baz'}
|
54
|
+
assert_equal Hash['foo' => 'baz'], subject.properties
|
55
|
+
end
|
56
|
+
|
57
|
+
should 'raise TypeError if new attributes is not a Hash' do
|
58
|
+
assert_raise(TypeError) { subject.properties = 'this a string' }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
should 'with merge! should merge new attributes' do
|
63
|
+
subject.properties.merge!({'b'=>'bravo', 'c'=>'charlie'})
|
64
|
+
assert_equal Hash['foo' => 'bar', 'b' => 'bravo', 'c' => 'charlie'], subject.properties
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'When writing attributes with hash access' do
|
69
|
+
subject { Version.new('foo' => 'bar') }
|
70
|
+
|
71
|
+
setup do
|
72
|
+
subject.properties['foo'] = 'babar'
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'write a property into properties' do
|
76
|
+
assert_equal Hash['foo' => 'babar'], subject.properties
|
77
|
+
end
|
78
|
+
|
79
|
+
should 'save property in properties' do
|
80
|
+
subject.save
|
81
|
+
version = Version.find(subject.id)
|
82
|
+
assert_equal Hash['foo' => 'babar'], version.properties
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
context 'The properties of an object' do
|
88
|
+
subject { Version.new }
|
89
|
+
|
90
|
+
setup do
|
91
|
+
subject.properties={'foo'=>'bar', :tic=>:tac}
|
92
|
+
end
|
93
|
+
|
94
|
+
should 'be accessible with :properties method' do
|
95
|
+
assert_equal Hash['foo'=>'bar', :tic=>:tac], subject.properties
|
96
|
+
end
|
97
|
+
|
98
|
+
should 'be a kind of Hash' do
|
99
|
+
assert_kind_of Hash, subject.properties
|
100
|
+
end
|
101
|
+
|
102
|
+
should 'respond to delete' do
|
103
|
+
assert_equal 'bar', subject.properties.delete('foo')
|
104
|
+
assert_nil subject.properties['foo']
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'Setting attributes' do
|
110
|
+
subject { Version.new }
|
111
|
+
|
112
|
+
setup do
|
113
|
+
subject.attributes = {'foo'=>'bar', 'title'=>'test', 'backup' => 'please'}
|
114
|
+
end
|
115
|
+
|
116
|
+
should 'set rails attributes' do
|
117
|
+
assert_equal 'test', subject.title
|
118
|
+
end
|
119
|
+
|
120
|
+
should 'set properties' do
|
121
|
+
assert_equal Hash['foo'=>'bar'], subject.properties
|
122
|
+
end
|
123
|
+
|
124
|
+
should 'call native methods' do
|
125
|
+
assert_equal 'please', subject.backup
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'Initializing an object' do
|
130
|
+
subject { Version.new('foo'=>'bar', 'title'=>'test', 'backup' => 'please') }
|
131
|
+
|
132
|
+
should 'set rails attributes' do
|
133
|
+
assert_equal 'test', subject.title
|
134
|
+
end
|
135
|
+
|
136
|
+
should 'set properties' do
|
137
|
+
assert_equal Hash['foo'=>'bar'], subject.properties
|
138
|
+
end
|
139
|
+
|
140
|
+
should 'call native methods' do
|
141
|
+
assert_equal 'please', subject.backup
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'Updating attributes' do
|
146
|
+
setup do
|
147
|
+
version = Version.create('title' => 'first', 'tic' => 'tac')
|
148
|
+
@version = Version.find(version.id)
|
149
|
+
assert subject.update_attributes('foo'=>'bar', 'title'=>'test', 'backup' => 'please')
|
150
|
+
end
|
151
|
+
|
152
|
+
subject { @version }
|
153
|
+
|
154
|
+
should 'update rails attributes' do
|
155
|
+
assert_equal 'test', subject.title
|
156
|
+
end
|
157
|
+
|
158
|
+
should 'update properties' do
|
159
|
+
assert_equal Hash['tic' => 'tac', 'foo'=>'bar'], subject.properties
|
160
|
+
end
|
161
|
+
|
162
|
+
should 'call native methods' do
|
163
|
+
assert_equal 'please', subject.backup
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'Saving attributes' do
|
168
|
+
setup do
|
169
|
+
version = Version.create('title'=>'test', 'foo' => 'bar', 'backup' => 'please')
|
170
|
+
@version = Version.find(version.id)
|
171
|
+
end
|
172
|
+
|
173
|
+
subject { @version }
|
174
|
+
|
175
|
+
should 'save rails attributes' do
|
176
|
+
assert_equal 'test', subject.title
|
177
|
+
end
|
178
|
+
|
179
|
+
should 'save properties' do
|
180
|
+
assert_equal 'bar', subject.prop['foo']
|
181
|
+
end
|
182
|
+
|
183
|
+
should 'destroy' do
|
184
|
+
assert subject.destroy
|
185
|
+
assert subject.frozen?
|
186
|
+
end
|
187
|
+
|
188
|
+
should 'delete' do
|
189
|
+
assert subject.delete
|
190
|
+
assert subject.frozen?
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'Saving empty attributes' do
|
195
|
+
subject { Version.new('foo' => 'bar') }
|
196
|
+
|
197
|
+
setup do
|
198
|
+
subject.prop.delete('foo')
|
199
|
+
subject.save
|
200
|
+
end
|
201
|
+
|
202
|
+
should 'save nil in database' do
|
203
|
+
assert_nil subject['properties']
|
204
|
+
end
|
205
|
+
|
206
|
+
should 'save nil when last property is removed' do
|
207
|
+
subject = Version.create('foo' => 'bar', 'tic' => 'tac')
|
208
|
+
subject.attributes = {'foo' => nil}
|
209
|
+
subject.update_attributes('foo' => nil)
|
210
|
+
assert_equal ['tic'], subject.properties.keys
|
211
|
+
subject.properties.delete('tic')
|
212
|
+
subject.save
|
213
|
+
assert_nil subject['properties']
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'Saving without changes to properties' do
|
218
|
+
setup do
|
219
|
+
version = Version.create('title' => 'test', 'foo' => 'bar')
|
220
|
+
@version = Version.find(version.id)
|
221
|
+
subject.update_attributes('title' => 'updated')
|
222
|
+
end
|
223
|
+
|
224
|
+
subject { @version }
|
225
|
+
|
226
|
+
should 'not alter properties' do
|
227
|
+
assert_equal Hash['foo' => 'bar'], subject.properties
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'Find' do
|
232
|
+
subject { Version.create('title'=>'find me', 'foo' => 'bar') }
|
233
|
+
|
234
|
+
should 'find by id' do
|
235
|
+
version = Version.find(subject)
|
236
|
+
assert_equal 'bar', version.prop['foo']
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'A modified version receiving :reload_properties' do
|
241
|
+
should 'return properties stored in database' do
|
242
|
+
subject = Version.create('title'=>'find me', 'foo' => 'bar')
|
243
|
+
subject.prop['foo'] = 'Babar'
|
244
|
+
assert_equal 'Babar', subject.prop['foo']
|
245
|
+
subject.reload_properties!
|
246
|
+
assert_equal 'bar', subject.prop['foo']
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'Type cast' do
|
251
|
+
DataType = Class.new(ActiveRecord::Base) do
|
252
|
+
set_table_name 'dummies'
|
253
|
+
include Property
|
254
|
+
property do |p|
|
255
|
+
p.string 'mystring'
|
256
|
+
p.integer 'myinteger'
|
257
|
+
p.float 'myfloat'
|
258
|
+
p.datetime 'mytime'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
should 'save and read String' do
|
263
|
+
subject = DataType.create('mystring' => 'some string')
|
264
|
+
subject.reload
|
265
|
+
assert_kind_of String, subject.prop['mystring']
|
266
|
+
end
|
267
|
+
|
268
|
+
should 'save and read Integer' do
|
269
|
+
subject = DataType.create('myinteger' => 123)
|
270
|
+
subject.reload
|
271
|
+
assert_kind_of Integer, subject.prop['myinteger']
|
272
|
+
end
|
273
|
+
|
274
|
+
should 'save and read Float' do
|
275
|
+
subject = DataType.create('myfloat' => 12.3)
|
276
|
+
subject.reload
|
277
|
+
assert_kind_of Float, subject.prop['myfloat']
|
278
|
+
end
|
279
|
+
|
280
|
+
should 'save and read Time' do
|
281
|
+
subject = DataType.create('mytime' => Time.new)
|
282
|
+
subject.reload
|
283
|
+
assert_kind_of Time, subject.prop['mytime']
|
284
|
+
end
|
285
|
+
|
286
|
+
context 'from a String' do
|
287
|
+
should 'parse integer values' do
|
288
|
+
subject = DataType.create('myinteger' => '123')
|
289
|
+
subject.reload
|
290
|
+
assert_kind_of Integer, subject.prop['myinteger']
|
291
|
+
assert_equal 123, subject.prop['myinteger']
|
292
|
+
end
|
293
|
+
|
294
|
+
should 'parse float values' do
|
295
|
+
subject = DataType.create('myfloat' => '12.3')
|
296
|
+
subject.reload
|
297
|
+
assert_kind_of Float, subject.prop['myfloat']
|
298
|
+
assert_equal 12.3, subject.prop['myfloat']
|
299
|
+
end
|
300
|
+
|
301
|
+
should 'parse time values' do
|
302
|
+
subject = DataType.create('mytime' => '2010-02-10 21:21')
|
303
|
+
subject.reload
|
304
|
+
assert_kind_of Time, subject.prop['mytime']
|
305
|
+
assert_equal Time.utc(2010,02,10,21,21), subject.prop['mytime']
|
306
|
+
end
|
307
|
+
|
308
|
+
context 'in the model' do
|
309
|
+
should 'parse integer values' do
|
310
|
+
subject = DataType.new
|
311
|
+
subject.prop['myinteger'] = '123'
|
312
|
+
assert_kind_of Integer, subject.prop['myinteger']
|
313
|
+
assert_equal 123, subject.prop['myinteger']
|
314
|
+
end
|
315
|
+
|
316
|
+
should 'parse float values' do
|
317
|
+
subject = DataType.new
|
318
|
+
subject.prop['myfloat'] = '12.3'
|
319
|
+
assert_kind_of Float, subject.prop['myfloat']
|
320
|
+
assert_equal 12.3, subject.prop['myfloat']
|
321
|
+
end
|
322
|
+
|
323
|
+
should 'parse time values' do
|
324
|
+
subject = DataType.new
|
325
|
+
subject.prop['mytime'] = '2010-02-10 21:21'
|
326
|
+
assert_kind_of Time, subject.prop['mytime']
|
327
|
+
assert_equal Time.utc(2010,02,10,21,21), subject.prop['mytime']
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
end
|