active_model_serializer_plus 1.0.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.
- checksums.yaml +7 -0
- data/.yardopts +3 -0
- data/Appraisals +3 -0
- data/CHANGELOG.md +11 -0
- data/LICENSE.md +22 -0
- data/README.md +142 -0
- data/Rakefile +7 -0
- data/active_model_serializer_plus.gemspec +37 -0
- data/gemfiles/rails_4.2.gemfile +7 -0
- data/lib/active_model_serializer_plus.rb +4 -0
- data/lib/active_model_serializer_plus/assignment.rb +64 -0
- data/lib/active_model_serializer_plus/railtie.rb +7 -0
- data/lib/active_model_serializer_plus/translations.rb +218 -0
- data/lib/active_model_serializer_plus/version.rb +6 -0
- data/test/active_model_serializer_plus/active_model_serializer_plus_test.rb +10 -0
- data/test/active_model_serializer_plus/assignment_test.rb +64 -0
- data/test/active_model_serializer_plus/translations_test.rb +260 -0
- data/test/app/rails_4.2/Rakefile +6 -0
- data/test/app/rails_4.2/app/assets/javascripts/application.js +16 -0
- data/test/app/rails_4.2/app/assets/stylesheets/application.css +15 -0
- data/test/app/rails_4.2/app/controllers/application_controller.rb +5 -0
- data/test/app/rails_4.2/app/helpers/application_helper.rb +2 -0
- data/test/app/rails_4.2/app/views/layouts/application.html.erb +14 -0
- data/test/app/rails_4.2/bin/bundle +3 -0
- data/test/app/rails_4.2/bin/rails +8 -0
- data/test/app/rails_4.2/bin/rake +8 -0
- data/test/app/rails_4.2/bin/setup +29 -0
- data/test/app/rails_4.2/bin/spring +15 -0
- data/test/app/rails_4.2/config.ru +4 -0
- data/test/app/rails_4.2/config/application.rb +26 -0
- data/test/app/rails_4.2/config/boot.rb +3 -0
- data/test/app/rails_4.2/config/database.yml +25 -0
- data/test/app/rails_4.2/config/environment.rb +5 -0
- data/test/app/rails_4.2/config/environments/development.rb +41 -0
- data/test/app/rails_4.2/config/environments/production.rb +79 -0
- data/test/app/rails_4.2/config/environments/test.rb +42 -0
- data/test/app/rails_4.2/config/initializers/assets.rb +11 -0
- data/test/app/rails_4.2/config/initializers/backtrace_silencers.rb +7 -0
- data/test/app/rails_4.2/config/initializers/cookies_serializer.rb +3 -0
- data/test/app/rails_4.2/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/app/rails_4.2/config/initializers/inflections.rb +16 -0
- data/test/app/rails_4.2/config/initializers/mime_types.rb +4 -0
- data/test/app/rails_4.2/config/initializers/session_store.rb +3 -0
- data/test/app/rails_4.2/config/initializers/wrap_parameters.rb +14 -0
- data/test/app/rails_4.2/config/locales/en.yml +23 -0
- data/test/app/rails_4.2/config/routes.rb +56 -0
- data/test/app/rails_4.2/config/secrets.yml +22 -0
- data/test/app/rails_4.2/db/schema.rb +0 -0
- data/test/app/rails_4.2/db/seeds.rb +7 -0
- data/test/app/rails_4.2/public/404.html +67 -0
- data/test/app/rails_4.2/public/422.html +67 -0
- data/test/app/rails_4.2/public/500.html +66 -0
- data/test/app/rails_4.2/public/favicon.ico +0 -0
- data/test/app/rails_4.2/public/robots.txt +5 -0
- data/test/test_helper.rb +20 -0
- metadata +259 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 361857f5e46f0a701cf41f9d43fa7d7f404bf928
|
4
|
+
data.tar.gz: a59df64844aa6b4d4b7421263c8c98cb0104f69e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1f4f2018d56e59590fae7c2facc0160cc7971b55bff6e78069f05a8e26ac2bdabd783e60a4a6ace40d56fb542682d49566bda9ac860c2a4325926b3507aec072
|
7
|
+
data.tar.gz: 13e288511f09e66054680471fa61e51bac872308ac4f46595b46b898be86706ba5cd76a90a0400f9d68618d6954384788c28aec796d24ccd6a25e36d51168edd
|
data/.yardopts
ADDED
data/Appraisals
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Todd Knarr
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# @author Todd Knarr <tknarr@silverglass.org>
|
2
|
+
|
3
|
+
# ActiveModelSerializerPlus
|
4
|
+
|
5
|
+
Enhances the standard ActiveModel::Serializers::JSON and ActiveModel::Serializers::Xml
|
6
|
+
modules by adding a default `#attributes=` method that implements the normal loop used to
|
7
|
+
assign values to attributes. The loop makes use of the `#attribute_types` hash to convert
|
8
|
+
sub-hashes to objects of the right class and to parse strings into values of the right
|
9
|
+
type (eg. to insure a string containing a time value is converted into a Time object).
|
10
|
+
This allows for automatic deserialization of serialized objects without needing to write
|
11
|
+
code for it.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'active_model_serializer_plus'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install active_model_serializer_plus
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
Add the gem's module to your source file:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'active_model_serializer_plus`
|
35
|
+
```
|
36
|
+
|
37
|
+
Then in the class you need to serialize/deserialize you include the Assignment module
|
38
|
+
after including all of the ActiveModel modules you need:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
include ActiveModelSerializerPlus::Assignment
|
42
|
+
```
|
43
|
+
|
44
|
+
The class will need to implement the `#attribute_types` method which should return a hash
|
45
|
+
consisting of attribute names and the name of the type/class of the attribute. You only need
|
46
|
+
to include those attributes that are themselves a serializable class and that you want turned
|
47
|
+
back into objects rather than being left as hashes, or attributes that aren't automatically
|
48
|
+
converted from strings back into the correct type (eg. Date, Time, DateTime).
|
49
|
+
|
50
|
+
This would be an example:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'active_model_serializer_plus'
|
54
|
+
|
55
|
+
class Example
|
56
|
+
include ActiveModel::Model
|
57
|
+
include ActiveModel::Serializers::JSON
|
58
|
+
include ActiveModelSerializerPlus::Assignment
|
59
|
+
|
60
|
+
attr_accessor :integer_field
|
61
|
+
attr_accessor :time_field
|
62
|
+
attr_accessor :string_field
|
63
|
+
attr_accessor :object_field
|
64
|
+
|
65
|
+
def attributes
|
66
|
+
{
|
67
|
+
'integer_field' => nil,
|
68
|
+
'time_field' => nil,
|
69
|
+
'string_field' => nil,
|
70
|
+
'object_field' => nil
|
71
|
+
}
|
72
|
+
|
73
|
+
def attribute_types
|
74
|
+
{
|
75
|
+
'time_field' => 'Time',
|
76
|
+
'object_field' => 'SomeSerializableClass'
|
77
|
+
}
|
78
|
+
|
79
|
+
def initialize( i, t, s, o )
|
80
|
+
{
|
81
|
+
integer_field = i
|
82
|
+
time_field = t
|
83
|
+
string_field = s
|
84
|
+
object_field = o
|
85
|
+
}
|
86
|
+
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Examples of using this class to serialize an object to a JSON string:
|
91
|
+
|
92
|
+
obj = Example.new( 5, Time.now, 'xyzzy abc', SomeSerializableClass.new )
|
93
|
+
json_string = obj.to_json
|
94
|
+
|
95
|
+
json_string now contains:
|
96
|
+
|
97
|
+
{ 'integer_field' => 5, 'time_field' => '"2015-09-26T19:04:07-07:00', 'string_field' => 'xyzzy abc',
|
98
|
+
'object_field' => { ... } }
|
99
|
+
|
100
|
+
And deserializing that string back to an object:
|
101
|
+
|
102
|
+
new_obj = Example.new.from_json( json_string )
|
103
|
+
|
104
|
+
new_obj should now be identical to obj, including having time_field being a Time object and
|
105
|
+
object_field being a SomeSerializableClass object initialized from the hash in 'object_field'.
|
106
|
+
|
107
|
+
For ActiveModel classes the types could have been included in the `#attributes` hash, but that would
|
108
|
+
conflict with the use of `#attributes` in ActiveRecord classes and could cause confusion.
|
109
|
+
|
110
|
+
## Adding information about new types
|
111
|
+
|
112
|
+
In the `translations.rb` file there are some functions defined on the ActiveModelSerializerPlus module
|
113
|
+
itself. The ones you'll probably find useful are `#add_xlate` and `#add_type` which add information about
|
114
|
+
a type to the hashes that control formatting and parsing. You'll need to read the documentation on the hashes
|
115
|
+
themselves for details, but the short form is that `#add_type` takes the name of a type/class, a Proc that
|
116
|
+
takes and object and formats it into a string, a Proc that takes a string and parses it and initializes an
|
117
|
+
object from it, and a Proc that takes a deserialized hash and constructs an object from it. `#add_xlate`
|
118
|
+
takes the name of a type and the name of it's pseudo-parent class and adds an entry to the type name
|
119
|
+
translation table. The lookup routines check for a translation first and see if Procs exist for the
|
120
|
+
translated name (the pseudo-parent type). Most of the time you'd omit any new translations and let the
|
121
|
+
lookup routines walk up the normal class inheritance chain to find the correct formatting and parsing Procs.
|
122
|
+
You'd only fill in translations if you had classes that can be treated as derived from a common class but
|
123
|
+
that don't actually derive from any common class. `TrueClass` and `FalseClass` are a good example. They don't
|
124
|
+
need a formatting class because they already serialize as `true` and `false` which is convenient, but you'll\
|
125
|
+
find a parsing Proc for the pseudo-class `Boolean` that correctly parses both of those strings back to true and
|
126
|
+
false values. To set this up you'd do:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
add_type('Boolean', nil, Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip.downcase) }, nil)
|
130
|
+
add_xlate('TrueClass', 'Boolean')
|
131
|
+
add_xlate('FalseClass', 'Boolean')
|
132
|
+
```
|
133
|
+
|
134
|
+
which would create the parsing proc entry for `Boolean` and add the translation entries so that `TrueClass`
|
135
|
+
and `FalseClass` act as if derived from an imaginary `Boolean` class for formatting and parsing purposes.
|
136
|
+
|
137
|
+
## Contributing
|
138
|
+
|
139
|
+
This project uses `git-flow`, where new features are developed along feature branches based off the
|
140
|
+
`develop` branch rather than `master`. Avoid naming your branches `master`, `develop`, `hotfix-`* or `release-`*
|
141
|
+
as those conflict with standard branches for hotfixes and releases. You should fork and branch off of the
|
142
|
+
`develop` branch instead of `master`, and merge back to `develop` before creating your pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'active_model_serializer_plus/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'active_model_serializer_plus'
|
9
|
+
spec.version = ActiveModelSerializerPlus::VERSION
|
10
|
+
spec.date = Time.now.strftime('%Y-%m-%d')
|
11
|
+
spec.author = 'Todd Knarr'
|
12
|
+
spec.email = 'tknarr@silverglass.org'
|
13
|
+
spec.summary = %q{Enhanced serialization/deserialization support for ActiveModel classes.}
|
14
|
+
spec.description = %q{Adds methods for automatic deserialization from standard JSON and XML, and a variant implementation of serialization/deserialization for XML.}
|
15
|
+
spec.homepage = 'https://github.com/tknarr/active_model_serializer_plus'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = Dir.glob('lib/**/*.rb').select { |path| File.file?(path) } +
|
19
|
+
[ __FILE__ ]
|
20
|
+
spec.test_files = Dir.glob('test/**/*').select { |path| File.file?(path) && !File.fnmatch('*.{log,sqlite3}', path, File::FNM_EXTGLOB) } +
|
21
|
+
Dir.glob('gemfiles/*.gemfile') + [ 'Rakefile', 'Appraisals' ]
|
22
|
+
spec.extra_rdoc_files = [ 'README.md', 'CHANGELOG.md', 'LICENSE.md', '.yardopts' ]
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'rails', '~> 4.2'
|
26
|
+
spec.add_dependency 'activemodel', '~> 4.2'
|
27
|
+
|
28
|
+
# Development
|
29
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
31
|
+
spec.add_development_dependency 'yard', '~> 0'
|
32
|
+
|
33
|
+
# Testing
|
34
|
+
spec.add_development_dependency 'minitest', '~> 5'
|
35
|
+
spec.add_development_dependency 'appraisal', '~> 2'
|
36
|
+
spec.add_development_dependency 'sqlite3', '~> 1'
|
37
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'active_model_serializer_plus/translations'
|
2
|
+
|
3
|
+
module ActiveModelSerializerPlus
|
4
|
+
|
5
|
+
# @author Todd Knarr <tknarr@silverglass.org>
|
6
|
+
#
|
7
|
+
# Default implementation of the <tt>#attributes=</tt> method for ActiveModel classes.
|
8
|
+
#
|
9
|
+
# This is the base of the JSON and Xml modules. It supplies a standard <tt>#attributes=</tt> method to
|
10
|
+
# classes that follows the basic pattern of the custom one you'd normally write and adds the ability
|
11
|
+
# to pick up object type information from the <tt>#attributes_types</tt> hash to convert the hash values of
|
12
|
+
# contained objects into actual objects. This mostly eliminates the need to write custom <tt>#attributes=</tt>
|
13
|
+
# methods for each class with code to check attribute names and initialize objects from child hashes.
|
14
|
+
#
|
15
|
+
# The standard ActiveModel/ActiveRecord serialization/deserialization has some flaws, for instance it
|
16
|
+
# will serialize classes as contained hashes whether or not they have an <tt>#attributes=</tt> method available
|
17
|
+
# to deserialize them. We work around that by having building Procs which can do the correct thing in
|
18
|
+
# many cases.
|
19
|
+
module Assignment
|
20
|
+
|
21
|
+
# The default <tt>#attributes=</tt> method which assigns a hash to the object's attributes.
|
22
|
+
# @param [Hash] hash the hash of attribute names and values
|
23
|
+
# @return [self] self
|
24
|
+
# @raise [ArgumentError] if a value from the @attribute_types hash can't be converted to a Class
|
25
|
+
def attributes=(hash)
|
26
|
+
hash.each do |key, value|
|
27
|
+
# Check #attribute_types for what type this item should be, and if we have an entry
|
28
|
+
# convert it to an actual Class object we can use.
|
29
|
+
klass = nil
|
30
|
+
v = nil
|
31
|
+
v = self.attribute_types[key] if self.respond_to?(:attribute_types) && self.attribute_types.is_a?(Hash)
|
32
|
+
begin
|
33
|
+
klass = ActiveModelSerializerPlus.to_class(v) unless v.nil?
|
34
|
+
rescue ArgumentError
|
35
|
+
raise ArgumentError, "Type #{v.to_s} for attribute #{key.to_s} is not a valid type."
|
36
|
+
end
|
37
|
+
|
38
|
+
unless klass.nil?
|
39
|
+
if value.is_a?(Hash)
|
40
|
+
# If we're looking at a contained object (our value is a hash), see if we have a Proc
|
41
|
+
# to build the object from a hash. If we don't, fall back to creating an object and
|
42
|
+
# using the hash #attributes= method if possible. If all else fails just leave the value
|
43
|
+
v = ActiveModelSerializerPlus.build(klass.name, value)
|
44
|
+
if v.nil? && klass.method_defined?('attributes=')
|
45
|
+
v = klass.new
|
46
|
+
v.attributes = value
|
47
|
+
end
|
48
|
+
value = v unless v.nil?
|
49
|
+
elsif value.is_a?(String)
|
50
|
+
v = ActiveModelSerializerPlus.parse(klass.name, value)
|
51
|
+
value = v if v
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
self.send("#{key}=", value)
|
56
|
+
end
|
57
|
+
|
58
|
+
self
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# @author Todd Knarr <tknarr@silverglass.org>
|
2
|
+
#
|
3
|
+
# Top-level namespace for the ActiveModelSerializerPlus extensions.
|
4
|
+
#
|
5
|
+
# The methods and module variables here aren't commonly used directly by applications, they're
|
6
|
+
# mainly for internal use by the Assignment, JSON and Xml namespaces. You'll want to become familiar
|
7
|
+
# with the contents if you want to extend the set of types/classes handled automatically during
|
8
|
+
# serialization and deserialization.
|
9
|
+
#
|
10
|
+
# Currently the formatting-related functionality is unused. It's included for use in a planned
|
11
|
+
# XML serialization/deserialization extension.
|
12
|
+
module ActiveModelSerializerPlus
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Pseudo-parent-class names for classes that can be handled by a common
|
17
|
+
# Proc even though they don't derive from a common parent class. That includes
|
18
|
+
# container classes, although we don't do anything with them yet.
|
19
|
+
@@type_name_xlate = {
|
20
|
+
'TrueClass' => 'Boolean',
|
21
|
+
'FalseClass' => 'Boolean',
|
22
|
+
'Hash' => 'Container',
|
23
|
+
'Array' => 'Container'
|
24
|
+
}
|
25
|
+
|
26
|
+
# If a class is listed here use the Proc to format it into a string, otherwise just use to_s
|
27
|
+
@@formatting = {
|
28
|
+
'Date' => Proc.new { |date| date.xmlschema },
|
29
|
+
'DateTime' => Proc.new { |datetime| datetime.xmlschema },
|
30
|
+
'Time' => Proc.new { |time| time.xmlschema }
|
31
|
+
}
|
32
|
+
|
33
|
+
# If a class is listed here, use the Proc to parse a string into an object of the right class
|
34
|
+
@@parsing = {
|
35
|
+
'Symbol' => Proc.new { |symbol| symbol.to_sym },
|
36
|
+
'Time' => Proc.new { |time| Time.xmlschema(time) rescue ::Time.parse(time) },
|
37
|
+
'Date' => Proc.new { |date| Date.xmlschema(date) rescue ::Date.parse(date) },
|
38
|
+
'DateTime' => Proc.new { |datetime| DateTime.xmlschema(datetime) rescue ::DateTime.parse(datetime) },
|
39
|
+
'Integer' => Proc.new { |integer| integer.to_i },
|
40
|
+
'Float' => Proc.new { |float| float.to_f },
|
41
|
+
'BigDecimal' => Proc.new { |number| BigDecimal(number) },
|
42
|
+
'Boolean' => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip.downcase) }
|
43
|
+
}
|
44
|
+
|
45
|
+
# If a class is listed here, use the Proc to build it from a Hash, otherwise use the
|
46
|
+
# standard hash initialization method.
|
47
|
+
#
|
48
|
+
# This is used to deal with really unpleasant cases where a class is serialized as a hash but
|
49
|
+
# can't be constructed from a hash or the values in the hash and we can't for whatever reason
|
50
|
+
# insert a new method into the class to do the job.
|
51
|
+
@@building = {
|
52
|
+
'IPAddr' => Proc.new { |addr_hash|
|
53
|
+
fam = addr_hash['family']
|
54
|
+
addr = addr_hash['addr']
|
55
|
+
mask = addr_hash['mask_addr']
|
56
|
+
# Kludgy. IPAddr has no way to build from the hash or it's contents so we depend
|
57
|
+
# on #new accepting an integer address parameter correctly.
|
58
|
+
result = IPAddr.new(addr, fam)
|
59
|
+
# Kludgy. All we have is the integer netmask but we need either the prefix length as an
|
60
|
+
# integer or the netmask as a string of octets. So we use the same #new behavior as above
|
61
|
+
# to take the netmask integer value and convert it to a netmask string through an IPAddr
|
62
|
+
# object.
|
63
|
+
netmask = IPAddr.new(mask,fam).to_s
|
64
|
+
result.mask(netmask)
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
public
|
69
|
+
|
70
|
+
# @!scope class
|
71
|
+
|
72
|
+
# Translate a type/class name to it's psuedo-parent class name if it has one.
|
73
|
+
# @param [String] class_name the name of the class to translate
|
74
|
+
# @return [String] the translated name
|
75
|
+
def self.type_name_xlate(class_name)
|
76
|
+
@@type_name_xlate[class_name]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Format a value into a string using the defined formatting Proc for <tt>value</tt>'s class.
|
80
|
+
# @note If no formatting Proc is defined, <tt>#to_s</tt> is used instead.
|
81
|
+
# @note Checks all parent classes of <tt>value</tt> for Procs.
|
82
|
+
# @param [Object] value the value to format
|
83
|
+
# @return [String] the value formatted into a string
|
84
|
+
def self.format(value)
|
85
|
+
# Check the translation table first for a pseudo-parent, and if we found one check
|
86
|
+
# for it's proc. Then start with the value's class name and work up through it's
|
87
|
+
# parent classes until we find a proc for one. If we can't find a proc, just use
|
88
|
+
# the #to_s method.
|
89
|
+
p = nil
|
90
|
+
n = @@type_name_xlate[value.class.name]
|
91
|
+
p = @@formatting[n] unless n.nil?
|
92
|
+
if p.nil?
|
93
|
+
b = value.class
|
94
|
+
until b.nil? do
|
95
|
+
p = @@formatting[b.name]
|
96
|
+
break unless p.nil?
|
97
|
+
b = b.superclass
|
98
|
+
end
|
99
|
+
end
|
100
|
+
p ? p.call(value) : value.to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
# Parse a string value into an object of the named type if a parsing Proc is defined.
|
104
|
+
# @note Checks all parent classes of <tt>class_name</tt> for Procs.
|
105
|
+
# @param [String] class_name name of the class of object being created from the value
|
106
|
+
# @param [String] value the string containing the formatted value to be parsed
|
107
|
+
# @return [Object] the new object, or nil if no parsing Proc for <tt>class_name</tt> is defined
|
108
|
+
def self.parse(class_name, value)
|
109
|
+
# Check for a proc for the type name given, and if we find one use it. Otherwise
|
110
|
+
# try to convert that name to a class. If we can, start with it and work up through
|
111
|
+
# it's parent classes checking for a proc for each. If we can't find a proc, return
|
112
|
+
# nil.
|
113
|
+
p = nil
|
114
|
+
xlt = @@type_name_xlate[class_name] || class_name
|
115
|
+
p = @@parsing[xlt] unless xlt.nil?
|
116
|
+
if p.nil?
|
117
|
+
klass = nil
|
118
|
+
begin
|
119
|
+
klass = class_name.constantize
|
120
|
+
rescue NameError
|
121
|
+
klass = nil
|
122
|
+
end
|
123
|
+
until klass.nil?
|
124
|
+
p = @@parsing[klass.name]
|
125
|
+
break unless p.nil?
|
126
|
+
klass = klass.superclass
|
127
|
+
end
|
128
|
+
end
|
129
|
+
p ? p.call(value.to_s) : nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# Build an object of the named type from a hash if a building Proc is defined.
|
133
|
+
# @param [String] class_name name of the class of object being built from the hash
|
134
|
+
# @param [Hash] hash hash containing the attribute names and values from which the object is to be built
|
135
|
+
# @return [Object] the new object, or nil if no building Proc for <tt>class_name</tt> is defined
|
136
|
+
def self.build(class_name, hash)
|
137
|
+
p = nil
|
138
|
+
xlt = @@type_name_xlate[class_name] || class_name
|
139
|
+
p = @@building[xlt] unless xlt.nil?
|
140
|
+
p ? p.call(hash) : nil
|
141
|
+
end
|
142
|
+
|
143
|
+
# Add a new type translation.
|
144
|
+
# Translations are used to create pseudo-parent classes in cases where several classes can use common
|
145
|
+
# Procs for formatting, parsing and/or building but don't share a common parent class, eg. TrueClass
|
146
|
+
# and FalseClass which can both use the Boolean formatting and parsing Procs.
|
147
|
+
# @param [String] type_name name of the specific type
|
148
|
+
# @param [String] parent_type_name name of the pseudo-parent type
|
149
|
+
# @return [void] no return value
|
150
|
+
def self.add_xlate( type_name, parent_type_name )
|
151
|
+
return if type_name.blank?
|
152
|
+
@@type_name_xlate[type_name] = parent_type_name unless parent_type_name.blank?
|
153
|
+
return
|
154
|
+
end
|
155
|
+
|
156
|
+
# Add information about a new type to the formatting/parsing/building hashes.
|
157
|
+
# <tt>type_name</tt> is required, all others are optional and should be specified as <tt>nil</tt>
|
158
|
+
# if not being defined.
|
159
|
+
# @param [String] type_name name of the type to add
|
160
|
+
# @param [Proc] formatting_proc Proc to add to format an object's value into a string
|
161
|
+
# @param [Proc] parsing_proc Proc to add to parse a string value and create an object from it
|
162
|
+
# @param [Proc] building_proc Proc to add to create an object from a hash of attribute names and values
|
163
|
+
# @return [void] no return value
|
164
|
+
def self.add_type(type_name, formatting_proc = nil, parsing_proc = nil, building_proc = nil)
|
165
|
+
return if type_name.blank?
|
166
|
+
@@formatting[type_name] = formatting_proc unless formatting_proc.nil?
|
167
|
+
@@parsing[type_name] = parsing_proc unless parsing_proc.nil?
|
168
|
+
@@building[type_name] = building_proc unless building_proc.nil?
|
169
|
+
return
|
170
|
+
end
|
171
|
+
|
172
|
+
# Helper method to convert a type/class name into an actual class.
|
173
|
+
# @param [Class, Symbol, String] class_name name to convert into a Class object
|
174
|
+
# @return [Class] Class object for the named class
|
175
|
+
# @raise [ArgumentError] if <tt>class_name</tt> is not of an acceptable type
|
176
|
+
# @raise [NameError] if <tt>class_name</tt> is a Class that doesn't exist
|
177
|
+
def self.to_class(class_name)
|
178
|
+
klass = nil
|
179
|
+
if class_name.is_a?(Class)
|
180
|
+
klass = class_name
|
181
|
+
elsif class_name.is_a?(String)
|
182
|
+
begin
|
183
|
+
klass = class_name.constantize unless class_name.blank?
|
184
|
+
rescue NameError
|
185
|
+
raise ArgumentError, "Type #{class_name} is invalid"
|
186
|
+
end
|
187
|
+
elsif class_name.is_a?(Symbol)
|
188
|
+
begin
|
189
|
+
klass = class_name.to_s.constantize
|
190
|
+
rescue NameError
|
191
|
+
raise ArgumentError, "Type #{class_name.to_s} is invalid"
|
192
|
+
end
|
193
|
+
else
|
194
|
+
raise ArgumentError, "Type #{class_name.to_s} is invalid"
|
195
|
+
end
|
196
|
+
klass
|
197
|
+
end
|
198
|
+
|
199
|
+
# Helper method to convert a representation of a class or class name into a string containing the class name.
|
200
|
+
# @param [Class, Symbol, String] class_name the Class whose name you need or a symbol or string representing a class name
|
201
|
+
# @return [String] the class name represented by <tt>class_name</tt> converted to a string
|
202
|
+
# @raise [ArgumentError] if <tt>class_name</tt> is not of an acceptable type
|
203
|
+
# @raise [NameError] if <tt>class_name</tt> is a Class that doesn't exist
|
204
|
+
def self.to_classname(class_name)
|
205
|
+
klassname = nil
|
206
|
+
if class_name.is_a?(Class)
|
207
|
+
klassname = class_name.name
|
208
|
+
elsif class_name.is_a?(String)
|
209
|
+
klassname = class_name
|
210
|
+
elsif class_name.is_a?(Symbol)
|
211
|
+
klassname = class_name.to_s
|
212
|
+
else
|
213
|
+
raise ArgumentError, "Argument type #{class_name.class.name} is not Class, String or Symbol"
|
214
|
+
end
|
215
|
+
klassname
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|