appfuel 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +25 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +19 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +9 -0
  9. data/README.md +38 -0
  10. data/Rakefile +6 -0
  11. data/appfuel.gemspec +42 -0
  12. data/bin/console +7 -0
  13. data/bin/setup +8 -0
  14. data/lib/appfuel.rb +210 -0
  15. data/lib/appfuel/application.rb +4 -0
  16. data/lib/appfuel/application/app_container.rb +223 -0
  17. data/lib/appfuel/application/container_class_registration.rb +22 -0
  18. data/lib/appfuel/application/container_key.rb +201 -0
  19. data/lib/appfuel/application/qualify_container_key.rb +76 -0
  20. data/lib/appfuel/application/root.rb +140 -0
  21. data/lib/appfuel/cli_msg_request.rb +19 -0
  22. data/lib/appfuel/configuration.rb +14 -0
  23. data/lib/appfuel/configuration/definition_dsl.rb +175 -0
  24. data/lib/appfuel/configuration/file_loader.rb +61 -0
  25. data/lib/appfuel/configuration/populate.rb +95 -0
  26. data/lib/appfuel/configuration/search.rb +45 -0
  27. data/lib/appfuel/db_model.rb +16 -0
  28. data/lib/appfuel/domain.rb +7 -0
  29. data/lib/appfuel/domain/criteria.rb +436 -0
  30. data/lib/appfuel/domain/domain_name_parser.rb +44 -0
  31. data/lib/appfuel/domain/dsl.rb +247 -0
  32. data/lib/appfuel/domain/entity.rb +242 -0
  33. data/lib/appfuel/domain/entity_collection.rb +87 -0
  34. data/lib/appfuel/domain/expr.rb +127 -0
  35. data/lib/appfuel/domain/value_object.rb +7 -0
  36. data/lib/appfuel/errors.rb +104 -0
  37. data/lib/appfuel/feature.rb +2 -0
  38. data/lib/appfuel/feature/action_loader.rb +25 -0
  39. data/lib/appfuel/feature/initializer.rb +43 -0
  40. data/lib/appfuel/handler.rb +6 -0
  41. data/lib/appfuel/handler/action.rb +17 -0
  42. data/lib/appfuel/handler/base.rb +103 -0
  43. data/lib/appfuel/handler/command.rb +18 -0
  44. data/lib/appfuel/handler/inject_dsl.rb +88 -0
  45. data/lib/appfuel/handler/validator_dsl.rb +256 -0
  46. data/lib/appfuel/initialize.rb +70 -0
  47. data/lib/appfuel/initialize/initializer.rb +68 -0
  48. data/lib/appfuel/msg_request.rb +207 -0
  49. data/lib/appfuel/predicates.rb +10 -0
  50. data/lib/appfuel/presenter.rb +18 -0
  51. data/lib/appfuel/presenter/base.rb +7 -0
  52. data/lib/appfuel/repository.rb +73 -0
  53. data/lib/appfuel/repository/base.rb +72 -0
  54. data/lib/appfuel/repository/initializer.rb +19 -0
  55. data/lib/appfuel/repository/mapper.rb +203 -0
  56. data/lib/appfuel/repository/mapping_dsl.rb +210 -0
  57. data/lib/appfuel/repository/mapping_entry.rb +76 -0
  58. data/lib/appfuel/repository/mapping_registry.rb +121 -0
  59. data/lib/appfuel/repository_runner.rb +60 -0
  60. data/lib/appfuel/request.rb +53 -0
  61. data/lib/appfuel/response.rb +96 -0
  62. data/lib/appfuel/response_handler.rb +79 -0
  63. data/lib/appfuel/root_module.rb +31 -0
  64. data/lib/appfuel/run_error.rb +9 -0
  65. data/lib/appfuel/storage.rb +3 -0
  66. data/lib/appfuel/storage/db.rb +4 -0
  67. data/lib/appfuel/storage/db/active_record_model.rb +42 -0
  68. data/lib/appfuel/storage/db/mapper.rb +213 -0
  69. data/lib/appfuel/storage/db/migration_initializer.rb +42 -0
  70. data/lib/appfuel/storage/db/migration_runner.rb +15 -0
  71. data/lib/appfuel/storage/db/migration_tasks.rb +18 -0
  72. data/lib/appfuel/storage/db/repository.rb +231 -0
  73. data/lib/appfuel/storage/db/repository_query.rb +13 -0
  74. data/lib/appfuel/storage/file.rb +1 -0
  75. data/lib/appfuel/storage/file/base.rb +32 -0
  76. data/lib/appfuel/storage/memory.rb +2 -0
  77. data/lib/appfuel/storage/memory/mapper.rb +30 -0
  78. data/lib/appfuel/storage/memory/repository.rb +37 -0
  79. data/lib/appfuel/types.rb +53 -0
  80. data/lib/appfuel/validation.rb +80 -0
  81. data/lib/appfuel/validation/validator.rb +59 -0
  82. data/lib/appfuel/validation/validator_pipe.rb +47 -0
  83. data/lib/appfuel/version.rb +3 -0
  84. metadata +335 -0
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.13.7
6
+
7
+ deploy:
8
+ on:
9
+ tags: true
10
+ provider: rubygems
11
+ api_key:
12
+ secure: Z4sBUqVeIuNJNKxXa+oYDxnfXlVaCAYl3f+8rWm4pKnHpLQYP1M8vj5LM3cmdHtUQiy/085zkvt5C6p2045PZ5sX3rueojoShqfvmNHgr3wYWQ2MZ3oHy3Vl6UJ3JYGoM/2wEclmqWQ49s1Zk4gnNZR4QyNZ/P4SWZ4FmJ9VmlI=
13
+
14
+ addons:
15
+ code_climate:
16
+ repo_token: 11e7712330b39d9d748126a0fccccb0dc60f19536ad3d46f2ca484003ff4899c
17
+
18
+ after_success:
19
+ - bundle exec codeclimate-test-reporter
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at rsb.code@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in appfuel.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "simplecov", "~> 0.13"
8
+ gem "codeclimate-test-reporter", "~> 1.0.0"
9
+ end
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ [![Travis Build Status](https://travis-ci.org/rsb/appfuel.svg?branch=master)](https://travis-ci.org/rsb/appfuel)
2
+ [![Code Climate](https://codeclimate.com/github/rsb/appfuel/badges/gpa.svg)](https://codeclimate.com/github/rsb/appfuel)
3
+ [![Test Coverage](https://codeclimate.com/github/rsb/appfuel/badges/coverage.svg)](https://codeclimate.com/github/rsb/appfuel/coverage)
4
+ [![Issue Count](https://codeclimate.com/github/rsb/appfuel/badges/issue_count.svg)](https://codeclimate.com/github/rsb/appfuel)
5
+
6
+ # Appfuel
7
+ Appfuel is a library that employs a set of conventions and patterns used to separate your business code from the API framework in which it lives, while providing a consistent interface for your api code to interact with it. The idea is that your business code should live isolated in its own gem making the api boundary, your rails app for example, just another client..
8
+
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'appfuel'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install appfuel
25
+
26
+ ## Usage
27
+
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/appfuel. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
38
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/appfuel.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'appfuel/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "appfuel"
8
+ spec.version = Appfuel::VERSION
9
+ spec.authors = ["Robert Scott-Buccleuch"]
10
+ spec.email = ["rsb.code@gmail.com"]
11
+
12
+ spec.summary = %q{Appfuel decouples your business code from its API framework}
13
+ spec.description = %q{A library that allows you to isolate your business code}
14
+ spec.homepage = "https://github.com/rsb/appfuel"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ # we have to lock dry-types due to failures I am encountering
24
+ # when dynamically creating form validators. Not sure if it is the library
25
+ # or the way I am using it.
26
+ spec.add_dependency "activerecord", "~> 5.0.2"
27
+ spec.add_dependency "dry-types", "0.9.2"
28
+ spec.add_dependency "dry-container", "~> 0.6"
29
+ spec.add_dependency "dry-validation", "~> 0.10.5"
30
+ spec.add_dependency "dry-monads", "~> 0.2"
31
+ spec.add_dependency "dry-configurable", "~> 0.6"
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.13"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "rspec", "~> 3.5"
36
+ spec.add_development_dependency "pry", "~> 0.10"
37
+ spec.add_development_dependency "awesome_print", "~> 1.7"
38
+ spec.add_development_dependency "pry-awesome_print", ">= 9.6.1"
39
+ spec.add_development_dependency "database_cleaner", "~> 1.5"
40
+ spec.add_development_dependency "faker", "~> 1.7"
41
+ spec.add_development_dependency "factory_girl", "~> 4.8"
42
+ end
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "appfuel"
5
+ require "pry"
6
+
7
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/appfuel.rb ADDED
@@ -0,0 +1,210 @@
1
+ # Third party dependencies
2
+ require "json"
3
+ require "dry-validation"
4
+ require "active_record"
5
+
6
+ require "appfuel/version"
7
+
8
+ # Appfuel framework for Action/Comand pattern
9
+ require "appfuel/types"
10
+ require "appfuel/errors"
11
+ require "appfuel/run_error"
12
+
13
+ require "appfuel/configuration"
14
+ require "appfuel/application"
15
+ require "appfuel/initialize"
16
+ require "appfuel/feature"
17
+
18
+ # Action/command input/output interfaces
19
+ require "appfuel/response"
20
+ require "appfuel/response_handler"
21
+ require "appfuel/request"
22
+
23
+ # Custom predicates & validators
24
+ require "appfuel/predicates"
25
+ require "appfuel/validation"
26
+
27
+ # Interface for dscribing domain queries
28
+
29
+ # Dependency management for actions, commands and repos
30
+ require "appfuel/root_module"
31
+
32
+ require "appfuel/presenter"
33
+
34
+ require "appfuel/repository"
35
+ require "appfuel/db_model"
36
+ require "appfuel/repository_runner"
37
+ require "appfuel/storage"
38
+
39
+ # callable operations
40
+ require "appfuel/handler"
41
+
42
+ module Appfuel
43
+ # The appfuel top level interface mainly deals with interacting with both
44
+ # the application dependency injection container and the framework di
45
+ # container.
46
+ class << self
47
+ attr_writer :framework_container
48
+
49
+ # The framework dependency injection container holds information
50
+ # specific to appfuel plus an app container for each app it will manage.
51
+ # While it is most common to has only a single app it is designed to
52
+ # host multiple app since all there dependencies are contained in one
53
+ # container.
54
+ #
55
+ # @return [Dry::Container]
56
+ def framework_container
57
+ @framework_container ||= Dry::Container.new
58
+ end
59
+
60
+
61
+ # The default app name must exist and the container must be registered
62
+ # for this to be true
63
+ #
64
+ # @return [Bool]
65
+ def default_app?
66
+ framework_container.key?(:default_app_name) &&
67
+ framework_container.key?(framework_container[:default_app_name])
68
+ end
69
+
70
+ # Used when retrieving, resolving an item from or registering an item
71
+ # with an application container without using its name. The default
72
+ # app is considered the main app where all others have to be specified
73
+ # manually. This is assigned during setup via the module
74
+ # Appfuel::Initialize::Setup
75
+ #
76
+ # @raises Dry::Container::Error when default_app_name is not registered
77
+ #
78
+ # @return [String]
79
+ def default_app_name
80
+ framework_container[:default_app_name]
81
+ end
82
+
83
+ # The application container is a di container used to hold all dependencies
84
+ # for the given application.
85
+ #
86
+ # @raises Dry::Container::Error when name is not registered
87
+ #
88
+ # @param name [String, Nil] default name is used when name is nil
89
+ # @return [Dry::Container]
90
+ def app_container(name = nil)
91
+ framework_container[name || default_app_name]
92
+ end
93
+
94
+ # Resolve an item out of the application container
95
+ #
96
+ # @raises RuntimeError when container does not implement :resolve
97
+ # @raises Dry::Container::Error when key is not registered
98
+ # @raises Dry::Container::Error when app_name is not registered
99
+ #
100
+ # @param key [String] key of the item in the app container
101
+ # @param app_name [String, Nil] name of the app container
102
+ # @return the item that was resolved with name
103
+ def resolve(key, app_name = nil)
104
+ di = app_container(app_name)
105
+ unless di.respond_to?(:resolve)
106
+ fail "Application container (#{app_name}) does not implement :resolve"
107
+ end
108
+ di.resolve(key)
109
+ end
110
+
111
+
112
+ # Register an item in the application container
113
+ #
114
+ # @raises RuntimeError when container does not implement :register
115
+ # @raises Dry::Container::Error when key is not registered
116
+ # @raises Dry::Container::Error when app_name is not registered
117
+ #
118
+ # @param key [String] key of the item in the app container
119
+ # @param app_name [String, Nil] name of the app container
120
+ # @return the item that was resolved with name
121
+ def register(key, value, app_name = nil)
122
+ di = app_container(app_name)
123
+ unless di.respond_to?(:register)
124
+ fail "Application container (#{app_name}) does not implement :register"
125
+ end
126
+ di.register(key, value)
127
+ end
128
+
129
+
130
+ def setup_container_dependencies(namespace_key, container)
131
+ container.namespace(namespace_key) do
132
+ register('initializers', ThreadSafe::Array.new)
133
+ register('validators', {})
134
+ register('repositories', {})
135
+ register('validator_pipes', {})
136
+ register('domain_builders', {})
137
+ register('presenters', {})
138
+ end
139
+ container
140
+ end
141
+
142
+ # Run all initializers registered in the app container
143
+ #
144
+ # @param container [Dry::Container] application container
145
+ # @param app_name [String] name of the app for errors
146
+ # @param params [Hash]
147
+ # @option excludes [Array] list of initializers to exclude from running
148
+ # @return [Dry::Container] same container passed in
149
+ def run_initializers(key, container, exclude = [])
150
+ unless exclude.is_a?(Array)
151
+ fail ArgumentError, ":exclude must be an array"
152
+ end
153
+ exclude.map! {|item| item.to_s}
154
+
155
+ env = container[:env]
156
+ config = container[:config]
157
+ container["#{key}.initializers"].each do |init|
158
+ if !init.env_allowed?(env) || exclude.include?(init.name)
159
+ next
160
+ end
161
+
162
+ begin
163
+ init.call(config, container)
164
+ rescue => e
165
+ app_name = container[:app_name]
166
+ msg = "[Appfuel:#{app_name}] Initialization FAILURE - " + e.message
167
+ error = RuntimeError.new(msg)
168
+ error.set_backtrace(e.backtrace)
169
+ raise error
170
+ end
171
+ end
172
+ container.register("#{key}.initialized", true)
173
+
174
+ container
175
+ end
176
+
177
+ # memberships.user => features.memberships.presenters.user
178
+ # global.user => global.presenters.user
179
+ #
180
+ # array
181
+ # global
182
+ # user
183
+ #
184
+ # array
185
+ # membership
186
+ # user
187
+ def presenter(key, opts = {}, &block)
188
+ key = expect_container_key(key, 'presenters')
189
+ container = app_container(root_name | default_app_name)
190
+
191
+ container.register(key, '')
192
+
193
+ end
194
+
195
+ def expand_container_key(key, category)
196
+ parts = key.to_s.split('.')
197
+ parts.insert(1, category)
198
+ if parts.first != 'global'
199
+ parts.unshift('features')
200
+ end
201
+ parts.join('.')
202
+ end
203
+
204
+ end
205
+ end
206
+
207
+ # Domain Entities
208
+ require "appfuel/domain"
209
+
210
+
@@ -0,0 +1,4 @@
1
+ require_relative 'application/root'
2
+ require_relative 'application/app_container'
3
+ require_relative 'application/container_key'
4
+ require_relative 'application/container_class_registration'
@@ -0,0 +1,223 @@
1
+ module Appfuel
2
+ module Application
3
+ # Mixins to allow you to handle application container keys. The application
4
+ # container operates based on certain conventions which we take into account
5
+ # here.
6
+ module AppContainer
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ base.extend(ContainerClassRegistration)
10
+ end
11
+
12
+ module ClassMethods
13
+ # Parse the namespace assuming it is a ruby namespace and assign
14
+ # the list to container_path_list
15
+ #
16
+ # @param namespace [String]
17
+ # @return [Array]
18
+ def load_path_from_ruby_namespace(namespace)
19
+ self.container_path = parse_list_string(namespace, '::')
20
+ end
21
+
22
+ # Parse the namespace assuming it is a dry container namespace and
23
+ # assign the list to container_path_list
24
+ #
25
+ # @param namespace [String]
26
+ # @return [Array]
27
+ def load_path_from_container_namespace(namespace)
28
+ self.container_path = parse_list_string(namespace, '.')
29
+ end
30
+
31
+ # @param namespace [String] encoded string that represents a path
32
+ # @param char [String] character used to split the keys into a list
33
+ # @return [Array] an array of lower case snake cased strings
34
+ def parse_list_string(namespace, char)
35
+ fail "split char must be '.' or '::'" unless ['.', '::'].include?(char)
36
+ namespace.to_s.split(char).map {|i| i.underscore }
37
+ end
38
+
39
+ # return [Boolean]
40
+ def container_path?
41
+ !@container_path.nil?
42
+ end
43
+ # @param list [Array] list of container namespace parts including root
44
+ # @return [Array]
45
+ def container_path=(list)
46
+ fail "container path list must be an array" unless list.is_a?(Array)
47
+ @container_path = list
48
+ @container_path.freeze
49
+ end
50
+
51
+ # An array representation of the application container namespace, where
52
+ # the root is the name of the application and not part of the namespace
53
+ # and the rest is hierarchical path to features or globals
54
+ #
55
+ # @return [Array]
56
+ def container_path
57
+ load_path_from_ruby_namespace(self.to_s) unless container_path?
58
+ @container_path
59
+ end
60
+
61
+ # This is the application name used to identify the application container
62
+ # that is stored in the framework container
63
+ #
64
+ # @return string
65
+ def container_root_name
66
+ container_path.first
67
+ end
68
+
69
+ # All root namespace for anything inside features, use this name. It is
70
+ # important to note that to avoid long namespace in ruby features are the
71
+ # name of the module directly below the root.
72
+ #
73
+ # @return [String]
74
+ def container_features_root_name
75
+ @container_features_root_name ||= 'features'
76
+ end
77
+
78
+
79
+ # The actual name of the feature
80
+ #
81
+ # @return [String]
82
+ def container_feature_name
83
+ container_path[1]
84
+ end
85
+
86
+ # The feature name is the second item in the path, that is always prexfix
87
+ # with "features"
88
+ #
89
+ # @return [String]
90
+ def container_feature_key
91
+ @container_feature_key ||= (
92
+ "#{container_features_root_name}.#{container_feature_name}"
93
+ )
94
+ end
95
+
96
+ # Container key relative from feature or global, depending on which class
97
+ # this is mixed into
98
+ #
99
+ # @return [String]
100
+ def container_relative_key
101
+ @container_relative_key ||= container_path[2..-1].join('.')
102
+ end
103
+
104
+ # This refers to either the global path or the path to a particular
105
+ # feature
106
+ #
107
+ # @return [String]
108
+ def top_container_key
109
+ container_global_path? ? container_global_name : container_feature_key
110
+ end
111
+
112
+ def container_key_basename
113
+ @container_path.last
114
+ end
115
+
116
+ # Fully qualified key, meaning you can access the class this was mixed into
117
+ # if you stored it into the container using this key
118
+ #
119
+ # @return [String]
120
+ def container_qualified_key
121
+ @container_qualified_key ||= (
122
+ "#{top_container_key}.#{container_relative_key}"
123
+ )
124
+ end
125
+
126
+ # Determines if the container path represents a global glass
127
+ #
128
+ # @return [Boolean]
129
+ def container_global_path?
130
+ container_path[1] == container_global_name
131
+ end
132
+
133
+ # @return [String]
134
+ def container_global_name
135
+ @container_global_name ||= 'global'
136
+ end
137
+
138
+ # Convert the injection key to a fully qualified namespaced key that
139
+ # is used to pull an item out of the app container.
140
+ #
141
+ # Rules:
142
+ # 1) split the injection key by '.'
143
+ # 2) use the feature_key as the initial namespace
144
+ # 3) when the first part of the key is "global" use that instead of
145
+ # the feature_key
146
+ # 4) append the type_key to the namespace unless it is "container"
147
+ # type_key like "repositories" or "commands" removes the need for
148
+ # the user to have to specify it since they already did that when
149
+ # they used the type param.
150
+ #
151
+ #
152
+ # note: feature_key in these examples will be "features.my-feature"
153
+ # @example of a feature repository named foo
154
+ #
155
+ # convert_to_qualified_container_key('foo', 'repositories')
156
+ #
157
+ # returns 'features.my-feature.repositories.foo'
158
+ #
159
+ # @example of a global command names bar
160
+ #
161
+ # convert_to_qualified_container_key('global.bar', 'commands')
162
+ #
163
+ # returns 'gloval.commands.bar'
164
+ #
165
+ # @example of a container item baz
166
+ # NOTE: feature container items are not in any namespace, they are any item
167
+ # that can resolve from the namespace given by "feature_key"
168
+ #
169
+ # convert_to_qualified_container_key('baz', 'container')
170
+ #
171
+ # returns 'features.my-feature.baz'
172
+ #
173
+ # @example of a global container item baz
174
+ # NOTE: global container items are not in any namespace, they are any item
175
+ # you can resolve from the application container.
176
+ #
177
+ # convert_to_qualified_container_key('global.baz', 'container')
178
+ #
179
+ # returns 'baz'
180
+ #
181
+ # @param key [String] partial key to be built into fully qualified key
182
+ # @param type_ns [String] namespace for key
183
+ # @return [String] fully qualified namespaced key
184
+ def qualify_container_key(key, type_ns)
185
+ parts = key.to_s.split('.')
186
+ namespace = "#{container_feature_key}."
187
+ if parts[0].downcase == 'global'
188
+ namespace = 'global.'
189
+ parts.shift
190
+ elsif parts[0] == container_feature_name
191
+ parts.shift
192
+ end
193
+
194
+ # when the key is a global container the namespace is only the path
195
+ if type_ns == "container"
196
+ namespace = '' if namespace == 'global.'
197
+ type_ns = ''
198
+ else
199
+ type_ns = "#{type_ns}."
200
+ end
201
+
202
+ path = parts.join('.')
203
+ "#{namespace}#{type_ns}#{path}"
204
+ end
205
+
206
+ def app_container
207
+ Appfuel.app_container(container_root_name)
208
+ end
209
+
210
+ end
211
+
212
+ # Instance methods
213
+ def qualify_container_key(key, type_ns)
214
+ self.class.qualify_container_key(key, type_ns)
215
+ end
216
+
217
+ def app_container
218
+ self.class.app_container
219
+ end
220
+
221
+ end
222
+ end
223
+ end