morf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ # List of build in casters
2
+ module Morf::Casters
3
+ require 'morf/casters/array_caster'
4
+ require 'morf/casters/boolean_caster'
5
+ require 'morf/casters/date_caster'
6
+ require 'morf/casters/datetime_caster'
7
+ require 'morf/casters/float_caster'
8
+ require 'morf/casters/hash_caster'
9
+ require 'morf/casters/integer_caster'
10
+ require 'morf/casters/string_caster'
11
+ require 'morf/casters/symbol_caster'
12
+ require 'morf/casters/time_caster'
13
+ end
@@ -0,0 +1,30 @@
1
+ class Morf::Casters::ArrayCaster
2
+ def self.cast(value, attr_name, options = {})
3
+ if value.is_a?(Array)
4
+ if options[:each]
5
+ cast_array_items(value, attr_name, options)
6
+ else
7
+ value
8
+ end
9
+ else
10
+ raise Morf::Errors::CastingError, "should be an array"
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def self.cast_array_items(array, attr_name, options)
17
+ caster_name = options[:each]
18
+ caster = Morf.casters[caster_name]
19
+ check_caster_exists!(caster, caster_name)
20
+ array.map do |item|
21
+ caster.cast(item, "#{attr_name} item", options)
22
+ end
23
+ end
24
+
25
+ def self.check_caster_exists!(caster, caster_name)
26
+ unless caster
27
+ raise Morf::Errors::CasterNotFoundError, "caster with name #{caster_name} is not found"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ class Morf::Casters::BooleanCaster
2
+ def self.cast(value, attr_name, options = {})
3
+ if [TrueClass, FalseClass].include?(value.class)
4
+ value
5
+ elsif ['1', 'true', 'on', 1].include?(value)
6
+ true
7
+ elsif ['0', 'false', 'off', 0].include?(value)
8
+ false
9
+ else
10
+ raise Morf::Errors::CastingError, "should be a boolean"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ require 'active_support/core_ext/date'
2
+
3
+ class Morf::Casters::DateCaster
4
+ def self.cast(value, attr_name, options = {})
5
+ if value.is_a?(Date)
6
+ value
7
+ elsif value.is_a?(String)
8
+ begin
9
+ Date.parse(value)
10
+ rescue ArgumentError => e
11
+ raise Morf::Errors::CastingError, "is invalid date"
12
+ end
13
+ else
14
+ raise Morf::Errors::CastingError, "should be a date"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ class Morf::Casters::DateTimeCaster
2
+ def self.cast(value, attr_name, options = {})
3
+ if value.is_a?(DateTime)
4
+ value
5
+ elsif value.is_a?(Time)
6
+ value.to_datetime
7
+ elsif value.is_a?(String)
8
+ begin
9
+ DateTime.parse(value)
10
+ rescue ArgumentError => e
11
+ raise Morf::Errors::CastingError, "is invalid datetime"
12
+ end
13
+ else
14
+ raise Morf::Errors::CastingError, "should be a datetime"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ class Morf::Casters::FloatCaster
2
+ def self.cast(value, attr_name, options = {})
3
+ if value.is_a?(Float)
4
+ value
5
+ elsif value.is_a?(String)
6
+ begin
7
+ Float(value)
8
+ rescue ArgumentError => e
9
+ raise Morf::Errors::CastingError, "is invalid float"
10
+ end
11
+ else
12
+ raise Morf::Errors::CastingError, "should be a float"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ class Morf::Casters::HasMorfer
2
+ def self.cast(value, attr_name, options = {})
3
+ if value.is_a?(Hash)
4
+ value
5
+ else
6
+ raise Morf::Errors::CastingError, "should be a hash"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ class Morf::Casters::IntegerCaster
2
+ def self.cast(value, attr_name, options = {})
3
+ if value.is_a?(Integer)
4
+ value
5
+ elsif value.is_a?(String)
6
+ begin
7
+ Integer(value)
8
+ rescue ArgumentError => e
9
+ raise Morf::Errors::CastingError, "is invalid integer"
10
+ end
11
+ else
12
+ raise Morf::Errors::CastingError, "should be a integer"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ class Morf::Casters::StringCaster
2
+ def self.cast(value, attr_name, options = {})
3
+ if value.is_a?(String)
4
+ value
5
+ elsif value.is_a?(Symbol)
6
+ value.to_s
7
+ else
8
+ raise Morf::Errors::CastingError, "should be a string"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ class Morf::Casters::SymbolCaster
2
+ MAX_SYMBOL_LENGTH = 1000
3
+
4
+ def self.cast(value, attr_name, options = {})
5
+ if value.is_a?(Symbol)
6
+ value
7
+ elsif value.is_a?(String)
8
+ if value.length > MAX_SYMBOL_LENGTH
9
+ raise Morf::Errors::CastingError, "is too long to be a symbol"
10
+ else
11
+ value.to_sym
12
+ end
13
+ else
14
+ raise Morf::Errors::CastingError, "should be a symbol"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'time'
2
+
3
+ class Morf::Casters::TimeCaster
4
+ def self.cast(value, attr_name, options = {})
5
+ if value.is_a?(Time)
6
+ value
7
+ elsif value.is_a?(String)
8
+ begin
9
+ Time.parse(value)
10
+ rescue ArgumentError => e
11
+ raise Morf::Errors::CastingError, "is invalid time"
12
+ end
13
+ else
14
+ raise Morf::Errors::CastingError, "should be a time"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,136 @@
1
+ module Morf
2
+ # Copied from here https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
3
+ #
4
+ # A typical module looks like this:
5
+ #
6
+ # module M
7
+ # def self.included(base)
8
+ # base.extend ClassMethods
9
+ # base.class_eval do
10
+ # scope :disabled, -> { where(disabled: true) }
11
+ # end
12
+ # end
13
+ #
14
+ # module ClassMethods
15
+ # ...
16
+ # end
17
+ # end
18
+ #
19
+ # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
20
+ # written as:
21
+ #
22
+ # require 'active_support/concern'
23
+ #
24
+ # module M
25
+ # extend ActiveSupport::Concern
26
+ #
27
+ # included do
28
+ # scope :disabled, -> { where(disabled: true) }
29
+ # end
30
+ #
31
+ # module ClassMethods
32
+ # ...
33
+ # end
34
+ # end
35
+ #
36
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
37
+ # and a +Bar+ module which depends on the former, we would typically write the
38
+ # following:
39
+ #
40
+ # module Foo
41
+ # def self.included(base)
42
+ # base.class_eval do
43
+ # def self.method_injected_by_foo
44
+ # ...
45
+ # end
46
+ # end
47
+ # end
48
+ # end
49
+ #
50
+ # module Bar
51
+ # def self.included(base)
52
+ # base.method_injected_by_foo
53
+ # end
54
+ # end
55
+ #
56
+ # class Host
57
+ # include Foo # We need to include this dependency for Bar
58
+ # include Bar # Bar is the module that Host really needs
59
+ # end
60
+ #
61
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
62
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
63
+ #
64
+ # module Bar
65
+ # include Foo
66
+ # def self.included(base)
67
+ # base.method_injected_by_foo
68
+ # end
69
+ # end
70
+ #
71
+ # class Host
72
+ # include Bar
73
+ # end
74
+ #
75
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
76
+ # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
77
+ # module dependencies are properly resolved:
78
+ #
79
+ # require 'active_support/concern'
80
+ #
81
+ # module Foo
82
+ # extend ActiveSupport::Concern
83
+ # included do
84
+ # def self.method_injected_by_foo
85
+ # ...
86
+ # end
87
+ # end
88
+ # end
89
+ #
90
+ # module Bar
91
+ # extend ActiveSupport::Concern
92
+ # include Foo
93
+ #
94
+ # included do
95
+ # self.method_injected_by_foo
96
+ # end
97
+ # end
98
+ #
99
+ # class Host
100
+ # include Bar # works, Bar takes care now of its dependencies
101
+ # end
102
+ module Concern
103
+ class MultipleIncludedBlocks < StandardError #:nodoc:
104
+ def initialize
105
+ super "Cannot define multiple 'included' blocks for a Concern"
106
+ end
107
+ end
108
+
109
+ def self.extended(base) #:nodoc:
110
+ base.instance_variable_set(:@_dependencies, [])
111
+ end
112
+
113
+ def append_features(base)
114
+ if base.instance_variable_defined?(:@_dependencies)
115
+ base.instance_variable_get(:@_dependencies) << self
116
+ return false
117
+ else
118
+ return false if base < self
119
+ @_dependencies.each { |dep| base.send(:include, dep) }
120
+ super
121
+ base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
122
+ base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
123
+ end
124
+ end
125
+
126
+ def included(base = nil, &block)
127
+ if base.nil?
128
+ raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
129
+
130
+ @_included_block = block
131
+ else
132
+ super
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,11 @@
1
+ class Morf::Config
2
+ attr_accessor :input_keys, :output_keys
3
+
4
+ def input_keys
5
+ @input_keys || :symbol
6
+ end
7
+
8
+ def output_keys
9
+ @output_keys || :symbol
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ module Morf::Errors
2
+
3
+ # Base error class for all Morf errors
4
+ class MorfError < StandardError; end
5
+
6
+ # Raised when caster with given name is not registered in Morf
7
+ class CasterNotFoundError < MorfError; end
8
+
9
+ # Raised when some of the given to Morf argument is not valid
10
+ class ArgumentError < MorfError; end
11
+
12
+ class AttributeError < MorfError
13
+ attr_reader :namespaces
14
+
15
+ def initialize(message, namespace = nil)
16
+ super(message)
17
+ @namespaces = []
18
+ @namespaces << namespace if namespace
19
+ end
20
+
21
+ def add_namespace(namespace)
22
+ namespaces << namespace
23
+ end
24
+
25
+ def message
26
+ to_s
27
+ end
28
+
29
+ def to_s
30
+ if namespaces.empty?
31
+ super
32
+ else
33
+ reverted_namespaces = namespaces.reverse
34
+ msg = reverted_namespaces.first.to_s
35
+ msg += reverted_namespaces[1..-1].inject("") { |res, item| res += "[#{item}]"}
36
+ msg + " " + super
37
+ end
38
+ end
39
+
40
+ end
41
+ # Raised when hash attribute can't be casted
42
+ class CastingError < AttributeError; end
43
+
44
+ # Raised when required hash attribute wasn't given for casting
45
+ class MissingAttributeError < AttributeError; end
46
+
47
+ # Raised when unexpected hash attribute was given for casting
48
+ class UnexpectedAttributeError < AttributeError; end
49
+
50
+ # Raised when hash has validation errors
51
+ class ValidationError < StandardError
52
+ attr_reader :errors
53
+
54
+ def initialize(message, errors)
55
+ @errors = errors
56
+ super(message)
57
+ end
58
+
59
+ def message
60
+ "#{@message}\n#{errors.to_hash}"
61
+ end
62
+
63
+ def short_message
64
+ 'Validation error'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,30 @@
1
+ module Morf::Metadata
2
+ class Attribute
3
+ attr_reader :name, :caster, :options
4
+ attr_accessor :children
5
+
6
+ def initialize(name, caster, options)
7
+ @name = name
8
+ @caster = caster
9
+ @options = options
10
+ @children = []
11
+ end
12
+
13
+ def has_children?
14
+ !children.empty?
15
+ end
16
+
17
+ def required?
18
+ !optional?
19
+ end
20
+
21
+ def optional?
22
+ !!options[:optional]
23
+ end
24
+
25
+ def allow_nil?
26
+ !!options[:allow_nil]
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Morf
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'morf/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "morf"
8
+ spec.version = Morf::VERSION
9
+ spec.authors = ["Droid Labs LLC"]
10
+ spec.email = ["droid@droidlabs.pro"]
11
+ spec.description = %q{Declarative object morpher}
12
+ spec.summary = %q{Declarative object morher}
13
+ spec.homepage = "https://github.com/droidlabs/morf"
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 "activesupport", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ end