model_attribute 0.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.rspec +2 -0
- data/CHANGELOG.md +34 -0
- data/Gemfile +1 -0
- data/Guardfile +47 -0
- data/LICENSE.txt +5 -1
- data/README.md +165 -5
- data/Rakefile +0 -1
- data/lib/model_attribute.rb +198 -1
- data/lib/model_attribute/errors.rb +14 -0
- data/lib/model_attribute/json.rb +27 -0
- data/lib/model_attribute/version.rb +1 -1
- data/model_attribute.gemspec +8 -3
- data/performance_comparison.rb +66 -0
- data/spec/model_attributes_spec.rb +611 -0
- data/spec/spec_helper.rb +92 -0
- metadata +84 -4
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZjY2MDhkZDI2ZjA0YjRjM2NkY2E5MTg0NWYxZmJhY2JmZTEyYWUzYg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MWRhZjRhOTUzNDQ5MzRhMWU5NTQwOTdkMmJjYmJlMDA1OTJhMzFiMA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZjZmMjY3NzMzM2EyODAyMDM1MTA2NTgxNGRhOWJhNGNmMGJmZTA4NDVmMzJi
|
10
|
+
NWQ0MzEyYTFlZDNjZmI5N2I5Y2FjMDE3NWE0MDIyMDBlNWJmMzNkNzNmMjBh
|
11
|
+
YTQ2Y2MzMjU2ZGI4MzNhODE5NDJkMjEyMmZmNjU3NDUwYjAzNTI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ODg1MWVhYjI3N2ZmNDM0MWY1ZGE1NWVmYWM3YzI4ODdiZTEzMGY0MDQ5YjRh
|
14
|
+
YjA3MWNmMjlmYTJmNTU4ZjJjNzk2ZmUwOWRjZjhhMGJkYTUzNGZlZDZmZGNh
|
15
|
+
N2VhZGJjYWE1MzgxYjY4ZDQwNWI4YmE5ZGY0OWNjZjk4YmNjMGI=
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
## 2.0.0
|
6
|
+
|
7
|
+
- **Breaking change**: Rename to `ModelAttribute` (no trailing 's') to avoid name
|
8
|
+
clash with another gem.
|
9
|
+
|
10
|
+
## 1.4.0
|
11
|
+
|
12
|
+
- **New method**: #changes_for_json Returns a hash from attribute name to its
|
13
|
+
new value, suitable for serialization to a JSON string. Easily generate the
|
14
|
+
payload to send in an HTTP PUT to a web service.
|
15
|
+
|
16
|
+
- **New attribute type: json** Store an array/hash/etc. built using the basic
|
17
|
+
JSON data types: nil, numeric, string, boolean, hash and array.
|
18
|
+
|
19
|
+
## 1.3.0
|
20
|
+
|
21
|
+
- **Breaking change**: Parsing an integer to a time attribute, the integer is
|
22
|
+
treated as the number of milliseconds since the epoch (not the number of
|
23
|
+
seconds). `attributes_as_json` emits integers for time attributes.
|
24
|
+
|
25
|
+
## 1.2.0
|
26
|
+
|
27
|
+
- **Breaking change**: `attributes_as_json` removed; replaced with
|
28
|
+
`attributes_for_json`. You will have to serialize this yourself:
|
29
|
+
`Oj.dump(attributes_for_json, mode: :strict)`. This allows you to modify the
|
30
|
+
returned hash before serializing it.
|
31
|
+
|
32
|
+
## 1.1.0
|
33
|
+
|
34
|
+
- Initial release
|
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec feature)
|
6
|
+
|
7
|
+
## Uncomment to clear the screen before every task
|
8
|
+
# clearing :on
|
9
|
+
|
10
|
+
## Make Guard exit when config is changed so it can be restarted
|
11
|
+
#
|
12
|
+
## Note: if you want Guard to automatically start up again, run guard in a
|
13
|
+
## shell loop, e.g.:
|
14
|
+
#
|
15
|
+
# $ while bundle exec guard; do echo "Restarting Guard..."; done
|
16
|
+
#
|
17
|
+
## Note: if you are using the `directories` clause above and you are not
|
18
|
+
## watching the project directory ('.'), the you will want to move the Guardfile
|
19
|
+
## to a watched dir and symlink it back, e.g.
|
20
|
+
#
|
21
|
+
# $ mkdir config
|
22
|
+
# $ mv Guardfile config/
|
23
|
+
# $ ln -s config/Guardfile .
|
24
|
+
#
|
25
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
26
|
+
#
|
27
|
+
watch ("Guardfile") do
|
28
|
+
UI.info "Exiting because Guard must be restarted for changes to take effect"
|
29
|
+
exit 0
|
30
|
+
end
|
31
|
+
|
32
|
+
guard :rspec, cmd: "bundle exec rspec --format=Nc --format=documentation" do
|
33
|
+
require "guard/rspec/dsl"
|
34
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
35
|
+
|
36
|
+
# RSpec files
|
37
|
+
rspec = dsl.rspec
|
38
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
39
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
40
|
+
watch(rspec.spec_files)
|
41
|
+
|
42
|
+
# Ruby files
|
43
|
+
ruby = dsl.ruby
|
44
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
45
|
+
|
46
|
+
watch(%r{lib/*}) { 'spec' }
|
47
|
+
end
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,170 @@
|
|
1
1
|
# ModelAttribute
|
2
2
|
|
3
|
-
|
3
|
+
Simple attributes for a non-ActiveRecord model.
|
4
|
+
|
5
|
+
- Stores attributes in instance variables.
|
6
|
+
- Type casting and checking.
|
7
|
+
- Dirty tracking.
|
8
|
+
- List attribute names and values.
|
9
|
+
- Handles integers, booleans, strings and times - a set of types that are very
|
10
|
+
easy to persist to and parse from JSON.
|
11
|
+
- Supports efficient serialization of attributes to JSON.
|
12
|
+
- Mass assignment - handy for initializers.
|
13
|
+
|
14
|
+
Why not [Virtus][virtus-gem]? Virtus doesn't provide attribute tracking, and
|
15
|
+
doesn't integrate with [ActiveModel::Dirty][am-dirty]. So if you're not using
|
16
|
+
ActiveRecord, but you need attributes with dirty tracking, ModelAttribute may be
|
17
|
+
what you're after. For example, it works very well for a model that fronts an
|
18
|
+
HTTP web service, and you want dirty tracking so you can PATCH appropriately.
|
19
|
+
|
20
|
+
Also in favor of ModelAttribute:
|
21
|
+
|
22
|
+
- It's simple - less than [200 lines of code][source].
|
23
|
+
- It supports efficient serialization and deserialization to/from JSON.
|
24
|
+
|
25
|
+
[virtus-gem]:https://github.com/solnic/virtus
|
26
|
+
[am-dirty]:https://github.com/rails/rails/blob/v3.0.20/activemodel/lib/active_model/dirty.rb
|
27
|
+
[source]:https://github.com/yammer/model_attribute/blob/master/lib/model_attribute.rb
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'model_attribute'
|
33
|
+
class User
|
34
|
+
extend ModelAttribute
|
35
|
+
attribute :id, :integer
|
36
|
+
attribute :paid, :boolean
|
37
|
+
attribute :name, :string
|
38
|
+
attribute :created_at, :time
|
39
|
+
attribute :grades, :json
|
40
|
+
|
41
|
+
def initialize(attributes = {})
|
42
|
+
set_attributes(attributes)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
User.attributes # => [:id, :paid, :name, :created_at, :grades]
|
47
|
+
user = User.new
|
48
|
+
|
49
|
+
user.attributes # => {:id=>nil, :paid=>nil, :name=>nil, :created_at=>nil, :grades=>nil}
|
50
|
+
|
51
|
+
# An integer attribute
|
52
|
+
user.id # => nil
|
53
|
+
|
54
|
+
user.id = 3
|
55
|
+
user.id # => 3
|
56
|
+
|
57
|
+
# Stores values that convert cleanly to an integer
|
58
|
+
user.id = '5'
|
59
|
+
user.id # => 5
|
60
|
+
|
61
|
+
# Protects you against nonsense assignment
|
62
|
+
user.id = '5error'
|
63
|
+
ArgumentError: invalid value for Integer(): "5error"
|
64
|
+
|
65
|
+
# A boolean attribute
|
66
|
+
user.paid # => nil
|
67
|
+
user.paid = true
|
68
|
+
|
69
|
+
# Booleans also define a predicate method (ending in '?')
|
70
|
+
user.paid? # => true
|
71
|
+
|
72
|
+
# Conversion from strings used by databases.
|
73
|
+
user.paid = 'f'
|
74
|
+
user.paid # => false
|
75
|
+
user.paid = 't'
|
76
|
+
user.paid # => true
|
77
|
+
|
78
|
+
# A :time attribute
|
79
|
+
user.created_at = Time.now
|
80
|
+
user.created_at # => 2015-01-08 15:57:05 +0000
|
81
|
+
|
82
|
+
# Also converts from other reasonable time formats
|
83
|
+
user.created_at = "2014-12-25 14:00:00 +0100"
|
84
|
+
user.created_at # => 2014-12-25 13:00:00 +0000
|
85
|
+
user.created_at = Date.parse('2014-01-08')
|
86
|
+
user.created_at # => 2014-01-08 00:00:00 +0000
|
87
|
+
user.created_at = DateTime.parse("2014-12-25 13:00:45")
|
88
|
+
user.created_at # => 2014-12-25 13:00:45 +0000
|
89
|
+
# Convert from seconds since the epoch
|
90
|
+
user.created_at = Time.now.to_f
|
91
|
+
user.created_at # => 2015-01-08 16:23:02 +0000
|
92
|
+
# Or milliseconds since the epoch
|
93
|
+
user.created_at = 1420734182000
|
94
|
+
user.created_at # => 2015-01-08 16:23:02 +0000
|
95
|
+
|
96
|
+
# A :json attribute is schemaless and accepts the basic JSON types - hash,
|
97
|
+
# array, nil, numeric, string and boolean.
|
98
|
+
user.grades = {'maths' => 'A', 'history' => 'C'}
|
99
|
+
user.grades # => {"maths"=>"A", "history"=>"C"}
|
100
|
+
user.grades = ['A', 'A*', 'C']
|
101
|
+
user.grades # => ["A", "A*", "C"]
|
102
|
+
user.grades = 'AAB'
|
103
|
+
user.grades # => "AAB"
|
104
|
+
user.grades = Time.now
|
105
|
+
# => RuntimeError: JSON only supports nil, numeric, string, boolean and arrays and hashes of those.
|
106
|
+
|
107
|
+
# read_attribute and write_attribute methods
|
108
|
+
user.read_attribute(:created_at)
|
109
|
+
user.write_attribute(:name, 'Fred')
|
110
|
+
|
111
|
+
# View attributes
|
112
|
+
user.attributes # => {:id=>5, :paid=>true, :name=>"Fred", :created_at=>2015-01-08 15:57:05 +0000, :grades=>{"maths"=>"A", "history"=>"C"}}
|
113
|
+
user.inspect # => "#<User id: 5, paid: true, name: \"Fred\", created_at: 2015-01-08 15:57:05 +0000, grades: {\"maths\"=>\"A\", \"history\"=>\"C\"}>"
|
114
|
+
|
115
|
+
# Mass assignment
|
116
|
+
user.set_attributes(name: "Sally", paid: false)
|
117
|
+
user.attributes # => {:id=>5, :paid=>false, :name=>"Sally", :created_at=>2015-01-08 15:57:05 +0000}
|
118
|
+
|
119
|
+
# Efficient JSON serialization and deserialization.
|
120
|
+
# Attributes with nil values are omitted.
|
121
|
+
user.attributes_for_json
|
122
|
+
# => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
|
123
|
+
require 'oj'
|
124
|
+
Oj.dump(user.attributes_for_json, mode: :strict)
|
125
|
+
# => "{\"id\":5,\"paid\":true,\"name\":\"Fred\",\"created_at\":1421171317762}"
|
126
|
+
user2 = User.new(Oj.load(json, strict: true))
|
127
|
+
|
128
|
+
# Change tracking. A much smaller set of function than that provided by
|
129
|
+
# ActiveModel::Dity.
|
130
|
+
user.changes # => {:id=>[nil, 5], :paid=>[nil, true], :created_at=>[nil, 2015-01-08 15:57:05 +0000], :name=>[nil, "Fred"]}
|
131
|
+
user.name_changed? # => true
|
132
|
+
# If you need the new values to send as a PUT to a web service
|
133
|
+
user.changes_for_json # => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
|
134
|
+
# If you're imitating ActiveRecord behaviour, changes are cleared after
|
135
|
+
# after_save callbacks, but before after_commit callbacks.
|
136
|
+
user.changes.clear
|
137
|
+
user.changes # => {}
|
138
|
+
|
139
|
+
# Equality of all the attribute values match
|
140
|
+
another = User.new
|
141
|
+
another.id = 5
|
142
|
+
another.paid = true
|
143
|
+
another.created_at = user.created_at
|
144
|
+
another.name = 'Fred'
|
145
|
+
|
146
|
+
user == another # => true
|
147
|
+
user === another # => true
|
148
|
+
user.eql? another # => true
|
149
|
+
|
150
|
+
# Making some attributes private
|
151
|
+
|
152
|
+
class User
|
153
|
+
extend ModelAttribute
|
154
|
+
attribute :events, :string
|
155
|
+
private :events=
|
156
|
+
|
157
|
+
def initialize(attributes)
|
158
|
+
# Pass flag to set_attributes to allow setting attributes with private writers
|
159
|
+
set_attributes(attributes, true)
|
160
|
+
end
|
161
|
+
|
162
|
+
def add_event(new_event)
|
163
|
+
events ||= ""
|
164
|
+
events += new_event
|
165
|
+
end
|
166
|
+
end
|
167
|
+
```
|
4
168
|
|
5
169
|
## Installation
|
6
170
|
|
@@ -18,10 +182,6 @@ Or install it yourself as:
|
|
18
182
|
|
19
183
|
$ gem install model_attribute
|
20
184
|
|
21
|
-
## Usage
|
22
|
-
|
23
|
-
TODO: Write usage instructions here
|
24
|
-
|
25
185
|
## Contributing
|
26
186
|
|
27
187
|
1. Fork it ( https://github.com/[my-github-username]/model_attribute/fork )
|
data/Rakefile
CHANGED
data/lib/model_attribute.rb
CHANGED
@@ -1,5 +1,202 @@
|
|
1
1
|
require "model_attribute/version"
|
2
|
+
require "model_attribute/json"
|
3
|
+
require "model_attribute/errors"
|
4
|
+
require "time"
|
2
5
|
|
3
6
|
module ModelAttribute
|
4
|
-
|
7
|
+
SUPPORTED_TYPES = [:integer, :boolean, :string, :time, :json]
|
8
|
+
|
9
|
+
def self.extended(base)
|
10
|
+
base.send(:include, InstanceMethods)
|
11
|
+
base.instance_variable_set('@attribute_names', [])
|
12
|
+
base.instance_variable_set('@attribute_types', {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def attribute(name, type)
|
16
|
+
name = name.to_sym
|
17
|
+
type = type.to_sym
|
18
|
+
raise UnsupportedTypeError.new(type) unless SUPPORTED_TYPES.include?(type)
|
19
|
+
|
20
|
+
@attribute_names << name
|
21
|
+
@attribute_types[name] = type
|
22
|
+
|
23
|
+
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
24
|
+
def #{name}=(value)
|
25
|
+
write_attribute(#{name.inspect}, value, #{type.inspect})
|
26
|
+
end
|
27
|
+
|
28
|
+
def #{name}
|
29
|
+
read_attribute(#{name.inspect})
|
30
|
+
end
|
31
|
+
|
32
|
+
def #{name}_changed?
|
33
|
+
!!changes[#{name.inspect}]
|
34
|
+
end
|
35
|
+
CODE
|
36
|
+
|
37
|
+
if type == :boolean
|
38
|
+
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
39
|
+
def #{name}?
|
40
|
+
!!read_attribute(#{name.inspect})
|
41
|
+
end
|
42
|
+
CODE
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def attributes
|
47
|
+
@attribute_names
|
48
|
+
end
|
49
|
+
|
50
|
+
module InstanceMethods
|
51
|
+
def write_attribute(name, value, type = nil)
|
52
|
+
name = name.to_sym
|
53
|
+
|
54
|
+
# Don't want to expose attribute types as a method on the class, so access
|
55
|
+
# via a back door.
|
56
|
+
type ||= self.class.instance_variable_get('@attribute_types')[name]
|
57
|
+
raise InvalidAttributeNameError.new(name) unless type
|
58
|
+
|
59
|
+
value = cast(value, type)
|
60
|
+
return if value == read_attribute(name)
|
61
|
+
|
62
|
+
if changes.has_key? name
|
63
|
+
original = changes[name].first
|
64
|
+
else
|
65
|
+
original = read_attribute(name)
|
66
|
+
end
|
67
|
+
|
68
|
+
if original == value
|
69
|
+
changes.delete(name)
|
70
|
+
else
|
71
|
+
changes[name] = [original, value]
|
72
|
+
end
|
73
|
+
|
74
|
+
instance_variable_set("@#{name}", value)
|
75
|
+
end
|
76
|
+
|
77
|
+
def read_attribute(name)
|
78
|
+
ivar_name = "@#{name}"
|
79
|
+
if instance_variable_defined?(ivar_name)
|
80
|
+
instance_variable_get(ivar_name)
|
81
|
+
elsif !self.class.attributes.include?(name.to_sym)
|
82
|
+
raise InvalidAttributeNameError.new(name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes
|
87
|
+
self.class.attributes.each_with_object({}) do |name, attributes|
|
88
|
+
attributes[name] = read_attribute(name)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_attributes(attributes, can_set_private_attrs = false)
|
93
|
+
attributes.each do |key, value|
|
94
|
+
send("#{key}=", value) if respond_to?("#{key}=", can_set_private_attrs)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def ==(other)
|
99
|
+
return true if equal?(other)
|
100
|
+
if respond_to?(:id)
|
101
|
+
other.kind_of?(self.class) && id == other.id
|
102
|
+
else
|
103
|
+
other.kind_of?(self.class) && attributes == other.attributes
|
104
|
+
end
|
105
|
+
end
|
106
|
+
alias_method :eql?, :==
|
107
|
+
|
108
|
+
def changes
|
109
|
+
@changes ||= {} #HashWithIndifferentAccess.new
|
110
|
+
end
|
111
|
+
|
112
|
+
# Attributes suitable for serializing to a JSON string.
|
113
|
+
#
|
114
|
+
# - Attribute keys are strings (for 'strict' JSON dumping).
|
115
|
+
# - Attributes with a nil value are omitted to speed serialization.
|
116
|
+
# - :time attributes are serialized as an Integer giving the number of
|
117
|
+
# milliseconds since the epoch.
|
118
|
+
def attributes_for_json
|
119
|
+
self.class.attributes.each_with_object({}) do |name, attributes|
|
120
|
+
value = read_attribute(name)
|
121
|
+
unless value.nil?
|
122
|
+
value = (value.to_f * 1000).to_i if value.is_a? Time
|
123
|
+
attributes[name.to_s] = value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Changed attributes suitable for serializing to a JSON string. Returns a
|
129
|
+
# hash from attribute name (as a string) to the new value of that attribute,
|
130
|
+
# for attributes that have changed.
|
131
|
+
#
|
132
|
+
# - :time attributes are serialized as an Integer giving the number of
|
133
|
+
# milliseconds since the epoch.
|
134
|
+
# - Unlike attributes_for_json, attributes that have changed to a nil value
|
135
|
+
# *are* included.
|
136
|
+
def changes_for_json
|
137
|
+
hash = {}
|
138
|
+
changes.each do |attr_name, (_old_value, new_value)|
|
139
|
+
new_value = (new_value.to_f * 1000).to_i if new_value.is_a? Time
|
140
|
+
hash[attr_name.to_s] = new_value
|
141
|
+
end
|
142
|
+
|
143
|
+
hash
|
144
|
+
end
|
145
|
+
|
146
|
+
# Includes the class name and all the attributes and their values. e.g.
|
147
|
+
# "#<User id: 1, paid: true, name: \"Fred\", created_at: 2014-12-25 08:00:00 +0000>"
|
148
|
+
def inspect
|
149
|
+
attribute_string = self.class.attributes.map do |key|
|
150
|
+
"#{key}: #{read_attribute(key).inspect}"
|
151
|
+
end.join(', ')
|
152
|
+
"#<#{self.class} #{attribute_string}>"
|
153
|
+
end
|
154
|
+
|
155
|
+
def cast(value, type)
|
156
|
+
return nil if value.nil?
|
157
|
+
|
158
|
+
case type
|
159
|
+
when :integer
|
160
|
+
int = Integer(value)
|
161
|
+
float = Float(value)
|
162
|
+
raise "Can't cast #{value.inspect} to an integer without loss of precision" unless int == float
|
163
|
+
int
|
164
|
+
when :boolean
|
165
|
+
if !!value == value
|
166
|
+
value
|
167
|
+
elsif value == 't'
|
168
|
+
true
|
169
|
+
elsif value == 'f'
|
170
|
+
false
|
171
|
+
else
|
172
|
+
raise "Can't cast #{value.inspect} to boolean"
|
173
|
+
end
|
174
|
+
when :time
|
175
|
+
case value
|
176
|
+
when Time
|
177
|
+
value
|
178
|
+
when Date, DateTime
|
179
|
+
value.to_time
|
180
|
+
when Integer
|
181
|
+
# Assume milliseconds since epoch.
|
182
|
+
Time.at(value / 1000.0)
|
183
|
+
when Numeric
|
184
|
+
# Numeric, but not an integer. Assume seconds since epoch.
|
185
|
+
Time.at(value)
|
186
|
+
else
|
187
|
+
Time.parse(value)
|
188
|
+
end
|
189
|
+
when :string
|
190
|
+
String(value)
|
191
|
+
when :json
|
192
|
+
if Json.valid?(value)
|
193
|
+
value
|
194
|
+
else
|
195
|
+
raise "JSON only supports nil, numeric, string, boolean and arrays and hashes of those."
|
196
|
+
end
|
197
|
+
else
|
198
|
+
raise UnsupportedTypeError.new(type)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
5
202
|
end
|