serializable_attributes 0.9.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/LICENSE +20 -0
- data/README.md +56 -0
- data/Rakefile +132 -0
- data/gemfiles/ar-2.2.gemfile +6 -0
- data/gemfiles/ar-2.3.gemfile +6 -0
- data/gemfiles/ar-3.0.gemfile +6 -0
- data/gemfiles/ar-3.1.gemfile +6 -0
- data/init.rb +2 -0
- data/lib/serializable_attributes/duplicable.rb +65 -0
- data/lib/serializable_attributes/format/active_support_json.rb +29 -0
- data/lib/serializable_attributes/schema.rb +188 -0
- data/lib/serializable_attributes/types.rb +139 -0
- data/lib/serializable_attributes.rb +66 -0
- data/rails_init.rb +3 -0
- data/script/setup +1 -0
- data/serializable_attributes.gemspec +67 -0
- data/test/serialized_attributes_test.rb +382 -0
- data/test/test_helper.rb +107 -0
- data/test/types_test.rb +42 -0
- metadata +80 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
module SerializableAttributes
|
2
|
+
class AttributeType
|
3
|
+
def initialize(options = {})
|
4
|
+
@default = options[:default]
|
5
|
+
end
|
6
|
+
|
7
|
+
def encode(s) s end
|
8
|
+
|
9
|
+
def type_for(key)
|
10
|
+
SerializableAttributes.const_get(key.to_s.classify).new
|
11
|
+
end
|
12
|
+
|
13
|
+
def default
|
14
|
+
@default && @default.duplicable? ? @default.dup : @default
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Integer < AttributeType
|
19
|
+
attr_reader :default
|
20
|
+
def parse(input) input.blank? ? nil : input.to_i end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Float < AttributeType
|
24
|
+
attr_reader :default
|
25
|
+
def parse(input) input.blank? ? nil : input.to_f end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Boolean < AttributeType
|
29
|
+
attr_reader :default
|
30
|
+
def parse(input)
|
31
|
+
return nil if input == ""
|
32
|
+
input && input.respond_to?(:to_i) ? (input.to_i > 0) : input
|
33
|
+
end
|
34
|
+
|
35
|
+
def encode(input)
|
36
|
+
return nil if input.to_s.empty?
|
37
|
+
return 0 if input == 'false'
|
38
|
+
input ? 1 : 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class String < AttributeType
|
43
|
+
# converts unicode (\u003c) to the actual character
|
44
|
+
# http://rishida.net/tools/conversion/
|
45
|
+
def parse(str)
|
46
|
+
return nil if str.nil?
|
47
|
+
str.to_s.gsub(/\\u([0-9a-fA-F]{4})/) do |s|
|
48
|
+
int = $1.to_i(16)
|
49
|
+
if int.zero? && s != "0000"
|
50
|
+
s
|
51
|
+
else
|
52
|
+
[int].pack("U")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Time < AttributeType
|
59
|
+
def parse(input)
|
60
|
+
return nil if input.blank?
|
61
|
+
case input
|
62
|
+
when ::Time then input
|
63
|
+
when ::String then ::Time.parse(input)
|
64
|
+
else input.to_time
|
65
|
+
end
|
66
|
+
end
|
67
|
+
def encode(input) input ? input.utc.xmlschema : nil end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Array < AttributeType
|
71
|
+
def initialize(options = {})
|
72
|
+
super
|
73
|
+
@item_type = type_for(options[:type] || "String")
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse(input)
|
77
|
+
if input.nil?
|
78
|
+
nil
|
79
|
+
elsif input.blank?
|
80
|
+
[]
|
81
|
+
else
|
82
|
+
input.map! { |item| @item_type.parse(item) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def encode(input)
|
87
|
+
if input.nil?
|
88
|
+
nil
|
89
|
+
elsif input.blank?
|
90
|
+
[]
|
91
|
+
else
|
92
|
+
input.map! { |item| @item_type.encode(item) }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Hash < AttributeType
|
98
|
+
def initialize(options = {})
|
99
|
+
super
|
100
|
+
@key_type = String.new
|
101
|
+
@types = (options[:types] || {})
|
102
|
+
@types.keys.each do |key|
|
103
|
+
value = @types.delete(key)
|
104
|
+
@types[key.to_s] = type_for(value)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse(input)
|
109
|
+
return nil if input.blank?
|
110
|
+
input.keys.each do |key|
|
111
|
+
value = input.delete(key)
|
112
|
+
key_s = @key_type.parse(key)
|
113
|
+
type = @types[key_s] || @key_type
|
114
|
+
input[key_s] = type.parse(value)
|
115
|
+
end
|
116
|
+
input
|
117
|
+
end
|
118
|
+
|
119
|
+
def encode(input)
|
120
|
+
return nil if input.blank?
|
121
|
+
input.each do |key, value|
|
122
|
+
type = @types[key] || @key_type
|
123
|
+
input[key] = type.encode(value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class << self
|
129
|
+
attr_accessor :types
|
130
|
+
def add_type(type, object = nil)
|
131
|
+
types[type] = object
|
132
|
+
Schema.send(:define_method, type) do |*names|
|
133
|
+
field type, *names
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
self.types = {}
|
138
|
+
end
|
139
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# create a BLOB column and setup your field types for conversion
|
2
|
+
# and convenient attr methods
|
3
|
+
#
|
4
|
+
# class Profile < ActiveRecord::Base
|
5
|
+
# # not needed if used as a rails plugin
|
6
|
+
# SerializableAttributes.setup(self)
|
7
|
+
#
|
8
|
+
# # assumes #data serializes to raw_data blob field
|
9
|
+
# serialize_attributes do
|
10
|
+
# string :title, :description
|
11
|
+
# integer :age
|
12
|
+
# float :rank, :percentage
|
13
|
+
# time :birthday
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Serializes #data to assumed raw_data blob field
|
17
|
+
# serialize_attributes :data do
|
18
|
+
# string :title, :description
|
19
|
+
# integer :age
|
20
|
+
# float :rank, :percentage
|
21
|
+
# time :birthday
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # set the blob field
|
25
|
+
# serialize_attributes :data, :blob => :serialized_field do
|
26
|
+
# string :title, :description
|
27
|
+
# integer :age
|
28
|
+
# float :rank, :percentage
|
29
|
+
# time :birthday
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
module SerializableAttributes
|
34
|
+
VERSION = "0.9.0"
|
35
|
+
|
36
|
+
require File.expand_path('../serializable_attributes/types', __FILE__)
|
37
|
+
require File.expand_path('../serializable_attributes/schema', __FILE__)
|
38
|
+
|
39
|
+
if nil.respond_to?(:duplicable?)
|
40
|
+
require File.expand_path('../serializable_attributes/duplicable', __FILE__)
|
41
|
+
end
|
42
|
+
|
43
|
+
module Format
|
44
|
+
autoload :ActiveSupportJson, File.expand_path('../serializable_attributes/format/active_support_json', __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
add_type :string, String
|
48
|
+
add_type :integer, Integer
|
49
|
+
add_type :float, Float
|
50
|
+
add_type :time, Time
|
51
|
+
add_type :boolean, Boolean
|
52
|
+
add_type :array, Array
|
53
|
+
add_type :hash, Hash
|
54
|
+
|
55
|
+
module ModelMethods
|
56
|
+
def serialize_attributes(field = :data, options = {}, &block)
|
57
|
+
schema = Schema.new(self, field, options)
|
58
|
+
schema.instance_eval(&block)
|
59
|
+
schema.fields.freeze
|
60
|
+
schema
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Object.const_set :SerializedAttributes, SerializableAttributes
|
66
|
+
|
data/rails_init.rb
ADDED
data/script/setup
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
bundle install --binstubs --path vendor/gems
|
@@ -0,0 +1,67 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version=
|
10
|
+
|
11
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
12
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
13
|
+
## the sub! line in the Rakefile
|
14
|
+
s.name = 'serializable_attributes'
|
15
|
+
s.version = '0.9.0'
|
16
|
+
s.date = '2011-12-05'
|
17
|
+
s.rubyforge_project = 'serializable_attributes'
|
18
|
+
|
19
|
+
## Make sure your summary is short. The description may be as long
|
20
|
+
## as you like.
|
21
|
+
s.summary = "Store a serialized hash of attributes in a single ActiveRecord column."
|
22
|
+
s.description = "A bridge between using AR and a full blown schema-free db."
|
23
|
+
|
24
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
25
|
+
## better to set the email to an email list or something. If you don't have
|
26
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
27
|
+
s.authors = ["Rick Olson"]
|
28
|
+
s.email = 'technoweenie@gmail.com'
|
29
|
+
s.homepage = 'http://github.com/technoweenie/serialized_attributes'
|
30
|
+
|
31
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
32
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
33
|
+
s.require_paths = %w[lib]
|
34
|
+
|
35
|
+
s.add_dependency "activerecord", [">= 2.2.0", "< 3.2.0"]
|
36
|
+
|
37
|
+
## Leave this section as-is. It will be automatically generated from the
|
38
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
39
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
40
|
+
# = MANIFEST =
|
41
|
+
s.files = %w[
|
42
|
+
LICENSE
|
43
|
+
README.md
|
44
|
+
Rakefile
|
45
|
+
gemfiles/ar-2.2.gemfile
|
46
|
+
gemfiles/ar-2.3.gemfile
|
47
|
+
gemfiles/ar-3.0.gemfile
|
48
|
+
gemfiles/ar-3.1.gemfile
|
49
|
+
init.rb
|
50
|
+
lib/serializable_attributes.rb
|
51
|
+
lib/serializable_attributes/duplicable.rb
|
52
|
+
lib/serializable_attributes/format/active_support_json.rb
|
53
|
+
lib/serializable_attributes/schema.rb
|
54
|
+
lib/serializable_attributes/types.rb
|
55
|
+
rails_init.rb
|
56
|
+
script/setup
|
57
|
+
serializable_attributes.gemspec
|
58
|
+
test/serialized_attributes_test.rb
|
59
|
+
test/test_helper.rb
|
60
|
+
test/types_test.rb
|
61
|
+
]
|
62
|
+
# = MANIFEST =
|
63
|
+
|
64
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
65
|
+
## matches what you actually use.
|
66
|
+
s.test_files = s.files.select { |path| path =~ %r{^test/*/.+\.rb} }
|
67
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require File.expand_path("../test_helper", __FILE__)
|
3
|
+
|
4
|
+
formatters = [SerializedAttributes::Format::ActiveSupportJson]
|
5
|
+
formatters.each do |fmt|
|
6
|
+
Object.const_set("SerializedAttributeWithSerializedDataTestWith#{fmt.name.demodulize}", Class.new(ActiveSupport::TestCase)).class_eval do
|
7
|
+
class << self
|
8
|
+
attr_accessor :format, :current_time, :raw_hash, :raw_data
|
9
|
+
end
|
10
|
+
self.format = fmt
|
11
|
+
self.current_time = Time.now.utc.midnight
|
12
|
+
self.raw_hash = {:title => 'abc', :age => 5, :average => 5.1, :birthday => current_time.xmlschema, :active => true, :names => %w(d e f), :lottery_picks => [1, 8, 7], :extras => {'b' => 'two'}}
|
13
|
+
self.raw_data = format.encode(raw_hash)
|
14
|
+
|
15
|
+
def setup
|
16
|
+
SerializedRecordWithDefaults.data_schema.formatter = SerializedRecord.data_schema.formatter = self.class.format
|
17
|
+
@newbie = SerializedRecordWithDefaults.new
|
18
|
+
@record = SerializedRecord.new
|
19
|
+
@changed = SerializedRecord.new
|
20
|
+
@record.raw_data = self.class.raw_data
|
21
|
+
@changed.raw_data = self.class.raw_data
|
22
|
+
@changed.title = 'def'
|
23
|
+
@changed.age = 6
|
24
|
+
end
|
25
|
+
|
26
|
+
test "schema lists attribute names" do
|
27
|
+
%w(title body age average birthday active default_in_my_favor names
|
28
|
+
lottery_picks extras).each do |attr|
|
29
|
+
assert SerializedRecord.data_schema.all_column_names.include?(attr),
|
30
|
+
"#{attr} attribute not found"
|
31
|
+
assert SerializedRecord.attribute_names.include?(attr),
|
32
|
+
"#{attr} attribute not found"
|
33
|
+
end
|
34
|
+
assert !SerializedRecord.data_schema.all_column_names.include?('raw_data'),
|
35
|
+
"raw_data attribute found"
|
36
|
+
end
|
37
|
+
|
38
|
+
test "existing model respects defaults from missing key" do
|
39
|
+
assert !@record.data.key?('default_in_my_favor')
|
40
|
+
assert @record.default_in_my_favor?
|
41
|
+
assert_equal true, @record.data['default_in_my_favor']
|
42
|
+
@record.default_in_my_favor = false
|
43
|
+
assert !@record.default_in_my_favor?
|
44
|
+
@record.default_in_my_favor = nil
|
45
|
+
assert @record.default_in_my_favor?
|
46
|
+
end
|
47
|
+
|
48
|
+
test "new model respects array defaults" do
|
49
|
+
assert_equal %w(a b c), @newbie.names
|
50
|
+
end
|
51
|
+
|
52
|
+
test "new model respects hash defaults" do
|
53
|
+
assert_equal({:a => 1}, @newbie.extras)
|
54
|
+
end
|
55
|
+
|
56
|
+
test "new model respects integer defaults" do
|
57
|
+
assert_equal 18, @newbie.age
|
58
|
+
end
|
59
|
+
|
60
|
+
test "new model respects string defaults" do
|
61
|
+
assert_equal 'blank', @newbie.title
|
62
|
+
assert_equal 'blank', @newbie.body
|
63
|
+
end
|
64
|
+
|
65
|
+
test "new model respects float defaults" do
|
66
|
+
assert_equal 5.2, @newbie.average
|
67
|
+
end
|
68
|
+
|
69
|
+
test "new model respects boolean defaults" do
|
70
|
+
assert @newbie.active?
|
71
|
+
end
|
72
|
+
|
73
|
+
test "new model respects date defaults" do
|
74
|
+
assert_equal Time.utc(2009, 1, 1), @newbie.birthday
|
75
|
+
end
|
76
|
+
|
77
|
+
test "reloads serialized data" do
|
78
|
+
@changed.id = 481516
|
79
|
+
assert_equal @record.title, @changed.reload(2342).title
|
80
|
+
assert_equal @record.age, @changed.age
|
81
|
+
end
|
82
|
+
|
83
|
+
test "initialized model is not changed" do
|
84
|
+
@record.data
|
85
|
+
assert !@record.data_changed?
|
86
|
+
end
|
87
|
+
|
88
|
+
test "#attribute_names contains serialized fields" do
|
89
|
+
assert_equal %w(active age average birthday extras lottery_picks names title), @record.attribute_names
|
90
|
+
@record.body = 'a'
|
91
|
+
assert_equal %w(active age average birthday body extras lottery_picks names title), @record.attribute_names
|
92
|
+
end
|
93
|
+
|
94
|
+
test "initialization does not call writers" do
|
95
|
+
def @record.title=(v)
|
96
|
+
raise ArgumentError
|
97
|
+
end
|
98
|
+
assert_not_nil @record.data
|
99
|
+
end
|
100
|
+
|
101
|
+
test "ignores data with extra keys" do
|
102
|
+
@record.raw_data = self.class.format.encode(self.class.raw_hash.merge(:foo => :bar))
|
103
|
+
assert_not_nil @record.title # no undefined foo= error
|
104
|
+
assert_equal false, @record.save # extra before_save cancels the operation
|
105
|
+
assert_equal self.class.raw_hash.merge(:active => 1).stringify_keys.keys.sort, self.class.format.decode(@record.raw_data).keys.sort
|
106
|
+
end
|
107
|
+
|
108
|
+
test "reads strings" do
|
109
|
+
assert_equal self.class.raw_hash[:title], @record.title
|
110
|
+
end
|
111
|
+
|
112
|
+
test "parses strings with unicode characters" do
|
113
|
+
@record.title = "Encöded ɐ \\u003c \\Upload \\upload" # test unicode char, \u**** code, and legit \U... string
|
114
|
+
assert_equal "Encöded ɐ < \\Upload \\upload", @record.title
|
115
|
+
end
|
116
|
+
|
117
|
+
test "clears strings with nil" do
|
118
|
+
assert @record.data.key?('title')
|
119
|
+
@record.title = nil
|
120
|
+
assert !@record.data.key?('title')
|
121
|
+
end
|
122
|
+
|
123
|
+
test "reads arrays" do
|
124
|
+
assert_equal self.class.raw_hash[:names], @record.names
|
125
|
+
end
|
126
|
+
|
127
|
+
test "reads arrays with custom type" do
|
128
|
+
assert_equal self.class.raw_hash[:lottery_picks], @record.lottery_picks
|
129
|
+
end
|
130
|
+
|
131
|
+
test "clears arrays with nil" do
|
132
|
+
assert @record.data.key?('names')
|
133
|
+
@record.names = nil
|
134
|
+
assert !@record.data.key?('names')
|
135
|
+
end
|
136
|
+
|
137
|
+
test "reads hashes" do
|
138
|
+
assert_equal self.class.raw_hash[:extras].stringify_keys, @record.extras
|
139
|
+
end
|
140
|
+
|
141
|
+
test "reads hashes with custom types" do
|
142
|
+
now = Time.utc(Time.now.year, 1, 1)
|
143
|
+
@record.raw_data = self.class.format.encode('extras' => {:num => "7", :foo => :bar, :started_at => now})
|
144
|
+
assert_equal({'num' => 7, 'started_at' => now, 'foo' => 'bar'}, @record.extras)
|
145
|
+
end
|
146
|
+
|
147
|
+
test "clears hashes with nil" do
|
148
|
+
assert @record.data.key?('extras')
|
149
|
+
@record.extras = nil
|
150
|
+
assert !@record.data.key?('extras')
|
151
|
+
end
|
152
|
+
|
153
|
+
test "reads integers" do
|
154
|
+
assert_equal self.class.raw_hash[:age], @record.age
|
155
|
+
end
|
156
|
+
|
157
|
+
test "parses integers from strings" do
|
158
|
+
@record.age = '5.5'
|
159
|
+
assert_equal 5, @record.age
|
160
|
+
end
|
161
|
+
|
162
|
+
test "clears integers with nil" do
|
163
|
+
assert @record.data.key?('age')
|
164
|
+
@record.age = nil
|
165
|
+
assert !@record.data.key?('age')
|
166
|
+
end
|
167
|
+
|
168
|
+
test "clears integers with blank" do
|
169
|
+
assert @record.data.key?('age')
|
170
|
+
@record.age = ''
|
171
|
+
assert !@record.data.key?('age')
|
172
|
+
end
|
173
|
+
|
174
|
+
test "reads floats" do
|
175
|
+
assert_equal self.class.raw_hash[:average], @record.average
|
176
|
+
end
|
177
|
+
|
178
|
+
test "parses floats from strings" do
|
179
|
+
@record.average = '5.5'
|
180
|
+
assert_equal 5.5, @record.average
|
181
|
+
end
|
182
|
+
|
183
|
+
test "clears floats with nil" do
|
184
|
+
assert @record.data.key?('average')
|
185
|
+
@record.average = nil
|
186
|
+
assert !@record.data.key?('average')
|
187
|
+
end
|
188
|
+
|
189
|
+
test "clears floats with blank" do
|
190
|
+
assert @record.data.key?('average')
|
191
|
+
@record.average = ''
|
192
|
+
assert !@record.data.key?('average')
|
193
|
+
end
|
194
|
+
|
195
|
+
test "reads times" do
|
196
|
+
assert_equal self.class.current_time, @record.birthday
|
197
|
+
end
|
198
|
+
|
199
|
+
test "parses times from strings" do
|
200
|
+
t = 5.years.ago.utc.midnight
|
201
|
+
@record.birthday = t.xmlschema
|
202
|
+
assert_equal t, @record.birthday
|
203
|
+
end
|
204
|
+
|
205
|
+
test "clears times with nil" do
|
206
|
+
assert @record.data.key?('birthday')
|
207
|
+
@record.birthday = nil
|
208
|
+
assert !@record.data.key?('birthday')
|
209
|
+
end
|
210
|
+
|
211
|
+
test "clears times with blank" do
|
212
|
+
assert @record.data.key?('birthday')
|
213
|
+
@record.birthday = ''
|
214
|
+
assert !@record.data.key?('birthday')
|
215
|
+
end
|
216
|
+
|
217
|
+
test "reads booleans" do
|
218
|
+
assert_equal true, @record.active
|
219
|
+
end
|
220
|
+
|
221
|
+
test "parses booleans from strings" do
|
222
|
+
@record.active = '1'
|
223
|
+
assert_equal true, @record.active
|
224
|
+
@record.active = '0'
|
225
|
+
assert_equal false, @record.active
|
226
|
+
end
|
227
|
+
|
228
|
+
test "parses booleans from integers" do
|
229
|
+
@record.active = 1
|
230
|
+
assert_equal true, @record.active
|
231
|
+
@record.active = 0
|
232
|
+
assert_equal false, @record.active
|
233
|
+
end
|
234
|
+
|
235
|
+
test "converts booleans to false with nil" do
|
236
|
+
assert @record.data.key?('active')
|
237
|
+
@record.active = nil
|
238
|
+
assert !@record.data.key?('active')
|
239
|
+
end
|
240
|
+
|
241
|
+
test "ignores empty strings for booleans" do
|
242
|
+
@newbie.clearance = ""
|
243
|
+
assert_nil @newbie.clearance
|
244
|
+
end
|
245
|
+
|
246
|
+
test "attempts to re-encode data when saving" do
|
247
|
+
assert_not_nil @record.title
|
248
|
+
@record.raw_data = nil
|
249
|
+
assert_equal false, @record.save # extra before_save cancels the operation
|
250
|
+
expected = self.class.raw_hash.merge \
|
251
|
+
:active => true,
|
252
|
+
:birthday => Time.parse(self.class.raw_hash[:birthday])
|
253
|
+
assert_equal expected.stringify_keys, @record.class.data_schema.decode(@record.raw_data)
|
254
|
+
end
|
255
|
+
|
256
|
+
test "knows untouched record is not changed" do
|
257
|
+
assert !@record.data_changed?
|
258
|
+
assert_equal [], @record.data_changed
|
259
|
+
end
|
260
|
+
|
261
|
+
test "knows updated record is changed" do
|
262
|
+
assert @changed.data_changed?
|
263
|
+
assert_equal %w(age title), @changed.data_changed.sort
|
264
|
+
end
|
265
|
+
|
266
|
+
test "tracks if field has changed" do
|
267
|
+
assert !@record.title_changed?
|
268
|
+
assert @changed.title_changed?
|
269
|
+
end
|
270
|
+
|
271
|
+
test "tracks field changes" do
|
272
|
+
assert_nil @record.title_change
|
273
|
+
assert_equal %w(abc def), @changed.title_change
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
Object.const_set("SerializedAttributeTest#{fmt.name.demodulize}", Class.new(ActiveSupport::TestCase)).class_eval do
|
278
|
+
class << self
|
279
|
+
attr_accessor :format
|
280
|
+
end
|
281
|
+
self.format = fmt
|
282
|
+
|
283
|
+
def setup
|
284
|
+
SerializedRecord.data_schema.formatter = self.class.format
|
285
|
+
@record = SerializedRecord.new
|
286
|
+
end
|
287
|
+
|
288
|
+
test "encodes and decodes data successfully" do
|
289
|
+
hash = {'a' => 1, 'b' => 2}
|
290
|
+
encoded = self.class.format.encode(hash)
|
291
|
+
assert_equal self.class.format.decode(encoded), hash
|
292
|
+
end
|
293
|
+
|
294
|
+
test "defines #data method on the model" do
|
295
|
+
assert @record.respond_to?(:data)
|
296
|
+
assert_equal @record.data, {'default_in_my_favor' => true}
|
297
|
+
end
|
298
|
+
|
299
|
+
attributes = {:string => [:title, :body], :integer => [:age], :float => [:average], :time => [:birthday], :boolean => [:active], :array => [:names, :lottery_picks], :hash => [:extras]}
|
300
|
+
attributes.values.flatten.each do |attr|
|
301
|
+
test "defines ##{attr} method on the model" do
|
302
|
+
assert @record.respond_to?(attr)
|
303
|
+
assert_nil @record.send(attr)
|
304
|
+
end
|
305
|
+
|
306
|
+
next if attr == :active
|
307
|
+
test "defines ##{attr}_before_type_cast method on the model" do
|
308
|
+
assert @record.respond_to?("#{attr}_before_type_cast")
|
309
|
+
assert_equal "", @record.send("#{attr}_before_type_cast")
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
test "defines #active_before_type_cast method on the model" do
|
314
|
+
assert @record.respond_to?(:active_before_type_cast)
|
315
|
+
assert_equal "", @record.active_before_type_cast
|
316
|
+
end
|
317
|
+
|
318
|
+
attributes[:string].each do |attr|
|
319
|
+
test "defines ##{attr}= method for string fields" do
|
320
|
+
assert @record.respond_to?("#{attr}=")
|
321
|
+
assert_equal 'abc', @record.send("#{attr}=", "abc")
|
322
|
+
assert_equal 'abc', @record.data[attr.to_s]
|
323
|
+
end
|
324
|
+
|
325
|
+
test "does not define ##{attr}? method for string fields" do
|
326
|
+
assert !@record.respond_to?("#{attr}?")
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
attributes[:integer].each do |attr|
|
331
|
+
test "defines ##{attr}= method for integer fields" do
|
332
|
+
assert @record.respond_to?("#{attr}=")
|
333
|
+
assert_equal 0, @record.send("#{attr}=", "abc")
|
334
|
+
assert_equal 1, @record.send("#{attr}=", "1.2")
|
335
|
+
assert_equal 1, @record.data[attr.to_s]
|
336
|
+
end
|
337
|
+
|
338
|
+
test "does not define ##{attr}? method for integer fields" do
|
339
|
+
assert !@record.respond_to?("#{attr}?")
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
attributes[:float].each do |attr|
|
344
|
+
test "defines ##{attr}= method for float fields" do
|
345
|
+
assert @record.respond_to?("#{attr}=")
|
346
|
+
assert_equal 0.0, @record.send("#{attr}=", "abc")
|
347
|
+
assert_equal 1.2, @record.send("#{attr}=", "1.2")
|
348
|
+
assert_equal 1.2, @record.data[attr.to_s]
|
349
|
+
end
|
350
|
+
|
351
|
+
test "does not define ##{attr}? method for float fields" do
|
352
|
+
assert !@record.respond_to?("#{attr}?")
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
attributes[:time].each do |attr|
|
357
|
+
test "defines ##{attr}= method for time fields" do
|
358
|
+
assert @record.respond_to?("#{attr}=")
|
359
|
+
t = Time.now.utc.midnight
|
360
|
+
assert_equal t, @record.send("#{attr}=", t.xmlschema)
|
361
|
+
assert_equal t, @record.data[attr.to_s]
|
362
|
+
end
|
363
|
+
|
364
|
+
test "does not define ##{attr}? method for boolean fields" do
|
365
|
+
assert !@record.respond_to?("#{attr}?")
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
attributes[:boolean].each do |attr|
|
370
|
+
test "defines ##{attr}= method for boolean fields" do
|
371
|
+
assert @record.respond_to?("#{attr}=")
|
372
|
+
assert_equal false, @record.send("#{attr}=", 0)
|
373
|
+
assert_equal true, @record.send("#{attr}=", "1.2")
|
374
|
+
assert_equal true, @record.data[attr.to_s]
|
375
|
+
end
|
376
|
+
|
377
|
+
test "defines ##{attr}? method for float fields" do
|
378
|
+
assert @record.respond_to?("#{attr}?")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|