mimi-core 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d247ba31bb61a81362ff2cb8d260d0a1b718bb9
4
- data.tar.gz: 332bd926feab1cdfd99e49e24c6a3b713c2ce254
3
+ metadata.gz: 4386bcac3a69eeb1f5a0422c445ba509e735ea4a
4
+ data.tar.gz: 6c583235d2e0069d255c0598b3e2cce4f4631bcb
5
5
  SHA512:
6
- metadata.gz: fcbb60ca621ead3705f57f73216d6952461cebbd592a5891a755ebe0d09be9f2fd0ab88ec437671f289e2e1bf8eade0f994888744df9c2aa78e2f5084a00252d
7
- data.tar.gz: 10df7f41b424a2fec0e460a2f242612ab7d23e508fbfe22428ca2f71cd4c5cb9fb480e81a05ac0807398266e90069b0a9ccd4bcb84d71ef7d36a823f63c53ec8
6
+ metadata.gz: d234685fd746173702527047e71aaa0c0d237da588e84b1de2f0fa23ecd74cca832569b91729af0e5968e53807d06d6eafb6077c73ac73d454cdfaa9ea041e92
7
+ data.tar.gz: db3ece3e5d363f942ebd3d98ff92b2c286fc198f74261378dcee0d1b67e38d4dd3f88fbbb443e3f2c648623dc85c2eefd679f6fc59a94f16f65bd8cf4e35533a
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/README.md CHANGED
@@ -1,8 +1,205 @@
1
1
  # mimi-core
2
2
 
3
- Core module for *mimi*, microframework for microservices.
3
+ Core module of the [Mimi framework](Mimi).
4
4
 
5
- [in development]
5
+ `mimi-core` defines the top-level Mimi namespace, provides a base for Mimi modules
6
+ and offers some core extentions.
7
+
8
+
9
+ ## Mimi namespace
10
+
11
+ At the top of hierarchy of the Mimi components there is a `Mimi` module that binds all components of the
12
+ framework together. It allows Mimi modules to be used (included in the stack and configured), started
13
+ and stopped.
14
+
15
+ ```ruby
16
+ require 'mimi/core'
17
+
18
+ Mimi # => Module
19
+ ```
20
+
21
+ ### Mimi.use(mod)
22
+
23
+ Configures the given module and appends it to the list of **used modules**.
24
+
25
+ ### Mimi.start
26
+
27
+ Starts the used modules in the order of appending.
28
+
29
+ ### Mimi.stop
30
+
31
+ Stops the used modules in reverse order.
32
+
33
+ ### Example
34
+
35
+ ```ruby
36
+ require 'mimi/db'
37
+
38
+ Mimi.use Mimi::DB, db_adapter: 'sqlite', db_database: 'tmp/dev.db'
39
+ Mimi.start
40
+
41
+ Mimi.app_root_path # => <Pathname> -- guesses current application's root
42
+ ```
43
+
44
+ ## Mimi::Core::Module
45
+
46
+ `Mimi::Core::Module` is a base for any Mimi module.
47
+
48
+ Mimi modules are pluggable components that you can include in your applications.
49
+ They provide some useful functionality, e.g. database abstraction layer, logging etc.
50
+
51
+ Any Mimi module provides a standard list of methods to interact with it:
52
+
53
+ ```
54
+ .configure(opts)
55
+ .start
56
+ .stop
57
+ ```
58
+
59
+ A Mimi module is typically packaged as a gem and it can declare a `module_root_path`.
60
+ Under this path the module may export some files, related to the module's offered functionality.
61
+ For example `Mimi::DB` exposes some rake tasks to perform database clearing, migrations etc.
62
+
63
+
64
+ ### Creating a new module
65
+
66
+ A Mimi component module can be created by including `Mimi::Core::Module` in a module or class
67
+ of yours:
68
+
69
+ ```ruby
70
+ # my_module.rb
71
+
72
+ require 'mimi-core'
73
+
74
+ module MyModule
75
+ include Mimi::Core::Module
76
+ end
77
+
78
+ Mimi.use MyModule, param: 1 # invokes MyModule.configure(param: 1)
79
+ Mimi.start # invokes MyModule.start
80
+ ```
81
+
82
+
83
+ ### Configuring a module
84
+
85
+ A Mimi module implements a `.manifest`, `.configure` and `.options` class methods.
86
+
87
+ The `.configure` class method accepts a Hash of configurable parameter values and sets the options Hash
88
+ available via `.options` class method. Which parameters can be set and whether there are any default
89
+ values for the parameters is defined by the module *manifest*.
90
+
91
+ ```ruby
92
+ Mimi.configure(param: 1)
93
+ Mimi.options[:param] # => 1
94
+ ```
95
+
96
+ ### Module manifest
97
+
98
+ The original `.configure` implementation in `Mimi::Core::Module` takes into consideration the
99
+ module *manifest*.
100
+
101
+ Module manifest is a set of formal definitions of configurable parameters.
102
+
103
+ The manifest declares which configurable parameters are accepted by the module,
104
+ which types of values are accepted, which are default values and so on.
105
+
106
+ When designing your own module, you should redefine `.manifest` method to contain
107
+ your module's actual manifest. The `.manifest` class method should return a Hash representation of the module manifest,
108
+ where keys define the configurable parameter names, and values are Hash-es with the configurable parameter
109
+ properties:
110
+
111
+
112
+ ```ruby
113
+ # my_module.rb
114
+
115
+ require 'mimi-core'
116
+
117
+ module MyModule
118
+ include Mimi::Core::Module
119
+
120
+ def self.manifest
121
+ {
122
+ param1: {} # minimal definition of a configurable parameter
123
+ }
124
+ end
125
+ end
126
+
127
+ Mimi.configure(param: 1)
128
+ Mimi.options[:param] # => 1
129
+ ```
130
+
131
+ Recognised configurable parameter properties are:
132
+
133
+ * `:desc` -- (default: `nil`) human readable description
134
+ * `:type` -- (default: `:string`) accepted value type
135
+ * `:default` -- (default: `nil`) default value for configurable parameter
136
+ * `:const` -- (default: `false`) makes the configurable parameter constant
137
+ * `:hidden` -- (default: `false`) omits the configurable parameter from a combined manifest
138
+
139
+
140
+ Example:
141
+
142
+ ```ruby
143
+ def self.manifest
144
+ {
145
+ param1: {
146
+ desc: 'My configurable param1',
147
+ type: :integer,
148
+ default: 1
149
+ },
150
+ }
151
+ end
152
+ ```
153
+
154
+ @see Mimi::Core::Manifest
155
+
156
+ ## Useful extensions
157
+
158
+ `mimi-core` gem contains some useful extensions which are included automatically, once the gem
159
+ is *require*-d:
160
+
161
+ * Mimi::Core::InheritableProperty
162
+ * Array and Hash extensions
163
+
164
+ ### Mimi::Core::InheritableProperty
165
+
166
+ Makes `.inheritable_property` method available to the class.
167
+
168
+ Example:
169
+
170
+ ```ruby
171
+ class A
172
+ include Mimi::Core::InheritableProperty
173
+
174
+ inheritable_property :my_var
175
+ end
176
+ ```
177
+ @see Mimi::Core::InheritableProperty::ClassMethods#inheritable_property
178
+
179
+
180
+ ### Array and Hash extensions
181
+
182
+ `Array` extensions:
183
+
184
+ ```
185
+ Array#only(*values) # select array elements only if listed in 'values'
186
+ Array#only!(*values) # as above, modify array in-place
187
+ Array#except(*values) # reject array elements listed in 'values'
188
+ Array#except!(*values) # as above, modify array in-place
189
+ ```
190
+
191
+ `Hash` extensions:
192
+
193
+ ```
194
+ Hash#only(*keys) # keep only those key-value pairs of the Hash which keys are listed in 'keys'
195
+ Hash#only!(*keys) # as above, modify Hash in-place
196
+ Hash#except(*keys) # reject key-value pairs of the Hash which keys are listed in 'keys'
197
+ Hash#except!(*keys) # as above, modify Hash in-place
198
+ Hash#deep_merge(other) # deep-merges another Hash
199
+ Hash#deep_dup # deep-duplicates a Hash, taking care of nested Hashes and Arrays
200
+ Hash#deep_symbolize_keys
201
+ Hash#deep_stringify_keys
202
+ ```
6
203
 
7
204
  ## License
8
205
 
@@ -1,5 +1,5 @@
1
- require 'active_support'
2
- require 'active_support/core_ext'
1
+ # frozen_string_literal: true
2
+
3
3
  require 'hashie'
4
4
 
5
5
  class Hash
@@ -9,6 +9,10 @@ class Hash
9
9
  end
10
10
 
11
11
  def only!(*keys)
12
+ if keys.size == 1 && keys.first.is_a?(Array)
13
+ raise ArgumentError, 'Hash#only!() expects keys as list of arguments,' \
14
+ ' not an Array as first argument'
15
+ end
12
16
  select! { |k, _| keys.include?(k) }
13
17
  self
14
18
  end
@@ -20,6 +24,10 @@ class Hash
20
24
  end
21
25
 
22
26
  def except!(*keys)
27
+ if keys.size == 1 && keys.first.is_a?(Array)
28
+ raise ArgumentError, 'Hash#except!() expects keys as list of arguments,' \
29
+ ' not an Array as first argument'
30
+ end
23
31
  reject! { |k, _| keys.include?(k) }
24
32
  self
25
33
  end
@@ -28,6 +36,69 @@ class Hash
28
36
  unless instance_methods(false).include?(:deep_merge)
29
37
  include Hashie::Extensions::DeepMerge
30
38
  end
39
+
40
+ unless instance_methods(false).include?(:deep_dup)
41
+ def deep_dup
42
+ map do |k, v|
43
+ v = v.respond_to?(:deep_dup) ? v.deep_dup : v.dup
44
+ [k, v]
45
+ end.to_h
46
+ end
47
+ end
48
+
49
+ unless instance_methods(false).include?(:symbolize_keys)
50
+ def symbolize_keys
51
+ map do |k, v|
52
+ k = k.respond_to?(:to_sym) ? k.to_sym : k
53
+ [k, v]
54
+ end.to_h
55
+ end
56
+
57
+ def symbolize_keys!
58
+ replace(symbolize_keys)
59
+ end
60
+ end
61
+
62
+ unless instance_methods(false).include?(:deep_symbolize_keys)
63
+ def deep_symbolize_keys
64
+ map do |k, v|
65
+ k = k.respond_to?(:to_sym) ? k.to_sym : k
66
+ v = v.respond_to?(:deep_symbolize_keys) ? v.deep_symbolize_keys : v
67
+ [k, v]
68
+ end.to_h
69
+ end
70
+
71
+ def deep_symbolize_keys!
72
+ replace(deep_symbolize_keys)
73
+ end
74
+ end
75
+
76
+ unless instance_methods(false).include?(:stringify_keys)
77
+ def stringify_keys
78
+ map do |k, v|
79
+ k = k.respond_to?(:to_s) ? k.to_s : k
80
+ [k, v]
81
+ end.to_h
82
+ end
83
+
84
+ def stringify_keys!
85
+ replace(stringify_keys)
86
+ end
87
+ end
88
+
89
+ unless instance_methods(false).include?(:deep_stringify_keys)
90
+ def deep_stringify_keys
91
+ map do |k, v|
92
+ k = k.respond_to?(:to_s) ? k.to_s : k
93
+ v = v.respond_to?(:deep_stringify_keys) ? v.deep_stringify_keys : v
94
+ [k, v]
95
+ end.to_h
96
+ end
97
+
98
+ def deep_stringify_keys!
99
+ replace(deep_stringify_keys)
100
+ end
101
+ end
31
102
  end
32
103
 
33
104
  class Array
@@ -37,6 +108,10 @@ class Array
37
108
  end
38
109
 
39
110
  def only!(*keys)
111
+ if keys.size == 1 && keys.first.is_a?(Array)
112
+ raise ArgumentError, 'Array#only!() expects keys as list of arguments,' \
113
+ ' not an Array as first argument'
114
+ end
40
115
  select! { |k| keys.include?(k) }
41
116
  self
42
117
  end
@@ -48,8 +123,20 @@ class Array
48
123
  end
49
124
 
50
125
  def except!(*keys)
126
+ if keys.size == 1 && keys.first.is_a?(Array)
127
+ raise ArgumentError, 'Array#except!() expects keys as list of arguments,' \
128
+ ' not an Array as first argument'
129
+ end
51
130
  reject! { |k| keys.include?(k) }
52
131
  self
53
132
  end
54
133
  end
134
+
135
+ unless instance_methods(false).include?(:deep_dup)
136
+ def deep_dup
137
+ map do |v|
138
+ v.respond_to?(:deep_dup) ? v.deep_dup : v.dup
139
+ end
140
+ end
141
+ end
55
142
  end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mimi
4
+ module Core
5
+ #
6
+ # Makes `.inheritable_property` method available to the class.
7
+ #
8
+ # Example:
9
+ #
10
+ # ```
11
+ # class A
12
+ # include Mimi::Core::InheritableProperty
13
+ #
14
+ # inheritable_property :my_var
15
+ # end
16
+ # ```
17
+ # @see Mimi::Core::InheritableProperty::ClassMethods#inheritable_property
18
+ #
19
+ module InheritableProperty
20
+ def self.included(base)
21
+ base.send :extend, ClassMethods
22
+ end
23
+
24
+ module ClassMethods
25
+ #
26
+ # Declares an inheritable property.
27
+ #
28
+ # The inheritable property is a special class variable, that is accessible
29
+ # in descendant classes, but each of the descendant classes can alter only its local value.
30
+ #
31
+ # Once a property is declared, a class method with the name of the property
32
+ # becomes available.
33
+ #
34
+ # ```
35
+ # class A
36
+ # include Mimi::Core::InheritableProperty
37
+ #
38
+ # inheritable_property :var1, default: 1
39
+ # end
40
+ #
41
+ # class B < A
42
+ # end
43
+ #
44
+ # A.var1 # => 1
45
+ # B.var1 # => 1
46
+ # ```
47
+ #
48
+ # A class method with the name of the property accepts one optional argument -- a new value
49
+ # for the property. If the argument is omitted, current inherited value is returned.
50
+ #
51
+ # If the argument is present, it sets a new property value for this class
52
+ # and its subclasses, but not for the parent class.
53
+ #
54
+ # ```
55
+ # class A
56
+ # include Mimi::Core::InheritableProperty
57
+ #
58
+ # inheritable_property :var1, default: 1
59
+ # end
60
+ #
61
+ # class B < A
62
+ # var1 123 # sets new value for B.var1
63
+ # end
64
+ #
65
+ # class C < B
66
+ # end
67
+ #
68
+ # A.var1 # => 1, the default value, unchanged
69
+ # B.var1 # => 123
70
+ # C.var1 # => 123
71
+ # ```
72
+ #
73
+ # Working with Hash values
74
+ # ===
75
+ # An `inherited_property` can be declared having the type `:hash`, then the inherited
76
+ # values are deep-merged in subclasses.
77
+ #
78
+ # ```
79
+ # class A
80
+ # include Mimi::Core::InheritableProperty
81
+ #
82
+ # inheritable_property :var1, default: { a: 1 }
83
+ # end
84
+ #
85
+ # class B < A
86
+ # var1 b: 2
87
+ # end
88
+ #
89
+ # class C < B
90
+ # end
91
+ #
92
+ # A.var1 # => { a: 1 }
93
+ # B.var1 # => { a: 1, b: 2 }
94
+ # C.var1 # => { a: 1, b: 2 }
95
+ # ```
96
+ #
97
+ # Proc as default value
98
+ # ===
99
+ # A Proc can be specified instead of literal default value, in which case it will
100
+ # be evaluated when the inherited property value is queried. The passed block will
101
+ # be evaluated in the context of the current class.
102
+ #
103
+ # ```
104
+ # class A
105
+ # include Mimi::Core::InheritableProperty
106
+ #
107
+ # inheritable_property :var1, default: -> { self.name }
108
+ # end
109
+ #
110
+ # class B < A
111
+ # end
112
+ #
113
+ # class C < B
114
+ # end
115
+ #
116
+ # A.var1 # => "A"
117
+ # B.var1 # => "B"
118
+ # ```
119
+ #
120
+ # @param name [Symbol] a name for the new inheritable property
121
+ # @param opts [Hash,nil] optional parameters for the inheritable property
122
+ # @option opts [Object,Proc] :default specifies the literal or Proc as the default value
123
+ # for the property
124
+ # @option opts [:hash,nil] :type instructs that the property is a Hash or a simple value
125
+ # (inherited values are deep-merged for Hash'es)
126
+ #
127
+ def inheritable_property(name, opts = {})
128
+ unless name.is_a?(Symbol)
129
+ raise ArgumentError, 'Symbol is expected as inheritable_property name'
130
+ end
131
+ var_name = :"@#{name}"
132
+ ip_get_name = :"inheritable_property_get_#{name}"
133
+ ip_set_name = :"inheritable_property_set_#{name}"
134
+ opts_default = opts[:default] && opts[:default].dup
135
+
136
+ define_singleton_method(ip_get_name) do |caller|
137
+ if opts[:type] == :hash
138
+ # combine this class' value with a superclass' value
139
+ self_value = instance_variable_get(var_name) || {}
140
+ super_value =
141
+ (superclass.respond_to?(ip_get_name) && superclass.send(ip_get_name, caller)) ||
142
+ (opts_default.is_a?(Proc) ? caller.instance_exec(&opts_default) : opts_default)
143
+ super_value.deep_merge(self_value)
144
+ else
145
+ instance_variable_get(var_name) ||
146
+ (superclass.respond_to?(ip_get_name) && superclass.send(ip_get_name, caller)) ||
147
+ (opts_default.is_a?(Proc) ? caller.instance_exec(&opts_default) : opts_default)
148
+ end
149
+ end
150
+
151
+ define_singleton_method(ip_set_name) do |arg|
152
+ if opts[:type] == :hash && !arg.is_a?(Hash) && !arg.nil?
153
+ raise ArgumentError, "Hash or nil is expected as a value for property #{self}.#{name}"
154
+ end
155
+ instance_variable_set(var_name, arg)
156
+ end
157
+
158
+ define_singleton_method(name) do |*args|
159
+ if args.empty?
160
+ send(:"inheritable_property_get_#{name}", self)
161
+ else
162
+ send(:"inheritable_property_set_#{name}", args.first)
163
+ end
164
+ end
165
+
166
+ define_singleton_method("#{name}!".to_sym) do
167
+ send(name) || (raise "Property #{self}.#{name} is not set")
168
+ end
169
+ end
170
+ end # module ClassMethods
171
+ end # module InheritableProperty
172
+ end # module Core
173
+ end # module Mimi