democritus 0.2.0 → 0.2.1

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: 8db17897f669a971aa5b22ba819978953a3eda10
4
- data.tar.gz: d3f9514bb722c2dd6eb51a4beb06d0e48e606524
3
+ metadata.gz: 31bf7a0b474a852c085027772e7d40715b88ae07
4
+ data.tar.gz: ed1008231c6ec39463300838f284f16951d51850
5
5
  SHA512:
6
- metadata.gz: 22a3ec1e45a1e3dce5cb8c36751723c88a37002d6a654e7ac7899a0dd926a5f3657f422592e5e94795df8cf1235e1bc6023a277dcef638445dbd99cbfeb1c3c3
7
- data.tar.gz: d34e1f0f3288fa495664c7c63710185ee1a9eb7ae2a61591a3e50b85c8a2b561e67ac5f5184cefcf3bca1bb44a7508dbc3de949fec934943d1159c8be8b4810a
6
+ metadata.gz: d931a7f8efab73769eeaedaa45ee3935693e17c0bf88d8b3da9f1116a1538f84cd644821c2f048f0fa6562efcd76667cde7fe0a6ba9155e8fb686d6245ef3877
7
+ data.tar.gz: 6ecc80dbd308f689ca8fc19ec4571dab4587c9b8de7620d66669c27715b4a7613c494d5a8ddf56c11b1bc428cfa37c36a2ebab216a3049849680c9de82991c15
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /.tags*
11
+ /tags
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --order random
3
+
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Test Coverage](https://codeclimate.com/github/jeremyf/democritus/badges/coverage.svg)](https://codeclimate.com/github/jeremyf/democritus)
7
7
  [![Documentation Status](http://inch-ci.org/github/jeremyf/democritus.svg?branch=master)](http://inch-ci.org/github/jeremyf/democritus)
8
8
 
9
- Democritus is a plugin for building class from reusable components.
9
+ Democritus is library for building classes from reusable components.
10
10
 
11
11
  Democritus is inspired as followup of a common pattern that I saw in the development of [Sipity's](https://github.com/ndlib/sipity/) form objects.
12
12
  It also aims to address the needs of Sipity's yet to be developed sibling application; The dissemination of processed data.
@@ -57,5 +57,5 @@ ApprovalForm = Demcritus.build_from_json(json)
57
57
  - [x] [Command::Attirubtes](./lib/democritus/class_builder/commands/attributes.rb)
58
58
  - [ ] Create the commands that build a Sipity processing form
59
59
  - [X] Build class from JSON configuration
60
- - [ ] Basic case for nested commands
61
- - [ ] Allow for "constantization" of command_namespaces option.
60
+ - [X] Basic case for nested commands
61
+ - [X] Allow for "constantization" of command_namespaces option.
data/Rakefile CHANGED
@@ -30,3 +30,4 @@ FlayTask.new do |task|
30
30
  end
31
31
 
32
32
  task(default: ['rubocop', 'reek', 'flay', 'spec'])
33
+ task(release: :default)
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Jeremy Friesen"]
10
10
  spec.email = ["jeremy.n.friesen@gmail.com"]
11
11
 
12
- spec.summary = %q{A placeholder for an attribute declaration mechanism}
13
- spec.description = %q{A placeholder for an attribute declaration mechanism}
12
+ spec.summary = %q{Democritus is library for building classes from reusable components.}
13
+ spec.description = %q{Democritus is library for building classes from reusable components.}
14
14
  spec.homepage = "https://github.com/jeremyf/democritus"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
@@ -23,11 +23,10 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "rake", "~> 10.0"
24
24
  spec.add_development_dependency "rspec", "~> 3.2"
25
25
  spec.add_development_dependency "rspec-its", "~> 1.2"
26
- spec.add_development_dependency "reek"
27
- spec.add_development_dependency "flay"
26
+ spec.add_development_dependency "rubycritic"
28
27
  spec.add_development_dependency "rubocop"
29
28
  spec.add_development_dependency "rubocop-rspec"
30
29
  spec.add_development_dependency "simplecov"
31
30
  spec.add_development_dependency "codeclimate-test-reporter"
32
- spec.add_development_dependency "byebug"
31
+ spec.add_development_dependency "shoulda-matchers"
33
32
  end
@@ -8,11 +8,23 @@ require 'democritus/from_json_class_builder'
8
8
  # for understanding the galaxy of objects.
9
9
  module Democritus
10
10
  # @api public
11
+ # @since 0.1.0
11
12
  #
12
13
  # Responsible for building a class based on atomic components.
13
14
  #
14
- # @yield [Democritus::ClassBuilder] Gives a builder to provide additional command style customizations
15
+ # @yield [Democritus::ClassBuilder] a builder object to provide additional command style customizations
15
16
  # @return Class
17
+ #
18
+ # @example
19
+ # NamedPerson = Democritus.build do |builder|
20
+ # builder.attributes do
21
+ # attribute(name: :given_name)
22
+ # attribute(name: :surname)
23
+ # end
24
+ # end
25
+ # NamedPerson.new(given_name: 'Farrokh', surname: 'Bulsara')
26
+ #
27
+ # @see Demcoritus::ClassBuilder
16
28
  def self.build(&configuration_block)
17
29
  builder = ClassBuilder.new
18
30
  builder.customize(&configuration_block)
@@ -20,10 +32,21 @@ module Democritus
20
32
  end
21
33
 
22
34
  # @api public
35
+ # @since 0.2.0
23
36
  #
24
37
  # Responsible for building a class based on the given JSON object.
25
38
  #
39
+ # @param [String] A "well-formed" JSON document
26
40
  # @return Class
41
+ #
42
+ # NamedPerson = Democritus.build_from_json(%{
43
+ # "#attributes": {
44
+ # "#attribute": [{ "name": "given_name" }, { "name": "surname"}]
45
+ # }
46
+ # })
47
+ # NamedPerson.new(given_name: 'Farrokh', surname: 'Bulsara')
48
+ #
49
+ # @see Demcoritus::FromJsonClassBuilder for details on "well-formed"
27
50
  def self.build_from_json(json)
28
51
  builder = FromJsonClassBuilder.new(json)
29
52
  builder.generate_class
@@ -29,11 +29,23 @@ module Democritus
29
29
  # The above #a_customization method is captured in the customization_module and applied as an instance method
30
30
  # to the generated class.
31
31
  attr_accessor :customization_module
32
+
33
+ # The module with the generated code.
34
+ #
35
+ # @example
36
+ # Democritus.build do |builder|
37
+ # builder.a_command
38
+ # def a_customization
39
+ # end
40
+ # end
41
+ #
42
+ # The above `builder.a_command` invocation is captured and is applied to the generation_module.
32
43
  attr_accessor :generation_module
33
44
 
34
45
  # Command operations to be applied as class methods of the generated_class.
35
46
  attr_accessor :class_operations
36
47
 
48
+ # The default namespaces in which Democritus will look up commands.
37
49
  def default_command_namespaces
38
50
  [Democritus::ClassBuilder::Commands]
39
51
  end
@@ -42,7 +54,14 @@ module Democritus
42
54
  attr_reader :command_namespaces
43
55
 
44
56
  def command_namespaces=(input)
45
- @command_namespaces = Array(input)
57
+ @command_namespaces = Array(input).map do |command_namespace|
58
+ case command_namespace
59
+ when Module, Class
60
+ command_namespace
61
+ else
62
+ Object.const_get(command_namespace.to_s)
63
+ end
64
+ end
46
65
  end
47
66
 
48
67
  # Command operations to be applied as instance methods of the generated_class.
@@ -89,17 +108,36 @@ module Democritus
89
108
  customization_mod = customization_module # get a local binding
90
109
  apply_operations(instance_operations, generation_mod)
91
110
  generated_class = Class.new do
111
+ # requires the local binding from above
92
112
  const_set :GeneratedMethods, generation_mod
93
113
  const_set :Customizations, customization_mod
114
+ # because == and case operators are useful
94
115
  include DemocritusObjectTag
95
116
  include generation_mod
117
+
118
+ # customization should be applied last as it allows for "overrides" of generated methods
96
119
  include customization_mod
97
120
  end
98
121
  generated_class
99
122
  end
100
123
  # rubocop:enable MethodLength
101
124
 
102
- def defer(options = {}, &deferred_operation)
125
+ # @api public
126
+ #
127
+ # When configuring the class that is being built, we don't want to apply all of the modifications at once, instead allowing them
128
+ # to be applied in a specified order.
129
+ #
130
+ # @example
131
+ # Democritus::ClassBuilder.new.defer(prepend: true) do
132
+ # define_method(:help) { 'Did you try turning it off and on again?' }
133
+ # end
134
+ #
135
+ # @param [Hash] options
136
+ # @option options [Boolean] :prepend Is there something about this deferred_operation that should happen first?
137
+ # @param deferred_operation [#call] The operation that will be applied to the generated class
138
+ #
139
+ # @return void
140
+ def defer(**options, &deferred_operation)
103
141
  if options[:prepend]
104
142
  instance_operations.unshift(deferred_operation)
105
143
  else
@@ -121,6 +159,16 @@ module Democritus
121
159
  private
122
160
 
123
161
  # @api public
162
+ #
163
+ # The guts of the Democritus plugin system. The ClassBuilder brokers missing methods to registered commands within the
164
+ # CommandNamespace.
165
+ #
166
+ # @param method_name [Symbol] Name of the message being sent to this object
167
+ # @param args Non-keyword arguments for the message sent to this object
168
+ # @param kargs Keyword arguments for the message sent to this object
169
+ # @param block Block argument for the message sent to this object
170
+ # @return void if there is a Command object that is called
171
+ # @return unknown if no Command object is found
124
172
  def method_missing(method_name, *args, **kargs, &block)
125
173
  command_name = self.class.command_name_for_method(method_name)
126
174
  command_namespace = command_namespace_for(command_name)
@@ -133,17 +181,26 @@ module Democritus
133
181
  end
134
182
 
135
183
  # @api public
184
+ #
185
+ # A required sibling method when implementing #method_missing
186
+ #
187
+ # @param method_name [Symbol] Name of the message being sent to this object
188
+ # @param args Additional arguments passed to the query
189
+ # @return Boolean
136
190
  def respond_to_missing?(method_name, *args)
137
191
  respond_to_definition(method_name, :respond_to_missing?, *args)
138
192
  end
139
193
 
140
- # @api public
194
+ # @api private
141
195
  def respond_to_definition(method_name, *)
142
196
  command_name = self.class.command_name_for_method(method_name)
143
197
  command_namespace_for(command_name)
144
198
  end
145
199
 
146
200
  # @api private
201
+ #
202
+ # Find the first matching the command_namespace that contains the given
203
+ # command_name
147
204
  def command_namespace_for(command_name)
148
205
  command_namespaces.detect { |cs| cs.const_defined?(command_name) }
149
206
  end
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module Democritus
2
4
  class ClassBuilder
3
5
  # @api public
@@ -17,16 +19,17 @@ module Democritus
17
19
  end
18
20
 
19
21
  # @api public
20
- #
21
22
  # @abstract Subclass and override #call to implement
23
+ #
24
+ # Responsible for applying changes to the class that is being built.
25
+ #
26
+ # @return void
22
27
  def call
23
28
  fail(NotImplementedError, 'Method #call should be overriden in child classes')
24
29
  end
25
30
 
26
- # @api private
27
- def defer(options = {}, &block)
28
- builder.defer(options, &block)
29
- end
31
+ extend Forwardable
32
+ def_delegator :builder, :defer
30
33
 
31
34
  private
32
35
 
@@ -5,14 +5,21 @@ module Democritus
5
5
  module Commands
6
6
  # Command to assign an attribute to the given built class.
7
7
  class Attribute < ::Democritus::ClassBuilder::Command
8
+ # @param builder [#defer, Democritus::ClassBuilder]
9
+ # @param name [#to_sym] The name of the attribute
10
+ # @param options [Hash] The configuration options of the attribute
8
11
  def initialize(name:, **options)
9
12
  self.builder = options.fetch(:builder)
10
13
  self.name = name
11
14
  self.options = options
12
15
  end
13
16
 
14
- attr_reader :name, :options
17
+ attr_reader :name
18
+ attr_reader :options
15
19
 
20
+ # Generate the code for the attribute
21
+ #
22
+ # @return void
16
23
  # :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attribute#call' ] }
17
24
  def call
18
25
  defer do |subject|
@@ -26,7 +33,11 @@ module Democritus
26
33
 
27
34
  private
28
35
 
29
- attr_writer :name, :options
36
+ attr_writer :options
37
+
38
+ def name=(input)
39
+ @name = input.to_sym
40
+ end
30
41
  end
31
42
  end
32
43
  end
@@ -12,39 +12,74 @@ module Democritus
12
12
  # end
13
13
  class Attributes < ::Democritus::ClassBuilder::Command
14
14
  # @param builder [Democritus::ClassBuilder]
15
- # @param additional_configuration [Proc] A means to nest additional configuration
16
- def initialize(builder:, &additional_configuration)
15
+ # @param pre_deferment_operation [Proc] A means to nest additional operations
16
+ def initialize(builder:, &pre_deferment_operation)
17
17
  self.builder = builder
18
- self.additional_configuration = additional_configuration
18
+ self.pre_deferment_operation = pre_deferment_operation
19
19
  self.attribute_names = []
20
20
  end
21
21
 
22
- # :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#call' ] }
23
- # :reek:TooManyStatements: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#call' ] }
22
+ # @api public
23
+ #
24
+ # Method that generates the behavior for the Attributes command
25
+ #
26
+ # @return void
27
+ # @see Democritus::ClassBuilder#defer
24
28
  def call
29
+ # In order to register any nested attributes, the pre_deferment_operation must be performed
30
+ # outside of the defer method call. If it were done inside the defer block, then the pre_deferment_operation
31
+ # might get lost.
32
+ execute_pre_deferment_operation
33
+ defer { |subject| configure(subject: subject) }
34
+ end
35
+
36
+ private
37
+
38
+ def execute_pre_deferment_operation
39
+ return unless pre_deferment_operation.respond_to?(:call)
25
40
  # It may seem a little odd to yield self via an instance_exec, however in some cases I need a
26
41
  # receiver for messages (i.e. FromJsonClassBuilder)
27
- instance_exec(self, &additional_configuration) if additional_configuration.respond_to?(:call)
28
- defer do |subject|
29
- subject.module_exec(@attribute_names) do |attribute_names|
30
- define_method(:initialize) do |**attributes|
31
- attribute_names.each do |attribute_name|
32
- send("#{attribute_name}=", attributes.fetch(attribute_name.to_sym, nil))
33
- end
42
+ instance_exec(self, &pre_deferment_operation)
43
+ end
44
+
45
+ # :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#configure' ] }
46
+ def configure(subject:)
47
+ subject.module_exec(@attribute_names) do |attribute_names|
48
+ define_method(:initialize) do |**attributes|
49
+ attribute_names.each do |attribute_name|
50
+ send("#{attribute_name}=", attributes.fetch(attribute_name.to_sym, nil))
34
51
  end
35
52
  end
36
53
  end
37
54
  end
38
55
 
56
+ public
57
+
58
+ # @!group Commands Available Within the pre_deferment_operation
59
+
60
+ # @api public
61
+ #
62
+ # Exposes a mechanism for assigning individual attributes as part of the #attributes command.
63
+ #
64
+ # @param name [#to_sym] the name of the attribute
65
+ # @param options [Hash] additional options passed to the builder#attribute command
66
+ #
67
+ # @see Democritus::ClassBuilder::Commands::Attribute
68
+ # @see Democritus::ClassBuilder
39
69
  def attribute(name:, **options)
40
70
  name = name.to_sym
41
71
  attribute_names << name
42
72
  builder.attribute(name: name, **options)
43
73
  end
44
74
 
75
+ # @!endgroup
76
+
45
77
  private
46
78
 
47
- attr_accessor :additional_configuration, :attribute_names
79
+ attr_accessor :pre_deferment_operation
80
+
81
+ # [Array] of attribute names that have been generated
82
+ attr_accessor :attribute_names
48
83
  end
49
84
  end
50
85
  end
@@ -49,6 +49,7 @@ module Democritus
49
49
  class_builder.generate_class
50
50
  end
51
51
 
52
+ # Used to answer the question "Does the given Hash key represent a command?"
52
53
  KEY_IS_COMMAND_REGEXP = /\A\#(.+)$/.freeze
53
54
 
54
55
  private
@@ -2,5 +2,5 @@ module Democritus
2
2
  # Democritus is striving to adhere to semantic versioning.
3
3
  #
4
4
  # @see semver.org
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: democritus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Friesen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-01-07 00:00:00.000000000 Z
11
+ date: 2016-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -67,21 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.2'
69
69
  - !ruby/object:Gem::Dependency
70
- name: reek
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: flay
70
+ name: rubycritic
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - ">="
@@ -151,7 +137,7 @@ dependencies:
151
137
  - !ruby/object:Gem::Version
152
138
  version: '0'
153
139
  - !ruby/object:Gem::Dependency
154
- name: byebug
140
+ name: shoulda-matchers
155
141
  requirement: !ruby/object:Gem::Requirement
156
142
  requirements:
157
143
  - - ">="
@@ -164,7 +150,7 @@ dependencies:
164
150
  - - ">="
165
151
  - !ruby/object:Gem::Version
166
152
  version: '0'
167
- description: A placeholder for an attribute declaration mechanism
153
+ description: Democritus is library for building classes from reusable components.
168
154
  email:
169
155
  - jeremy.n.friesen@gmail.com
170
156
  executables: []
@@ -173,6 +159,7 @@ extra_rdoc_files: []
173
159
  files:
174
160
  - ".gitignore"
175
161
  - ".hound.yml"
162
+ - ".rspec"
176
163
  - ".travis.yml"
177
164
  - Gemfile
178
165
  - LICENSE
@@ -211,5 +198,5 @@ rubyforge_project:
211
198
  rubygems_version: 2.4.7
212
199
  signing_key:
213
200
  specification_version: 4
214
- summary: A placeholder for an attribute declaration mechanism
201
+ summary: Democritus is library for building classes from reusable components.
215
202
  test_files: []