appfuel 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +25 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +19 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/appfuel.gemspec +42 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/appfuel.rb +210 -0
- data/lib/appfuel/application.rb +4 -0
- data/lib/appfuel/application/app_container.rb +223 -0
- data/lib/appfuel/application/container_class_registration.rb +22 -0
- data/lib/appfuel/application/container_key.rb +201 -0
- data/lib/appfuel/application/qualify_container_key.rb +76 -0
- data/lib/appfuel/application/root.rb +140 -0
- data/lib/appfuel/cli_msg_request.rb +19 -0
- data/lib/appfuel/configuration.rb +14 -0
- data/lib/appfuel/configuration/definition_dsl.rb +175 -0
- data/lib/appfuel/configuration/file_loader.rb +61 -0
- data/lib/appfuel/configuration/populate.rb +95 -0
- data/lib/appfuel/configuration/search.rb +45 -0
- data/lib/appfuel/db_model.rb +16 -0
- data/lib/appfuel/domain.rb +7 -0
- data/lib/appfuel/domain/criteria.rb +436 -0
- data/lib/appfuel/domain/domain_name_parser.rb +44 -0
- data/lib/appfuel/domain/dsl.rb +247 -0
- data/lib/appfuel/domain/entity.rb +242 -0
- data/lib/appfuel/domain/entity_collection.rb +87 -0
- data/lib/appfuel/domain/expr.rb +127 -0
- data/lib/appfuel/domain/value_object.rb +7 -0
- data/lib/appfuel/errors.rb +104 -0
- data/lib/appfuel/feature.rb +2 -0
- data/lib/appfuel/feature/action_loader.rb +25 -0
- data/lib/appfuel/feature/initializer.rb +43 -0
- data/lib/appfuel/handler.rb +6 -0
- data/lib/appfuel/handler/action.rb +17 -0
- data/lib/appfuel/handler/base.rb +103 -0
- data/lib/appfuel/handler/command.rb +18 -0
- data/lib/appfuel/handler/inject_dsl.rb +88 -0
- data/lib/appfuel/handler/validator_dsl.rb +256 -0
- data/lib/appfuel/initialize.rb +70 -0
- data/lib/appfuel/initialize/initializer.rb +68 -0
- data/lib/appfuel/msg_request.rb +207 -0
- data/lib/appfuel/predicates.rb +10 -0
- data/lib/appfuel/presenter.rb +18 -0
- data/lib/appfuel/presenter/base.rb +7 -0
- data/lib/appfuel/repository.rb +73 -0
- data/lib/appfuel/repository/base.rb +72 -0
- data/lib/appfuel/repository/initializer.rb +19 -0
- data/lib/appfuel/repository/mapper.rb +203 -0
- data/lib/appfuel/repository/mapping_dsl.rb +210 -0
- data/lib/appfuel/repository/mapping_entry.rb +76 -0
- data/lib/appfuel/repository/mapping_registry.rb +121 -0
- data/lib/appfuel/repository_runner.rb +60 -0
- data/lib/appfuel/request.rb +53 -0
- data/lib/appfuel/response.rb +96 -0
- data/lib/appfuel/response_handler.rb +79 -0
- data/lib/appfuel/root_module.rb +31 -0
- data/lib/appfuel/run_error.rb +9 -0
- data/lib/appfuel/storage.rb +3 -0
- data/lib/appfuel/storage/db.rb +4 -0
- data/lib/appfuel/storage/db/active_record_model.rb +42 -0
- data/lib/appfuel/storage/db/mapper.rb +213 -0
- data/lib/appfuel/storage/db/migration_initializer.rb +42 -0
- data/lib/appfuel/storage/db/migration_runner.rb +15 -0
- data/lib/appfuel/storage/db/migration_tasks.rb +18 -0
- data/lib/appfuel/storage/db/repository.rb +231 -0
- data/lib/appfuel/storage/db/repository_query.rb +13 -0
- data/lib/appfuel/storage/file.rb +1 -0
- data/lib/appfuel/storage/file/base.rb +32 -0
- data/lib/appfuel/storage/memory.rb +2 -0
- data/lib/appfuel/storage/memory/mapper.rb +30 -0
- data/lib/appfuel/storage/memory/repository.rb +37 -0
- data/lib/appfuel/types.rb +53 -0
- data/lib/appfuel/validation.rb +80 -0
- data/lib/appfuel/validation/validator.rb +59 -0
- data/lib/appfuel/validation/validator_pipe.rb +47 -0
- data/lib/appfuel/version.rb +3 -0
- 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
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
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
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
data/bin/setup
ADDED
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,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
|