hash_cast 0.4.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/.github/workflows/rspec.yml +34 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +22 -0
- data/README.md +93 -0
- data/Rakefile +1 -0
- data/hcast.gemspec +23 -0
- data/lib/hcast/attributes_caster.rb +102 -0
- data/lib/hcast/attributes_parser.rb +63 -0
- data/lib/hcast/caster.rb +142 -0
- data/lib/hcast/casters/array_caster.rb +32 -0
- data/lib/hcast/casters/boolean_caster.rb +15 -0
- data/lib/hcast/casters/date_caster.rb +19 -0
- data/lib/hcast/casters/datetime_caster.rb +19 -0
- data/lib/hcast/casters/float_caster.rb +17 -0
- data/lib/hcast/casters/hash_caster.rb +11 -0
- data/lib/hcast/casters/integer_caster.rb +17 -0
- data/lib/hcast/casters/string_caster.rb +13 -0
- data/lib/hcast/casters/symbol_caster.rb +18 -0
- data/lib/hcast/casters/time_caster.rb +19 -0
- data/lib/hcast/casters.rb +13 -0
- data/lib/hcast/concern.rb +136 -0
- data/lib/hcast/config.rb +11 -0
- data/lib/hcast/errors.rb +67 -0
- data/lib/hcast/metadata/attribute.rb +30 -0
- data/lib/hcast/version.rb +3 -0
- data/lib/hcast.rb +46 -0
- data/spec/hcast/caster_spec.rb +384 -0
- data/spec/hcast/hcast_spec.rb +37 -0
- data/spec/spec_helper.rb +6 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 71b3bcf7f98727c99228d5fb0ceaba02b87767721c251ba69d5c91079f936b5a
|
4
|
+
data.tar.gz: 1cd37fcce0009d5895dc72a4af32bd0c2dd2ea6ce5abee97b7e6b190a33c1655
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e607f921e463af6ff705ad3cfd86c9ca5532d7813913f6c795bfec73879b02cc4a8101b44ab9f475f45576ccbadd8b47d5d71e57ca3565b98813e4a3e5c6427c
|
7
|
+
data.tar.gz: a7297e7a81c4165dcda83135131e68817e29c043fd20594f5ce3ec9ab8b37eef07afd16bd9d606bf2a6ab0a8bb041e0cf17067ddc0b46212749e56b2a55afe34
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Rspec
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
strategy:
|
21
|
+
matrix:
|
22
|
+
ruby-version: ['3.1', '3.3']
|
23
|
+
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v2
|
26
|
+
- name: Set up Ruby
|
27
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
28
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
29
|
+
uses: ruby/setup-ruby@v1
|
30
|
+
with:
|
31
|
+
ruby-version: ${{ matrix.ruby-version }}
|
32
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
33
|
+
- name: Run tests
|
34
|
+
run: bundle exec rspec spec/
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
hash_caster
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3.4
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
hash_cast (0.4.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.5.1)
|
10
|
+
rake (10.1.1)
|
11
|
+
rspec (3.13.0)
|
12
|
+
rspec-core (~> 3.13.0)
|
13
|
+
rspec-expectations (~> 3.13.0)
|
14
|
+
rspec-mocks (~> 3.13.0)
|
15
|
+
rspec-core (3.13.2)
|
16
|
+
rspec-support (~> 3.13.0)
|
17
|
+
rspec-expectations (3.13.3)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.13.0)
|
20
|
+
rspec-mocks (3.13.2)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.13.0)
|
23
|
+
rspec-support (3.13.1)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler
|
30
|
+
hash_cast!
|
31
|
+
rake
|
32
|
+
rspec
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
2.5.22
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Albert Gazizov
|
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,93 @@
|
|
1
|
+
# HCast
|
2
|
+
[](https://github.com/droidlabs/hcast/actions?query=workflow%3ARspec)
|
3
|
+
|
4
|
+
HCast is a library for casting hash attributes
|
5
|
+
|
6
|
+
### Usage
|
7
|
+
|
8
|
+
Create caster class and declare hash attributes inside:
|
9
|
+
```ruby
|
10
|
+
class ContactCaster
|
11
|
+
include HCast::Caster
|
12
|
+
|
13
|
+
attributes do
|
14
|
+
hash :contact do
|
15
|
+
string :name
|
16
|
+
integer :age, optional: true
|
17
|
+
float :weight
|
18
|
+
date :birthday
|
19
|
+
datetime :last_logged_in
|
20
|
+
time :last_visited_at
|
21
|
+
hash :company do
|
22
|
+
string :name
|
23
|
+
end
|
24
|
+
array :emails, each: :string
|
25
|
+
array :social_accounts, each: :hash do
|
26
|
+
string :name
|
27
|
+
symbol :type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
Instanticate the caster and give your hash for casting:
|
34
|
+
```ruby
|
35
|
+
ContactCaster.cast({
|
36
|
+
contact: {
|
37
|
+
name: "John Smith",
|
38
|
+
age: "22",
|
39
|
+
weight: "65.5",
|
40
|
+
birthday: "2014-02-02",
|
41
|
+
last_logged_in: "2014-02-02 10:10:00",
|
42
|
+
last_visited_at: "2014-02-02 10:10:00",
|
43
|
+
company: {
|
44
|
+
name: "MyCo"
|
45
|
+
},
|
46
|
+
emails: ["test@example.com", "test2@example.com"],
|
47
|
+
social_accounts: [
|
48
|
+
{
|
49
|
+
name: "john_smith",
|
50
|
+
type: "twitter"
|
51
|
+
},
|
52
|
+
{
|
53
|
+
name: "John",
|
54
|
+
type: :facebook
|
55
|
+
}
|
56
|
+
]
|
57
|
+
}
|
58
|
+
}
|
59
|
+
})
|
60
|
+
```
|
61
|
+
The caster will cast your hash attributes to:
|
62
|
+
```ruby
|
63
|
+
{
|
64
|
+
contact: {
|
65
|
+
name: "John Smith",
|
66
|
+
age: 22,
|
67
|
+
weight: 65.5,
|
68
|
+
birthday: #<Date: 2014-02-02 ((2456691j,0s,0n),+0s,2299161j)>,
|
69
|
+
last_logged_in: #<DateTime: 2014-02-02T10:10:00+00:00 ((2456691j,36600s,0n),+0s,2299161j)>,
|
70
|
+
last_visited_at: 2014-02-02 10:10:00 +0400,
|
71
|
+
company: {
|
72
|
+
name: "MyCo"
|
73
|
+
},
|
74
|
+
emails: ["test@example.com", "test2@example.com"],
|
75
|
+
social_accounts: [
|
76
|
+
{
|
77
|
+
name: "john_smith",
|
78
|
+
type: :twitter"
|
79
|
+
},
|
80
|
+
{
|
81
|
+
name: "John",
|
82
|
+
type: :facebook
|
83
|
+
}
|
84
|
+
]
|
85
|
+
}
|
86
|
+
}
|
87
|
+
```
|
88
|
+
|
89
|
+
if some of the attributes can't be casted the HCast::Errors::CastingError is raised
|
90
|
+
|
91
|
+
|
92
|
+
## Author
|
93
|
+
Albert Gazizov, [@deeper4k](https://twitter.com/deeper4k)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/hcast.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hcast/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hash_cast"
|
8
|
+
spec.version = HCast::VERSION
|
9
|
+
spec.authors = ["Albert Gazizov"]
|
10
|
+
spec.email = ["deeper4k@gmail.com"]
|
11
|
+
spec.description = %q{Declarative Hash Caster}
|
12
|
+
spec.summary = %q{Declarative Hash Caster}
|
13
|
+
spec.homepage = "http://github.com/droidlabs/hash_cast"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class HCast::AttributesCaster
|
2
|
+
attr_reader :attributes, :options
|
3
|
+
|
4
|
+
def initialize(attributes, options)
|
5
|
+
@attributes = attributes
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def cast(input_hash, opts = {})
|
10
|
+
casted_hash = {}
|
11
|
+
|
12
|
+
hash_keys = get_keys(input_hash)
|
13
|
+
attributes.each do |attribute|
|
14
|
+
if hash_keys.include?(attribute.name)
|
15
|
+
begin
|
16
|
+
casted_value = cast_attribute(attribute, input_hash)
|
17
|
+
casted_hash[attribute.name] = casted_value
|
18
|
+
rescue HCast::Errors::AttributeError => e
|
19
|
+
handle_attribute_error(e, attribute)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
raise HCast::Errors::MissingAttributeError.new("should be given", attribute.name)if attribute.required?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if !options[:skip_unexpected_attributes]
|
27
|
+
check_unexpected_attributes_not_given!(hash_keys, casted_hash.keys)
|
28
|
+
end
|
29
|
+
|
30
|
+
casted_hash
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def handle_attribute_error(exception, attribute)
|
36
|
+
exception.add_namespace(attribute.name)
|
37
|
+
raise exception
|
38
|
+
end
|
39
|
+
|
40
|
+
def cast_attribute(attribute, hash)
|
41
|
+
value = get_value(hash, attribute.name)
|
42
|
+
if value.nil? && attribute.allow_nil?
|
43
|
+
nil
|
44
|
+
else
|
45
|
+
casted_value = attribute.caster.cast(value, attribute.name, attribute.options)
|
46
|
+
if attribute.has_children?
|
47
|
+
cast_children(casted_value, attribute)
|
48
|
+
elsif caster = attribute.options[:caster]
|
49
|
+
cast_children_with_caster(casted_value, attribute, caster)
|
50
|
+
else
|
51
|
+
casted_value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def cast_children(value, attribute)
|
57
|
+
caster = self.class.new(attribute.children, options)
|
58
|
+
cast_children_with_caster(value, attribute, caster)
|
59
|
+
end
|
60
|
+
|
61
|
+
def cast_children_with_caster(value, attribute, caster)
|
62
|
+
if attribute.caster == HCast::Casters::ArrayCaster
|
63
|
+
value.map do |val|
|
64
|
+
caster.cast(val)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
caster.cast(value)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_keys(hash)
|
72
|
+
if options[:input_keys] != options[:output_keys]
|
73
|
+
if options[:input_keys] == :symbol
|
74
|
+
hash.keys.map(&:to_s)
|
75
|
+
else
|
76
|
+
hash.keys.map(&:to_sym)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
hash.keys
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_value(hash, key)
|
84
|
+
if options[:input_keys] != options[:output_keys]
|
85
|
+
if options[:input_keys] == :symbol
|
86
|
+
hash[key.to_sym]
|
87
|
+
else
|
88
|
+
hash[key.to_s]
|
89
|
+
end
|
90
|
+
else
|
91
|
+
hash[key]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def check_unexpected_attributes_not_given!(input_hash_keys, casted_hash_keys)
|
96
|
+
unexpected_keys = input_hash_keys - casted_hash_keys
|
97
|
+
unless unexpected_keys.empty?
|
98
|
+
raise HCast::Errors::UnexpectedAttributeError.new("is not valid attribute name", unexpected_keys.first)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Parses caster rules
|
2
|
+
# and returns list of HCast::Metadata::Attribute instances
|
3
|
+
# which contains casting rules
|
4
|
+
class HCast::AttributesParser
|
5
|
+
|
6
|
+
# Performs casting
|
7
|
+
# @param block [Proc] block with casting rules
|
8
|
+
# @return Array(HCast::Metadata::Attribute) list of casting rules
|
9
|
+
def self.parse(&block)
|
10
|
+
dsl = DSL.new
|
11
|
+
dsl.instance_exec(&block)
|
12
|
+
dsl.attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
class DSL
|
16
|
+
attr_reader :attributes
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@attributes = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Redefined becase each class has the built in hash method
|
23
|
+
def hash(*args, &block)
|
24
|
+
method_missing(:hash, *args, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(caster_name, *args, &block)
|
28
|
+
attr_name = args[0]
|
29
|
+
options = args[1] || {}
|
30
|
+
caster = HCast.casters[caster_name]
|
31
|
+
|
32
|
+
check_caster_exists!(caster, caster_name)
|
33
|
+
check_attr_name_valid!(attr_name)
|
34
|
+
check_options_is_hash!(options)
|
35
|
+
|
36
|
+
attribute = HCast::Metadata::Attribute.new(attr_name, caster, options)
|
37
|
+
if block_given?
|
38
|
+
attribute.children = HCast::AttributesParser.parse(&block)
|
39
|
+
end
|
40
|
+
attributes << attribute
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def check_caster_exists!(caster, caster_name)
|
46
|
+
if !caster
|
47
|
+
raise HCast::Errors::CasterNotFoundError, "caster with name '#{caster_name}' is not found"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_attr_name_valid!(attr_name)
|
52
|
+
if !attr_name.is_a?(Symbol) && !attr_name.is_a?(String)
|
53
|
+
raise HCast::Errors::ArgumentError, "attribute name should be a symbol or string"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_options_is_hash!(options)
|
58
|
+
if !options.is_a?(Hash)
|
59
|
+
raise HCast::Errors::ArgumentError, "attribute options should be a Hash"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/hcast/caster.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# Include this module to create your caster
|
2
|
+
#
|
3
|
+
# Example caster:
|
4
|
+
# class ContactCaster
|
5
|
+
# include HCast::Caster
|
6
|
+
#
|
7
|
+
# attributes do
|
8
|
+
# hash :contact do
|
9
|
+
# string :name
|
10
|
+
# integer :age, optional: true
|
11
|
+
# float :weight
|
12
|
+
# date :birthday
|
13
|
+
# datetime :last_logged_in
|
14
|
+
# time :last_visited_at
|
15
|
+
# hash :company do
|
16
|
+
# string :name
|
17
|
+
# end
|
18
|
+
# array :emails, each: :string
|
19
|
+
# array :social_accounts, each: :hash do
|
20
|
+
# string :name
|
21
|
+
# symbol :type
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# The defined caster will have #cast method which accepts hash
|
28
|
+
# Use it to cast hash:
|
29
|
+
# ContactCaster.new.cast({
|
30
|
+
# contact: {
|
31
|
+
# name: "John Smith",
|
32
|
+
# age: "22",
|
33
|
+
# weight: "65.5",
|
34
|
+
# birthday: "2014-02-02",
|
35
|
+
# last_logged_in: "2014-02-02 10:10:00",
|
36
|
+
# last_visited_at: "2014-02-02 10:10:00",
|
37
|
+
# company: {
|
38
|
+
# name: "MyCo",
|
39
|
+
# },
|
40
|
+
# emails: [ "test@example.com", "test2@example.com" ],
|
41
|
+
# social_accounts: [
|
42
|
+
# {
|
43
|
+
# name: "john_smith",
|
44
|
+
# type: 'twitter',
|
45
|
+
# },
|
46
|
+
# {
|
47
|
+
# name: "John",
|
48
|
+
# type: :facebook,
|
49
|
+
# },
|
50
|
+
# ]
|
51
|
+
# }
|
52
|
+
# })
|
53
|
+
#
|
54
|
+
# The output will be casted hash:
|
55
|
+
# {
|
56
|
+
# contact: {
|
57
|
+
# name: "John Smith",
|
58
|
+
# age: 22,
|
59
|
+
# weight: 65.5,
|
60
|
+
# birthday: Date.parse("2014-02-02"),
|
61
|
+
# last_logged_in: DateTime.parse("2014-02-02 10:10:00"),
|
62
|
+
# last_visited_at: Time.parse("2014-02-02 10:10:00"),
|
63
|
+
# company: {
|
64
|
+
# name: "MyCo",
|
65
|
+
# },
|
66
|
+
# emails: [ "test@example.com", "test2@example.com" ],
|
67
|
+
# social_accounts: [
|
68
|
+
# {
|
69
|
+
# name: "john_smith",
|
70
|
+
# type: :twitter,
|
71
|
+
# },
|
72
|
+
# {
|
73
|
+
# name: "John",
|
74
|
+
# type: :facebook,
|
75
|
+
# },
|
76
|
+
# ]
|
77
|
+
# }
|
78
|
+
# }
|
79
|
+
module HCast::Caster
|
80
|
+
extend HCast::Concern
|
81
|
+
|
82
|
+
module ClassMethods
|
83
|
+
|
84
|
+
# Defines casting rules
|
85
|
+
# @example
|
86
|
+
# attributes do
|
87
|
+
# string :first_name
|
88
|
+
# string :last_name
|
89
|
+
# integer :age, optional: true
|
90
|
+
# end
|
91
|
+
def attributes(&block)
|
92
|
+
raise ArgumentError, "You should provide block" unless block_given?
|
93
|
+
|
94
|
+
attributes = HCast::AttributesParser.parse(&block)
|
95
|
+
self.class_variable_set(:@@attributes, attributes)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Performs casting
|
99
|
+
# @param hash [Hash] hash for casting
|
100
|
+
# @param options [Hash] options, input_keys: :string, output_key: :symbol
|
101
|
+
def cast(hash, options = {})
|
102
|
+
check_attributes_defined!
|
103
|
+
check_hash_given!(hash)
|
104
|
+
check_options!(options)
|
105
|
+
set_default_options(options)
|
106
|
+
|
107
|
+
attributes_caster = HCast::AttributesCaster.new(class_variable_get(:@@attributes), options)
|
108
|
+
attributes_caster.cast(hash)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def check_attributes_defined!
|
114
|
+
unless class_variable_defined?(:@@attributes)
|
115
|
+
raise HCast::Errors::ArgumentError, "Attributes block should be defined"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def check_options!(options)
|
120
|
+
unless options.is_a?(Hash)
|
121
|
+
raise HCast::Errors::ArgumentError, "Options should be a hash"
|
122
|
+
end
|
123
|
+
if options[:input_keys] && ![:string, :symbol].include?(options[:input_keys])
|
124
|
+
raise HCast::Errors::ArgumentError, "input_keys should be :string or :symbol"
|
125
|
+
end
|
126
|
+
if options[:output_keys] && ![:string, :symbol].include?(options[:output_keys])
|
127
|
+
raise HCast::Errors::ArgumentError, "output_keys should be :string or :symbol"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def check_hash_given!(hash)
|
132
|
+
unless hash.is_a?(Hash)
|
133
|
+
raise HCast::Errors::ArgumentError, "Hash should be given"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_default_options(options)
|
138
|
+
options[:input_keys] ||= HCast.config.input_keys
|
139
|
+
options[:output_keys] ||= HCast.config.output_keys
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class HCast::Casters::ArrayCaster
|
2
|
+
|
3
|
+
def self.cast(value, attr_name, options = {})
|
4
|
+
if value.is_a?(Array)
|
5
|
+
if options[:each]
|
6
|
+
cast_array_items(value, attr_name, options)
|
7
|
+
else
|
8
|
+
value
|
9
|
+
end
|
10
|
+
else
|
11
|
+
raise HCast::Errors::CastingError, "should be an array"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.cast_array_items(array, attr_name, options)
|
18
|
+
caster_name = options[:each]
|
19
|
+
caster = HCast.casters[caster_name]
|
20
|
+
check_caster_exists!(caster, caster_name)
|
21
|
+
array.map do |item|
|
22
|
+
caster.cast(item, "#{attr_name} item", options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.check_caster_exists!(caster, caster_name)
|
27
|
+
unless caster
|
28
|
+
raise HCast::Errors::CasterNotFoundError, "caster with name #{caster_name} is not found"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class HCast::Casters::BooleanCaster
|
2
|
+
|
3
|
+
def self.cast(value, attr_name, options = {})
|
4
|
+
if [TrueClass, FalseClass].include?(value.class)
|
5
|
+
value
|
6
|
+
elsif ['1', 'true', 'on', 1].include?(value)
|
7
|
+
true
|
8
|
+
elsif ['0', 'false', 'off', 0].include?(value)
|
9
|
+
false
|
10
|
+
else
|
11
|
+
raise HCast::Errors::CastingError, "should be a boolean"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class HCast::Casters::DateCaster
|
4
|
+
|
5
|
+
def self.cast(value, attr_name, options = {})
|
6
|
+
if value.is_a?(Date)
|
7
|
+
value
|
8
|
+
elsif value.is_a?(String)
|
9
|
+
begin
|
10
|
+
Date.parse(value)
|
11
|
+
rescue ArgumentError => e
|
12
|
+
raise HCast::Errors::CastingError, "is invalid date"
|
13
|
+
end
|
14
|
+
else
|
15
|
+
raise HCast::Errors::CastingError, "should be a date"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class HCast::Casters::DateTimeCaster
|
2
|
+
|
3
|
+
def self.cast(value, attr_name, options = {})
|
4
|
+
if value.is_a?(DateTime)
|
5
|
+
value
|
6
|
+
elsif value.is_a?(Time)
|
7
|
+
value.to_datetime
|
8
|
+
elsif value.is_a?(String)
|
9
|
+
begin
|
10
|
+
DateTime.parse(value)
|
11
|
+
rescue ArgumentError => e
|
12
|
+
raise HCast::Errors::CastingError, "is invalid datetime"
|
13
|
+
end
|
14
|
+
else
|
15
|
+
raise HCast::Errors::CastingError, "should be a datetime"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class HCast::Casters::FloatCaster
|
2
|
+
|
3
|
+
def self.cast(value, attr_name, options = {})
|
4
|
+
if value.is_a?(Float)
|
5
|
+
value
|
6
|
+
elsif value.is_a?(String)
|
7
|
+
begin
|
8
|
+
Float(value)
|
9
|
+
rescue ArgumentError => e
|
10
|
+
raise HCast::Errors::CastingError, "is invalid float"
|
11
|
+
end
|
12
|
+
else
|
13
|
+
raise HCast::Errors::CastingError, "should be a float"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class HCast::Casters::IntegerCaster
|
2
|
+
|
3
|
+
def self.cast(value, attr_name, options = {})
|
4
|
+
if value.is_a?(Integer)
|
5
|
+
value
|
6
|
+
elsif value.is_a?(String)
|
7
|
+
begin
|
8
|
+
Integer(value)
|
9
|
+
rescue ArgumentError => e
|
10
|
+
raise HCast::Errors::CastingError, "is invalid integer"
|
11
|
+
end
|
12
|
+
else
|
13
|
+
raise HCast::Errors::CastingError, "should be a integer"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|