hcast 0.0.1

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.
@@ -0,0 +1,4 @@
1
+ vendor/
2
+ tags
3
+ .bundle
4
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 2.0.0
3
+ script: "bundle exec rspec spec/"
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'debugger'
8
+ end
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ hcast (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ columnize (0.3.6)
10
+ debugger (1.6.5)
11
+ columnize (>= 0.3.1)
12
+ debugger-linecache (~> 1.2.0)
13
+ debugger-ruby_core_source (~> 1.3.1)
14
+ debugger-linecache (1.2.0)
15
+ debugger-ruby_core_source (1.3.1)
16
+ diff-lcs (1.2.5)
17
+ rake (10.1.1)
18
+ rspec (2.14.1)
19
+ rspec-core (~> 2.14.0)
20
+ rspec-expectations (~> 2.14.0)
21
+ rspec-mocks (~> 2.14.0)
22
+ rspec-core (2.14.7)
23
+ rspec-expectations (2.14.4)
24
+ diff-lcs (>= 1.1.3, < 2.0)
25
+ rspec-mocks (2.14.4)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ bundler (~> 1.3)
32
+ debugger
33
+ hcast!
34
+ rake
35
+ rspec
@@ -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.
@@ -0,0 +1,113 @@
1
+ # HCast [![Build Status](https://travis-ci.org/AlbertGazizov/hcast.png)](https://travis-ci.org/AlbertGazizov/hcast) [![Code Climate](https://codeclimate.com/github/AlbertGazizov/hcast.png)](https://codeclimate.com/github/AlbertGazizov/hcast)
2
+
3
+
4
+
5
+ HCast is a library for casting hash attributes
6
+
7
+ ## Usage
8
+
9
+ Create caster class and declare hash attributes inside:
10
+ ```ruby
11
+ class ContactCaster
12
+ include HCast::Caster
13
+
14
+ attributes do
15
+ hash :contact do
16
+ string :name
17
+ integer :age, optional: true
18
+ float :weight
19
+ date :birthday
20
+ datetime :last_logged_in
21
+ time :last_visited_at
22
+ hash :company do
23
+ string :name
24
+ end
25
+ array :emails, each: :string
26
+ array :social_accounts, each: :hash do
27
+ string :name
28
+ symbol :type
29
+ end
30
+ end
31
+ end
32
+ end
33
+ ```
34
+ Instanticate the caster and give your hash for casting:
35
+ ```ruby
36
+ ContactCaster.new.cast({
37
+ contact: {
38
+ name: "John Smith",
39
+ age: "22",
40
+ weight: "65.5",
41
+ birthday: "2014-02-02",
42
+ last_logged_in: "2014-02-02 10:10:00",
43
+ last_visited_at: "2014-02-02 10:10:00",
44
+ company: {
45
+ name: "MyCo"
46
+ },
47
+ emails: ["test@example.com", "test2@example.com"],
48
+ social_accounts: [
49
+ {
50
+ name: "john_smith",
51
+ type: "twitter"
52
+ },
53
+ {
54
+ name: "John",
55
+ type: :facebook
56
+ }
57
+ ]
58
+ }
59
+ }
60
+ })
61
+ ```
62
+ The casted will cast your hash and output will be:
63
+ ```ruby
64
+ {
65
+ contact: {
66
+ name: "John Smith",
67
+ age: 22,
68
+ weight: 65.5,
69
+ birthday: #<Date: 2014-02-02 ((2456691j,0s,0n),+0s,2299161j)>,
70
+ last_logged_in: #<DateTime: 2014-02-02T10:10:00+00:00 ((2456691j,36600s,0n),+0s,2299161j)>,
71
+ last_visited_at: 2014-02-02 10:10:00 +0400,
72
+ company: {
73
+ name: "MyCo"
74
+ },
75
+ emails: ["test@example.com", "test2@example.com"],
76
+ social_accounts: [
77
+ {
78
+ name: "john_smith",
79
+ type: :twitter"
80
+ },
81
+ {
82
+ name: "John",
83
+ type: :facebook
84
+ }
85
+ ]
86
+ }
87
+ }
88
+ ```
89
+
90
+ if some of the attributes can't be casted the HCast::Errors::CastingError will be raised
91
+
92
+
93
+ ## Installation
94
+
95
+ Add this line to your application's Gemfile:
96
+
97
+ gem 'hcast'
98
+
99
+ And then execute:
100
+
101
+ $ bundle
102
+
103
+ Or install it yourself as:
104
+
105
+ $ gem install hcast
106
+
107
+ ## Contributing
108
+
109
+ 1. Fork it
110
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
111
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
112
+ 4. Push to the branch (`git push origin my-new-feature`)
113
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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 = "hcast"
8
+ spec.version = HCast::VERSION
9
+ spec.authors = ["Albert Gazizov"]
10
+ spec.email = ["deeper4k@gmail.com"]
11
+ spec.description = %q{Hash Caster and Validator}
12
+ spec.summary = %q{Hash Caster and Validator}
13
+ spec.homepage = "http://github.com/AlbertGazizov/hcast"
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", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,46 @@
1
+ require 'hcast/version'
2
+ require 'hcast/errors'
3
+ require 'hcast/config'
4
+ require 'hcast/casters'
5
+ require 'hcast/concern.rb'
6
+ require 'hcast/metadata/attribute'
7
+ require 'hcast/attributes_parser'
8
+ require 'hcast/attributes_caster'
9
+ require 'hcast/caster'
10
+
11
+ module HCast
12
+ @@casters = {}
13
+
14
+ # Defines caster without adding own class
15
+ # @note Not yet implemented
16
+ def self.create(&block)
17
+ end
18
+
19
+ # Returns list of defined casters
20
+ def self.casters
21
+ @@casters
22
+ end
23
+
24
+ # Adds new casters to HCast
25
+ # Allow extend HCast with your own casters
26
+ # @param caster_name [Symbol] caster name
27
+ # @param caster [Class] caster
28
+ def self.add_caster(caster_name, caster)
29
+ @@casters[caster_name] = caster
30
+ end
31
+
32
+ def self.config
33
+ @@config ||= HCast::Config.new
34
+ end
35
+ end
36
+
37
+ HCast.add_caster(:array, HCast::Casters::ArrayCaster)
38
+ HCast.add_caster(:boolean, HCast::Casters::BooleanCaster)
39
+ HCast.add_caster(:date, HCast::Casters::DateCaster)
40
+ HCast.add_caster(:datetime, HCast::Casters::DateTimeCaster)
41
+ HCast.add_caster(:float, HCast::Casters::FloatCaster)
42
+ HCast.add_caster(:hash, HCast::Casters::HashCaster)
43
+ HCast.add_caster(:integer, HCast::Casters::IntegerCaster)
44
+ HCast.add_caster(:string, HCast::Casters::StringCaster)
45
+ HCast.add_caster(:symbol, HCast::Casters::SymbolCaster)
46
+ HCast.add_caster(:time, HCast::Casters::TimeCaster)
@@ -0,0 +1,83 @@
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)
10
+ casted_hash = {}
11
+
12
+ hash_keys = get_keys(input_hash)
13
+ attributes.each do |attribute|
14
+ if hash_keys.include?(attribute.name)
15
+ casted_value = cast_attribute(attribute, input_hash)
16
+ casted_hash[attribute.name] = casted_value
17
+ else
18
+ raise HCast::Errors::MissingAttributeError, "#{attribute.name} should be given" if attribute.required?
19
+ end
20
+ end
21
+ check_unexpected_attributes_not_given!(hash_keys, casted_hash.keys)
22
+
23
+ casted_hash
24
+ end
25
+
26
+ private
27
+
28
+ def cast_attribute(attribute, hash)
29
+ value = get_value(hash, attribute.name)
30
+ casted_value = attribute.caster.cast(value, attribute.name, attribute.options)
31
+
32
+ if attribute.has_children?
33
+ cast_children(hash, attribute)
34
+ else
35
+ casted_value
36
+ end
37
+ end
38
+
39
+ def cast_children(hash, attribute)
40
+ value = get_value(hash, attribute.name)
41
+ if attribute.caster == HCast::Casters::ArrayCaster
42
+ value.map do |val|
43
+ caster = self.class.new(attribute.children, options)
44
+ caster.cast(val)
45
+ end
46
+ else
47
+ caster = self.class.new(attribute.children, options)
48
+ caster.cast(value)
49
+ end
50
+ end
51
+
52
+ def get_keys(hash)
53
+ if options[:input_keys] != options[:output_keys]
54
+ if options[:input_keys] == :symbol
55
+ hash.keys.map(&:to_s)
56
+ else
57
+ hash.keys.map(&:to_sym)
58
+ end
59
+ else
60
+ hash.keys
61
+ end
62
+ end
63
+
64
+ def get_value(hash, key)
65
+ if options[:input_keys] != options[:output_keys]
66
+ if options[:input_keys] == :symbol
67
+ hash[key.to_sym]
68
+ else
69
+ hash[key.to_s]
70
+ end
71
+ else
72
+ hash[key]
73
+ end
74
+ end
75
+
76
+ def check_unexpected_attributes_not_given!(input_hash_keys, casted_hash_keys)
77
+ unexpected_keys = input_hash_keys - casted_hash_keys
78
+ unless unexpected_keys.empty?
79
+ raise HCast::Errors::UnexpectedAttributeError, "Unexpected attributes given: #{unexpected_keys}"
80
+ end
81
+ end
82
+
83
+ 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)
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)
46
+ if !caster
47
+ raise HCast::Errors::CasterNotFoundError, "caster with name '#{caster}' 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
@@ -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