morf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 946b8a0940f00a90e751212769a15915f69d0ea4
4
+ data.tar.gz: 0c5bc2171fcfc37ab7af826cfa5fb7a5e6decf0e
5
+ SHA512:
6
+ metadata.gz: b14c86eb318d49676e10f343d569a8ae5254fb092c7cf0415b02bbf588a31e1e0a231cae94425a4e711448773f081b86e1b72a628b80ac5f46ee6bd2bc879e59
7
+ data.tar.gz: 2f693a3dd1720b810093bfc7657ef1a6b26973fed1fed2f636d13ba98d5bc3752a2512da5d9e32402babc8cf2debf53e9f4c94eb7c8e7ab4c26aca760e051ca9
@@ -0,0 +1,6 @@
1
+ vendor/
2
+ tags
3
+ .bundle
4
+ .DS_Store
5
+ tmp/
6
+ pkg
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 2.0.0
3
+ script: "bundle exec rspec spec/"
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ end
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ v (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (1.4.4)
10
+ diff-lcs (1.3)
11
+ rake (12.3.0)
12
+ rspec (3.7.0)
13
+ rspec-core (~> 3.7.0)
14
+ rspec-expectations (~> 3.7.0)
15
+ rspec-mocks (~> 3.7.0)
16
+ rspec-core (3.7.0)
17
+ rspec-support (~> 3.7.0)
18
+ rspec-expectations (3.7.0)
19
+ diff-lcs (>= 1.2.0, < 2.0)
20
+ rspec-support (~> 3.7.0)
21
+ rspec-mocks (3.7.0)
22
+ diff-lcs (>= 1.2.0, < 2.0)
23
+ rspec-support (~> 3.7.0)
24
+ rspec-support (3.7.0)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ activesupport (~> 1.3)
31
+ bundler (~> 1.3)
32
+ rake
33
+ rspec
34
+ v!
35
+
36
+ BUNDLED WITH
37
+ 1.15.4
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2017 Droid Labs
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 @@
1
+ Readme comming soon.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,46 @@
1
+ require 'morf/version'
2
+ require 'morf/errors'
3
+ require 'morf/config'
4
+ require 'morf/casters'
5
+ require 'morf/concern.rb'
6
+ require 'morf/metadata/attribute'
7
+ require 'morf/attributes_parser'
8
+ require 'morf/attributes_caster'
9
+ require 'morf/caster'
10
+
11
+ module Morf
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 Morf
25
+ # Allow extend Morf 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 ||= Morf::Config.new
34
+ end
35
+ end
36
+
37
+ Morf.add_caster(:array, Morf::Casters::ArrayCaster)
38
+ Morf.add_caster(:boolean, Morf::Casters::BooleanCaster)
39
+ Morf.add_caster(:date, Morf::Casters::DateCaster)
40
+ Morf.add_caster(:datetime, Morf::Casters::DateTimeCaster)
41
+ Morf.add_caster(:float, Morf::Casters::FloatCaster)
42
+ Morf.add_caster(:hash, Morf::Casters::HasMorfer)
43
+ Morf.add_caster(:integer, Morf::Casters::IntegerCaster)
44
+ Morf.add_caster(:string, Morf::Casters::StringCaster)
45
+ Morf.add_caster(:symbol, Morf::Casters::SymbolCaster)
46
+ Morf.add_caster(:time, Morf::Casters::TimeCaster)
@@ -0,0 +1,98 @@
1
+ class Morf::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, options = {})
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 Morf::Errors::AttributeError => e
19
+ e.add_namespace(attribute.name)
20
+ raise e
21
+ end
22
+ else
23
+ raise Morf::Errors::MissingAttributeError.new("should be given", attribute.name) if attribute.required?
24
+ end
25
+ end
26
+
27
+ if !options[:skip_unexpected_attributes]
28
+ check_unexpected_attributes_not_given!(hash_keys, casted_hash.keys)
29
+ end
30
+
31
+ casted_hash
32
+ end
33
+
34
+ private
35
+
36
+ def cast_attribute(attribute, hash)
37
+ value = get_value(hash, attribute.name)
38
+ if value.nil? && attribute.allow_nil?
39
+ nil
40
+ else
41
+ casted_value = attribute.caster.cast(value, attribute.name, attribute.options)
42
+ if attribute.has_children?
43
+ cast_children(casted_value, attribute)
44
+ elsif caster = attribute.options[:caster]
45
+ cast_children_with_caster(casted_value, attribute, caster)
46
+ else
47
+ casted_value
48
+ end
49
+ end
50
+ end
51
+
52
+ def cast_children(value, attribute)
53
+ caster = self.class.new(attribute.children, options)
54
+ cast_children_with_caster(value, attribute, caster)
55
+ end
56
+
57
+ def cast_children_with_caster(value, attribute, caster)
58
+ if attribute.caster == Morf::Casters::ArrayCaster
59
+ value.map do |val|
60
+ caster.cast(val, options)
61
+ end
62
+ else
63
+ caster.cast(value, options)
64
+ end
65
+ end
66
+
67
+ def get_keys(hash)
68
+ if options[:input_keys] != options[:output_keys]
69
+ if options[:input_keys] == :symbol
70
+ hash.keys.map(&:to_s)
71
+ else
72
+ hash.keys.map(&:to_sym)
73
+ end
74
+ else
75
+ hash.keys
76
+ end
77
+ end
78
+
79
+ def get_value(hash, key)
80
+ if options[:input_keys] != options[:output_keys]
81
+ if options[:input_keys] == :symbol
82
+ hash[key.to_sym]
83
+ else
84
+ hash[key.to_s]
85
+ end
86
+ else
87
+ hash[key]
88
+ end
89
+ end
90
+
91
+ def check_unexpected_attributes_not_given!(input_hash_keys, casted_hash_keys)
92
+ unexpected_keys = input_hash_keys - casted_hash_keys
93
+ unless unexpected_keys.empty?
94
+ raise Morf::Errors::UnexpectedAttributeError.new("is not valid attribute name", unexpected_keys.first)
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,63 @@
1
+ # Parses caster rules
2
+ # and returns list of Morf::Metadata::Attribute instances
3
+ # which contains casting rules
4
+ class Morf::AttributesParser
5
+
6
+ # Performs casting
7
+ # @param block [Proc] block with casting rules
8
+ # @return Array(Morf::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 = Morf.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 = Morf::Metadata::Attribute.new(attr_name, caster, options)
37
+ if block_given?
38
+ attribute.children = Morf::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 Morf::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 Morf::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 Morf::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 Morf::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 Morf::Caster
80
+ extend Morf::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 = Morf::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 = Morf::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 Morf::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 Morf::Errors::ArgumentError, "Options should be a hash"
122
+ end
123
+ if options[:input_keys] && ![:string, :symbol].include?(options[:input_keys])
124
+ raise Morf::Errors::ArgumentError, "input_keys should be :string or :symbol"
125
+ end
126
+ if options[:output_keys] && ![:string, :symbol].include?(options[:output_keys])
127
+ raise Morf::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 Morf::Errors::ArgumentError, "Hash should be given"
134
+ end
135
+ end
136
+
137
+ def set_default_options(options)
138
+ options[:input_keys] ||= Morf.config.input_keys
139
+ options[:output_keys] ||= Morf.config.output_keys
140
+ end
141
+ end
142
+ end