hexx 5.4.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ module Hexx
4
+
5
+ # Makes the class dependable from implementations through setter injections.
6
+ #
7
+ # Adds the private {#depends_on} helper method to the class.
8
+ # Basically the method is similar to the +attr_accessor+ with the following
9
+ # differencies:
10
+ #
11
+ # * it adds a semantics to the declaration.
12
+ # * it allows setting the default implementation.
13
+ # * dependency setters accepts classes and modules only and fails with
14
+ # the +TypeError+ otherwise.
15
+ # * dependency getters fails with the +NotImplemented+ error if the
16
+ # implementation hasn't been set.
17
+ # * if a default implementation is defined, the dependency cannot be
18
+ # set to +nil+.
19
+ #
20
+ # @example
21
+ # class MyClass
22
+ # extend Hexx::Dependable
23
+ #
24
+ # depends_on :another_class, default: AnotherClass
25
+ # depends_on :looks_for_implementation
26
+ # end
27
+ #
28
+ # object = MyClass.new
29
+ # object.another_class # => AnotherClass
30
+ #
31
+ # object.looks_for_implementation
32
+ # # => fails with NotImplementedError
33
+ #
34
+ # object.looks_for_implementation = SomeInjection
35
+ # object.looks_for_implementation # => SomeInjection
36
+ module Dependable
37
+
38
+ private
39
+
40
+ # @!method depends_on(name, options = {})
41
+ # Declares the dependency with its default implementation.
42
+ # @example (see Hexx::Dependable)
43
+ # @param [String, Symbol] name The name of the dependency.
44
+ # @param [Hash] options ({}) The dependency declaration options.
45
+ # @option options [String, Symbol, Class] :default (nil) Optional default
46
+ # implementation for the dependency.
47
+ def depends_on(name, default: nil)
48
+ Helpers::Dependency.add self, name, default
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,111 @@
1
+ # encoding: utf-8
2
+
3
+ module Hexx
4
+
5
+ # @api hide
6
+ # The module contains helper classes and modules.
7
+ #
8
+ # All the helpers injects some code into corresponding class or module.
9
+ #
10
+ # The content of the module is not a part of API, but its implementation
11
+ # details.
12
+ module Helpers
13
+
14
+ # @api hide
15
+ # @abstract
16
+ # The base class for attribute injection.
17
+ #
18
+ # The objects of the class validates its parameters and adds
19
+ # attribute's getter and setter into the target class or module.
20
+ class Base < Struct.new(:target, :name)
21
+
22
+ # @api hide
23
+ # @!method add(target, name)
24
+ # Injects an attribute to the target class.
25
+ #
26
+ # @param [Module] target The class or module to inject the attribute to.
27
+ # @param [String, Symbol] name The name of the attribute.
28
+ # @raise (see Hexx::Helpers::Base#validate)
29
+ # @return [Hexx::Helpers::Base] the base class object.
30
+ def self.add(*args)
31
+ new(*args).validate.add_getter.add_setter
32
+ end
33
+
34
+ # @api hide
35
+ # @!attribute target
36
+ # The class to inject the attribute to
37
+ # @return [Class] the target for the injection
38
+
39
+ # @api hide
40
+ # @!attribute name
41
+ # The name of the attribute to be created in a target class
42
+ # @return [Class] the name of the attribute
43
+
44
+ # @api hide
45
+ # Validates parameters of the dependency declaration.
46
+ #
47
+ # @raise (see Hexx::Helpers::Base#check_target)
48
+ # @raise (see Hexx::Helpers::Base#check_name_type)
49
+ # @raise (see Hexx::Helpers::Base#check_name_value)
50
+ # @return [Hexx::Helpers::Base] +self+
51
+ def validate
52
+ check_target
53
+ check_name_type
54
+ check_name_value
55
+ self
56
+ end
57
+
58
+ # @api hide
59
+ # Adds the parameter getter to the +target+ instance
60
+ # @return [Hexx::Helpers::Base] +self+
61
+ def add_getter
62
+ target.class_eval getter
63
+ self
64
+ end
65
+
66
+ # @api hide
67
+ # Adds the parameter setter to the +target+ instance
68
+ # @return [Hexx::Helpers::Base] +self+
69
+ def add_setter
70
+ target.class_eval setter
71
+ self
72
+ end
73
+
74
+ private
75
+
76
+ # @abstract
77
+ # @return [String] the getter definition
78
+ def getter
79
+ ""
80
+ end
81
+
82
+ # @abstract
83
+ # @return [String] the setter definition
84
+ def setter
85
+ ""
86
+ end
87
+
88
+ # @api hide
89
+ # @raise [TypeError] if a target to add the instance parameter to
90
+ # is not a class.
91
+ def check_target
92
+ return if target.is_a? Class
93
+ fail TypeError.new "#{ target.inspect } is not a class"
94
+ end
95
+
96
+ # @api hide
97
+ # @raise [TypeError] if the attribute name is neither a string nor symbol.
98
+ def check_name_type
99
+ return if name.is_a?(String) || name.is_a?(Symbol)
100
+ fail TypeError.new "#{ name.inspect } is neither string nor symbol"
101
+ end
102
+
103
+ # @api hide
104
+ # @raise [AttributeError] it the name is blank.
105
+ def check_name_value
106
+ return unless name.to_s == ""
107
+ fail ArgumentError.new "Dependency should have a name"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ require_relative "base"
3
+
4
+ module Hexx
5
+ module Helpers
6
+
7
+ # @api hide
8
+ # Coerces class attribute getter and setter with given type.
9
+ #
10
+ # @example
11
+ # Coersion.add MyClass, :name, Multibyte::Chars
12
+ # object = MyClass.new
13
+ #
14
+ # object.name = "Ivo"
15
+ # object.name
16
+ # # => #<Multibyte::Chars @wrapped_string="Ivo" >
17
+ class Coersion < Base
18
+
19
+ # @api hide
20
+ # @!scope class
21
+ # @!method add(target, name, type)
22
+ # Reloads the Base class initializer by adding the type of the attribute
23
+ # to coerce with.
24
+ #
25
+ # @example (see Hexx::Helpers::Coersion)
26
+ # @param (see Hexx::Helpers::Base.add)
27
+ # @param [Module] type The type of the attribute.
28
+ # @raise (see Hexx::Helpers::Coersion#validate)
29
+ # @return [Hexx::Helpers::Coersion] the coersion object.
30
+ def initialize(target, name, type)
31
+ super target, name
32
+ @type = type
33
+ end
34
+
35
+ # @!attribute type
36
+ # The type to coerce the attribute with
37
+ # @return [Class] the type of the attribute
38
+ attr_accessor :type
39
+
40
+ # @api hide
41
+ # Validates parameters of the coersion declaration.
42
+ #
43
+ # Adds validation of the coersion type to the
44
+ # {Hexx::Helper::Base#validate}.
45
+ #
46
+ # @raise (see Hexx::Helpers::Base.validate)
47
+ # @raise (see Hexx::Helpers::Coersion#check_type)
48
+ # @return [Hexx::Helpers::Coersion] +self+
49
+ def validate
50
+ check_type
51
+ super
52
+ end
53
+
54
+ private
55
+
56
+ # @api hide
57
+ # The definition of the coerced attribute getter.
58
+ # To be reloaded for ActiveRecord models.
59
+ def getter
60
+ "def #{ name }=(value);
61
+ @#{ name } = #{ type.name }.new(value);
62
+ end"
63
+ end
64
+
65
+ # @api hide
66
+ # The definition of the coerced attribute setter.
67
+ # To be reloaded for ActiveRecord models.
68
+ def setter
69
+ "def #{ name };
70
+ #{ type.name }.new(@#{ name });
71
+ end"
72
+ end
73
+
74
+ # @api hide
75
+ # @raise [TypeError] if type is not a class
76
+ def check_type
77
+ return if type.is_a? Class
78
+ fail TypeError.new "#{ type.inspect } is not a class"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ require_relative "base"
3
+
4
+ module Hexx
5
+ module Helpers
6
+
7
+ # @api hide
8
+ # @abstract
9
+ # The base class for both instance and module dependencies.
10
+ class Dependency < Base
11
+
12
+ # @api hide
13
+ # @!scope class
14
+ # @!method add(target, name, default)
15
+ # Reloads the Base class initializer by adding the default implementation
16
+ # for the dependency.
17
+ #
18
+ # @param (see Hexx::Helpers::Base)
19
+ # @param [Module] default The default implementation for the dependency.
20
+ # @raise (see Hexx::Helpers::Dependency#validate)
21
+ # @return [Hexx::Helpers::Dependency] the dependency object.
22
+ def initialize(target, name, default)
23
+ super target, name
24
+ @default = default
25
+ end
26
+
27
+ # @!attribute default
28
+ # The default implementation for the dependency
29
+ # @return [Module] the default implementation
30
+ attr_accessor :default
31
+
32
+ # @api hide
33
+ # Validates parameters for the dependency declaration.
34
+ #
35
+ # Adds validation of the dependency default implementation to the
36
+ # {Hexx::Helper::Base#validate}.
37
+ #
38
+ # @raise (see Hexx::Helpers::Base#validate)
39
+ # @raise (see Hexx::Helpers::Dependency#check_default)
40
+ # @return [Hexx::Helpers::Dependency] +self+
41
+ def validate
42
+ check_default
43
+ super
44
+ end
45
+
46
+ private
47
+
48
+ # @api hide
49
+ # The dependency getter
50
+ def getter
51
+ "def #{ name };
52
+ @#{ name } ||= #{ default_name };
53
+ return @#{ name } if @#{ name };
54
+ fail NotImplementedError
55
+ .new \"DI: #{ target.name }##{ name } not implemented\";
56
+ end"
57
+ end
58
+
59
+ # @api hide
60
+ # The dependency setter
61
+ def setter
62
+ "def #{ name }=(value);
63
+ if value.nil? || value.is_a?(Module);
64
+ @#{ name } = value;
65
+ #{ name };
66
+ else;
67
+ fail TypeError.new \"DI: value.inspect is not a module\";
68
+ end;
69
+ end"
70
+ end
71
+
72
+ # @api hide
73
+ # @raise [TypeError] if the default implementation is neither a class,
74
+ # nor module
75
+ def check_default
76
+ return if default.nil? || default.is_a?(Module)
77
+ fail TypeError.new "#{ default.inspect } is neither a class nor module"
78
+ end
79
+
80
+ # @api hide
81
+ # Returns the name of the default implementation of the dependency.
82
+ def default_name
83
+ @default_name ||= default ? default.name : "nil"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+ require_relative "dependency"
3
+
4
+ module Hexx
5
+ module Helpers
6
+
7
+ # @api hide
8
+ # Module dependency constructor.
9
+ #
10
+ # Adds the dependency to selected module.
11
+ #
12
+ # @example
13
+ # ModuleDependency.add(MyModule, :some_module, DefaultImplementation)
14
+ #
15
+ # MyModule.some_module
16
+ # # => DefaultImplementation
17
+ #
18
+ # MyModule.some_module = AnotherImplementation
19
+ # # => AnotherImplementation
20
+ #
21
+ # MyModule.some_module = 1
22
+ # # fails with #<TypeError @message="1 is not a module" >
23
+ class ModuleDependency < Dependency
24
+
25
+ # @api hide
26
+ # Adds the dependency to given module
27
+ # @example (see Hexx::Helpers::ModuleDependency)
28
+ # @param [Module] target The module to declare the dependency of.
29
+ # @param [String, Symbol] name The name of the dependency.
30
+ # @param [Module] default The default implementation of the dependency.
31
+
32
+ # @api hide
33
+ # Adds the module dependency getter
34
+ # @return [ModuleDependency] +self+.
35
+ def add_getter
36
+ target.instance_eval getter
37
+ self
38
+ end
39
+
40
+ # @api hide
41
+ # Adds the module dependency setter
42
+ # @return [ModuleDependency] +self+.
43
+ def add_setter
44
+ target.instance_eval setter
45
+ self
46
+ end
47
+
48
+ private
49
+
50
+ # @raise [TypeError] if a target to add the dependency is not a module.
51
+ def check_target
52
+ return if target.is_a? Module
53
+ fail TypeError.new "#{ target.inspect } is not a module"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ require_relative "base"
3
+
4
+ module Hexx
5
+ module Helpers
6
+
7
+ # @api hide
8
+ # Adds setter and getter for the instance parameter.
9
+ #
10
+ # @example
11
+ # # Providing that MyClass#params returns a hash
12
+ # Parameter.add MyClass, :name
13
+ # object = MyClass.new
14
+ #
15
+ # object.params["name"] # => nil
16
+ # object.name # => nil
17
+ #
18
+ # object.params["name"] = "Ivan"
19
+ # object.params["name"] # => "Ivan"
20
+ # object.name # => "Ivan"
21
+ #
22
+ # object.name = "Ivo"
23
+ # object.params["name"] # => "Ivo"
24
+ # object.name # => "Ivo"
25
+ class Parameter < Base
26
+
27
+ private
28
+
29
+ # @api hide
30
+ def setter
31
+ "def #{ name }; params[\"#{ name }\"]; end"
32
+ end
33
+
34
+ # @api hide
35
+ def getter
36
+ "def #{ name }=(value); params[\"#{ name }\"] = value; end"
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/hexx/service.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ require_relative "dependable"
2
3
 
3
4
  module Hexx
4
5
 
@@ -19,24 +20,99 @@ module Hexx
19
20
  # service.run
20
21
  # # => This will call the listener's method #on_found(item).
21
22
  class Service
23
+ extend Dependable
22
24
  include Wisper::Publisher
23
25
  include ActiveModel::Validations
24
- include Parameters
25
26
 
26
- # @!scope class
27
- # @!method new(options = {})
28
- # Constructs the service object.
27
+ # @api hide
28
+ # Returns the list of allowed parameters for service objects.
29
+ #
30
+ # The parameters are added to the list by the {.allow_params} private
31
+ # helper method.
29
32
  #
30
33
  # @example
31
- # service = Hexx::Service.new name: name
34
+ # class Service < Hexx::Service
35
+ # allow_params :name
36
+ # end
37
+ #
38
+ # Service.params # => "name"
39
+ #
40
+ # @return [Array<String>] The list of allowed instance parameters.
41
+ def self.params
42
+ @params ||= []
43
+ end
44
+
45
+ # @!scope class
46
+ # @!method validates(attribute, options)
47
+ # Adds a standard validation for the attribute.
48
+ # @param [Symbol, String] attribute The name of the attribute to validate.
49
+ # @param [Hash] options The list of validation options.
50
+ # @see ActiveModel validations {APIdocs}[
51
+ # http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates]
52
+
53
+ # @!scope class
54
+ # @!method validate(method, options)
55
+ # Adds a custom validation (calls given method).
56
+ # @param [Symbol, String] method The name of the validation method.
57
+ # @param [Hash] options The list of validation options.
58
+ # @see ActiveModel validations {APIdocs}[
59
+ # http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validate]
60
+
61
+ # @!scope class
62
+ # @!method new(params = {})
63
+ # Constructs the service object with given parameters.
32
64
  #
33
- # @param [Hash] options The options to be assigned to the {#params}.
65
+ # @example (see Hexx::Service)
66
+ # @param [Hash] params ({}) The parameters of the service object to be
67
+ # assigned to the {#params} attribute.
34
68
  # @return [Hexx::Service] The service object.
35
- def initialize(options = {})
36
- super(options)
69
+
70
+ # @api hide
71
+ # Initializes the service object.
72
+ # @param (see Hexx::Service.new)
73
+ # @return (see Hexx::Service.new)
74
+ def initialize(params = {})
75
+ @params = params.dup.stringify_keys.slice(*(self.class.params))
37
76
  @messages = []
38
77
  end
39
78
 
79
+ # @!attribute [r] params
80
+ # The list of service object parameters.
81
+ #
82
+ # The attribute is assigned via the {.new} method options.
83
+ # On initialization the parameters (keys) are stringified and whitelisted.
84
+ #
85
+ # Allowed parameters should be explicitly declared with the {.allow_params}.
86
+ #
87
+ # @example Only whitelisted params are being assigned.
88
+ # class GetItem < Hexx::Service
89
+ # allow_params :name
90
+ # end
91
+ #
92
+ # service = GetItem.new name: "Олег", family: "Рюрикович"
93
+ # service.params # => { "name" => "Олег" }
94
+ # @return [Hash] the service object parameters.
95
+
96
+ # @!attribute [r] messages
97
+ # The array of service messages (instances of {Hexx::Service::Message})
98
+ # with +text+ and +type+ attributes.
99
+ #
100
+ # @example
101
+ # class Test < Hexx::Service
102
+ # def run
103
+ # add_message "info", :ok
104
+ # end
105
+ # end
106
+ #
107
+ # service = Test.new
108
+ # service.run # adds message
109
+ # service.messages
110
+ # # => [#<Hexx::Service::Message @text="ok" @type="info" >]
111
+ #
112
+ # @return [Array<Hexx::Service::Message>] The array of messages.
113
+
114
+ attr_reader :params, :messages
115
+
40
116
  # @!scope class
41
117
  # @!visibility private
42
118
  # @!method allow_params(*params)
@@ -54,28 +130,6 @@ module Hexx
54
130
  #
55
131
  # @param [Array<Symbol, String>] params The list of allowed keys.
56
132
 
57
- # @!scope class
58
- # @!method validates(attribute, options)
59
- # Adds a standard validation for the attribute.
60
- # @note The method is defined in the {ActiveModel::Validations} module.
61
-
62
- # @!scope class
63
- # @!method validate(method, options)
64
- # Adds a custom validation (calls given method).
65
- # @note The method is defined in the {ActiveModel::Validations} module.
66
-
67
- # @!attribute params [r] The list of service object parameters.
68
- # The attribute is assigned via the {.new} method options.
69
- # The keys should be explicitly declared by the {.allow_params} helper.
70
- #
71
- # @example Only whitelisted params are being assigned.
72
- # class GetItem < Hexx::Service
73
- # allow_params :name
74
- # end
75
- #
76
- # service = GetItem.new name: "Олег", family: "Рюрикович"
77
- # service.params # => { "name" => "Олег" }
78
-
79
133
  # @!method subscribe(listener, options = {})
80
134
  # Subscribes the listener to service object's notifications.
81
135
  # The <tt>:prefix</tt> sets the prefix to be added to a notification name
@@ -90,8 +144,8 @@ module Hexx
90
144
 
91
145
  # @abstract
92
146
  # Runs the service object.
93
- # @note The method does nothing and should be reloaded by a specific
94
- # service class.
147
+ #
148
+ # The method does nothing. To be reloaded by a specific service class.
95
149
  def run
96
150
  end
97
151
 
@@ -119,27 +173,18 @@ module Hexx
119
173
  WithCallbacks.new(self, prefix: prefix)
120
174
  end
121
175
 
122
- # @!attribute [r] messages
123
- # The array of service messages (instances of {Hexx::Service::Message})
124
- # with +text+ and +type+ attributes.
125
- #
126
- # @example
127
- # class Test < Hexx::Service
128
- # def run
129
- # add_message "info", :ok
130
- # end
131
- # end
132
- #
133
- # service = Test.new
134
- # service.run # adds message
135
- # service.messages
136
- # # => [#<Hexx::Service::Message @text="ok" @type="info" >]
137
- #
138
- # @return [Array<Hexx::Service::Message>] The array of messages.
139
- attr_reader :messages
140
-
141
176
  private
142
177
 
178
+ # Sets a list of allowed parameters for the class constructor and
179
+ # defines the corresponding instance attributes.
180
+ #
181
+ # @example (see Hexx::Service::Parameters.params)
182
+ # @param [Array<Symbol, String>] keys The list of allowed parameters.
183
+ def self.allow_params(*keys)
184
+ @params = keys.map(&:to_s)
185
+ params.each { |name| Helpers::Parameter.add self, name }
186
+ end
187
+
143
188
  # The helper runs another service object and subscribes +self+ for the
144
189
  # service object's notifications.
145
190
  #