hcast 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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