trailblazer-loader 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +4 -0
- data/README.md +199 -49
- data/lib/trailblazer/loader.rb +30 -9
- data/lib/trailblazer/loader/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f993eca956a001ec251af5f8635dc5dd74ea5e0
|
4
|
+
data.tar.gz: 26a5ae8a81b39c05ac8de3bb81629d11ec1a456e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40b4ecbc9a013b6ba73094130b85d31221fdfd8aff7f657248aa69a40707c68626dac426da94c2ce531ff346592b1020864fc7e6204fbf550e993853dfde4338
|
7
|
+
data.tar.gz: 7b53184a5e2558b3d116cb53fb70c2f5a9259e5e3cc42dcec6f898886fd17291c50f5d0d8178a309093f2c8a0127dcd16b87cd047eae55da6845fcad65e7be0f
|
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -1,31 +1,176 @@
|
|
1
1
|
# Trailblazer::Loader
|
2
2
|
|
3
|
-
|
3
|
+
_Generic loader for Trailblazer projects._
|
4
4
|
|
5
|
-
|
5
|
+
Reportedly works with Rails, Grape, Lotus, and Roda, and many more, for sure.
|
6
6
|
|
7
|
-
|
7
|
+
**Rails users**: This gem is bundled with [trailblazer-rails](https://github.com/trailblazer/trailblazer-rails).
|
8
8
|
|
9
|
-
##
|
9
|
+
## Overview
|
10
|
+
|
11
|
+
While Trailblazer enforces a new file structure where you organize by concept, and not by technology, the naming and the structuring within each concept allows different styles.
|
12
|
+
|
13
|
+
Trailblazer-loader supports the following directory layouts concurrently.
|
14
|
+
|
15
|
+
## Compound-Singular
|
16
|
+
|
17
|
+
Per concept, you have one file per abstraction layer (called a _compound_ file). All is singular and reflects the namespace (except for operations which sit in the concept's namespace).
|
18
|
+
|
19
|
+
```
|
20
|
+
app
|
21
|
+
├── concepts
|
22
|
+
│ ├── comment
|
23
|
+
│ │ ├── callback.rb
|
24
|
+
│ │ ├── cell.rb
|
25
|
+
│ │ ├── contract.rb
|
26
|
+
│ │ ├── operation.rb
|
27
|
+
│ │ ├── policy.rb
|
28
|
+
│ │ └── views
|
29
|
+
│ │ ├── grid.haml
|
30
|
+
│ │ └── show.haml
|
31
|
+
```
|
32
|
+
|
33
|
+
You may nest concepts in concepts.
|
34
|
+
|
35
|
+
```
|
36
|
+
app
|
37
|
+
├── concepts
|
38
|
+
│ ├── comment
|
39
|
+
│ │ ├── contract.rb
|
40
|
+
│ │ ├── operation.rb
|
41
|
+
│ │ ├── admin
|
42
|
+
│ │ ├── contract.rb
|
43
|
+
│ │ └── operation.rb
|
44
|
+
```
|
45
|
+
|
46
|
+
Note: This is the structuring used in the [Trailblazer book](http://trailblazer.to/books/trailblazer.html).
|
47
|
+
|
48
|
+
|
49
|
+
## Explicit-Singular
|
10
50
|
|
11
|
-
Per concept
|
51
|
+
Per concept, you have one directory per abstraction layer and one file per class. All is singular and reflects the namespace (except for operations which sit in the concept's namespace).
|
12
52
|
|
13
53
|
```
|
14
|
-
app
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
54
|
+
app
|
55
|
+
├── concepts
|
56
|
+
│ ├── comment
|
57
|
+
│ │ ├── contract
|
58
|
+
│ │ │ ├── create.rb
|
59
|
+
│ │ │ └── update.rb
|
60
|
+
│ │ ├── cell
|
61
|
+
│ │ │ └── form.rb
|
62
|
+
│ │ ├── operation
|
63
|
+
│ │ │ ├── create.rb
|
64
|
+
│ │ │ └── update.rb
|
65
|
+
│ │ └── views
|
66
|
+
│ │ ├── grid.haml
|
67
|
+
│ │ └── show.haml
|
19
68
|
```
|
20
69
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
70
|
+
You may nest concepts in concepts.
|
71
|
+
|
72
|
+
```
|
73
|
+
app
|
74
|
+
├── concepts
|
75
|
+
│ ├── comment
|
76
|
+
│ │ ├── contract
|
77
|
+
│ │ │ ├── create.rb
|
78
|
+
│ │ │ └── update.rb
|
79
|
+
│ │ ├── operation
|
80
|
+
│ │ │ ├── create.rb
|
81
|
+
│ │ │ └── update.rb
|
82
|
+
│ │ ├── admin
|
83
|
+
│ │ │ └── contract
|
84
|
+
│ │ │ ├── create.rb
|
85
|
+
│ │ │ └── update.rb
|
86
|
+
```
|
87
|
+
|
88
|
+
## Explicit-Plural
|
89
|
+
|
90
|
+
Per concept, you have one pluralized directory per abstraction layer and one file per class.
|
91
|
+
|
92
|
+
```
|
93
|
+
app
|
94
|
+
├── concepts
|
95
|
+
│ ├── comment
|
96
|
+
│ │ ├── contracts
|
97
|
+
│ │ │ ├── create.rb
|
98
|
+
│ │ │ └── update.rb
|
99
|
+
│ │ ├── cells
|
100
|
+
│ │ │ └── form.rb
|
101
|
+
│ │ ├── operations
|
102
|
+
│ │ │ ├── create.rb
|
103
|
+
│ │ │ └── update.rb
|
104
|
+
│ │ └── views
|
105
|
+
│ │ ├── grid.haml
|
106
|
+
│ │ └── show.haml
|
107
|
+
```
|
108
|
+
|
109
|
+
And, yes, you may nest concepts in concepts.
|
110
|
+
|
111
|
+
```
|
112
|
+
app
|
113
|
+
├── concepts
|
114
|
+
│ ├── comment
|
115
|
+
│ │ ├── contracts
|
116
|
+
│ │ │ ├── create.rb
|
117
|
+
│ │ │ └── update.rb
|
118
|
+
│ │ ├── operation
|
119
|
+
│ │ │ ├── create.rb
|
120
|
+
│ │ │ └── update.rb
|
121
|
+
│ │ ├── admin
|
122
|
+
│ │ │ └── contracts
|
123
|
+
│ │ │ ├── create.rb
|
124
|
+
│ │ │ └── update.rb
|
125
|
+
```
|
126
|
+
|
127
|
+
|
128
|
+
## Loading order
|
129
|
+
|
130
|
+
The loading order is identical for all styles.
|
131
|
+
|
132
|
+
1. The loader finds all concept directories.
|
133
|
+
2. Concept directories are sorted by nesting level, deeper nestings are loaded later as they might reference concepts they're nested in. For example, `concepts/comment/admin` might reuse existing code from `concepts/comment`.
|
134
|
+
3. Per concept, files are lexically sorted, e.g. `create.rb` will be loaded *before* `update.rb` as we mostly do `Update < Create`.
|
135
|
+
4. Per concept, operation files will be loaded after all other layer files have been required. This is because abstraction files like representers or contracts should not reference their operation. The operation, howver, as an orchestrating asset needs to refer to various abstraction objects.
|
136
|
+
|
137
|
+
Here's a sample of a explicit-singular session.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
[
|
141
|
+
"app/concepts/navigation/cell.rb",
|
142
|
+
"app/concepts/session/impersonate.rb",
|
143
|
+
"app/concepts/session/operation.rb",
|
144
|
+
"app/concepts/user/operation.rb",
|
145
|
+
"app/concepts/comment/cell/cell.rb",
|
146
|
+
"app/concepts/comment/cell/grid.rb",
|
147
|
+
"app/concepts/comment/operation/create.rb",
|
148
|
+
"app/concepts/api/v1.rb",
|
149
|
+
"app/concepts/thing/callback/default.rb",
|
150
|
+
"app/concepts/thing/callback/upload.rb",
|
151
|
+
"app/concepts/thing/cell.rb",
|
152
|
+
"app/concepts/thing/cell/decorator.rb",
|
153
|
+
"app/concepts/thing/cell/form.rb",
|
154
|
+
"app/concepts/thing/cell/grid.rb",
|
155
|
+
"app/concepts/thing/contract/create.rb",
|
156
|
+
"app/concepts/thing/contract/update.rb",
|
157
|
+
"app/concepts/thing/policy.rb",
|
158
|
+
"app/concepts/thing/signed_in.rb",
|
159
|
+
"app/concepts/thing/operation/create.rb",
|
160
|
+
"app/concepts/thing/operation/delete.rb",
|
161
|
+
"app/concepts/thing/operation/show.rb",
|
162
|
+
"app/concepts/thing/operation/update.rb",
|
163
|
+
"app/concepts/api/v1/comment/representer/show.rb",
|
164
|
+
"app/concepts/api/v1/comment/operation/create.rb",
|
165
|
+
"app/concepts/api/v1/comment/operation/show.rb",
|
166
|
+
"app/concepts/api/v1/thing/representer/create.rb",
|
167
|
+
"app/concepts/api/v1/thing/representer/index.rb",
|
168
|
+
"app/concepts/api/v1/thing/representer/show.rb",
|
169
|
+
"app/concepts/api/v1/thing/operation/create.rb",
|
170
|
+
"app/concepts/api/v1/thing/operation/index.rb",
|
171
|
+
"app/concepts/api/v1/thing/operation/update.rb"
|
172
|
+
]
|
173
|
+
```
|
29
174
|
|
30
175
|
## Installation
|
31
176
|
|
@@ -35,55 +180,60 @@ Add this line to your application's Gemfile:
|
|
35
180
|
gem 'trailblazer-loader'
|
36
181
|
```
|
37
182
|
|
183
|
+
You do not need this step should you use one of the following binding gems.
|
184
|
+
|
185
|
+
* [trailblazer-rails](https://github.com/trailblazer/trailblazer-rails)
|
186
|
+
* [trailblazer-grape](https://github.com/trailblazer/trailblazer-grape)
|
187
|
+
* [trailblazer-roda](https://github.com/trailblazer/trailblazer-roda)
|
188
|
+
* [trailblazer-lotus](https://github.com/trailblazer/trailblazer-lotus)
|
189
|
+
|
38
190
|
## API
|
39
191
|
|
40
192
|
```ruby
|
41
|
-
Trailblazer::Loader.new.(
|
193
|
+
Trailblazer::Loader.new.() { |file| require_dependency(File.join(Rails.app.root, file)) }
|
42
194
|
```
|
43
195
|
|
196
|
+
`:concepts_root`
|
197
|
+
|
198
|
+
## Mixing
|
199
|
+
|
200
|
+
Note that you're free to mix these styles the way it feels right for your project.
|
44
201
|
|
45
|
-
|
202
|
+
For example, you can have compound files and explicit layout in one concept.
|
46
203
|
|
47
204
|
```
|
48
205
|
app
|
49
206
|
├── concepts
|
50
|
-
│
|
51
|
-
│
|
52
|
-
│
|
53
|
-
│
|
54
|
-
│
|
55
|
-
│ │ ├── representer.rb [optional]
|
56
|
-
│ │ ├── views
|
57
|
-
│ │ │ ├── show.haml
|
58
|
-
│ │ │ ├── list.haml
|
59
|
-
│ │ │ ├── comment.css.sass
|
207
|
+
│ ├── comment
|
208
|
+
│ │ ├── contract.rb - compound vs.
|
209
|
+
│ │ ├── operation - explicit directory
|
210
|
+
│ │ │ ├── create.rb
|
211
|
+
│ │ │ └── update.rb
|
60
212
|
```
|
61
213
|
|
62
|
-
|
214
|
+
## Namespacing Operations
|
63
215
|
|
64
|
-
|
216
|
+
Normally, operations in Trailblazer use the concept's namespace, e.g. `Comment::Create`, even though they can sit in an explicit file.
|
65
217
|
|
66
218
|
```
|
67
219
|
app
|
68
220
|
├── concepts
|
69
|
-
│
|
70
|
-
│
|
71
|
-
│
|
72
|
-
│
|
221
|
+
│ ├── comment
|
222
|
+
│ │ ├── operation - explicit directory
|
223
|
+
│ │ │ ├── create.rb - contains Comment::Create
|
224
|
+
│ │ │ └── update.rb
|
73
225
|
```
|
74
226
|
|
75
|
-
|
227
|
+
You are free to namespace your operations, if you like that better.
|
76
228
|
|
229
|
+
```ruby
|
230
|
+
module Comment::Operation
|
231
|
+
class Create < Trailblazer::Operation
|
77
232
|
```
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
│ │ │ │ ├── views
|
86
|
-
│ │ │ │ │ ├── homepage.haml
|
87
|
-
│ │ │ │ │ ├── search.haml
|
88
|
-
│ │ │ ├── collection
|
89
|
-
```
|
233
|
+
|
234
|
+
## Debugging
|
235
|
+
|
236
|
+
## Customizing
|
237
|
+
|
238
|
+
Trailblazer-loader allows you to inject your own sorting and filtering logic, should you refuse to go mainstream.
|
239
|
+
|
data/lib/trailblazer/loader.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "trailblazer/loader/version"
|
2
2
|
require "representable/pipeline"
|
3
|
+
require "pp"
|
3
4
|
|
4
5
|
module Trailblazer
|
5
6
|
class Loader
|
@@ -9,35 +10,55 @@ module Trailblazer
|
|
9
10
|
# NOTE: i will most probably use call_sheet and dry-container here soon.
|
10
11
|
def call(options={}, &block)
|
11
12
|
options[:concepts_root] ||= "app/concepts/"
|
13
|
+
options[:concept_dirs] = concept_dirs
|
12
14
|
|
13
15
|
pipeline = options[:pipeline] || Representable::Pipeline[
|
16
|
+
FindDirectories,
|
14
17
|
FindConcepts,
|
15
|
-
|
16
|
-
|
18
|
+
# PrintConcepts,
|
19
|
+
SortByLevel,
|
20
|
+
Representable::Collect[ConceptName, ConceptFiles, SortCreateFirst, SortOperationLast, AddConceptFiles] # per concept.
|
17
21
|
]
|
18
22
|
|
19
23
|
if args = options[:insert] # FIXME: this only implements a sub-set.
|
20
24
|
# pipeline = Representable::Pipeline::Insert.(pipeline, *args) # FIXME: implement :before in Pipeline.
|
21
|
-
pipeline[
|
25
|
+
pipeline[3].insert(pipeline[3].index(args.last[:before]), args.first)
|
22
26
|
end
|
23
27
|
|
24
28
|
files = pipeline.([], options).flatten
|
25
29
|
|
26
|
-
|
27
|
-
# pp files
|
30
|
+
pp files
|
28
31
|
|
29
32
|
load_files(files, &block)
|
30
33
|
end
|
31
34
|
|
32
|
-
|
33
|
-
|
35
|
+
def concept_dirs
|
36
|
+
%w{ callback cell contract operation policy representer view }
|
37
|
+
end
|
38
|
+
|
39
|
+
FindDirectories = ->(input, options) { Dir.glob("#{options[:concepts_root]}**/") }
|
40
|
+
# Filter out all directories containing /(callback|cell|contract|operation|policy|representer|view)/
|
41
|
+
FindConcepts = ->(input, options) { input.shift; input.reject { |dir| dir =~ /(#{options[:concept_dirs].join("|")})/ } }
|
42
|
+
PrintConcepts = ->(input, options) { puts " concepts: #{input.inspect}"; input }
|
43
|
+
|
44
|
+
# lame heuristic, but works for me: sort by directory levels.
|
34
45
|
# app/concepts/comment
|
35
46
|
# app/concepts/api/v1/comment
|
36
|
-
|
47
|
+
SortByLevel = ->(input, options) { input.sort { |a, b| a.split("/").size <=> b.split("/").size } }
|
37
48
|
# Extract concept name from path, e.g. /api/v1/comment.
|
38
49
|
ConceptName = ->(input, options) { options[:name] = input.sub(options[:concepts_root], "").chomp("/"); [] }
|
39
50
|
# Find all .rb files in one particular concept directory, e.g. as in /concepts/comment/*.rb.
|
40
|
-
ConceptFiles = ->(input, options)
|
51
|
+
ConceptFiles = ->(input, options) do
|
52
|
+
Dir.glob("#{options[:concepts_root]}#{options[:name]}/*.rb") + # .rb files directly in this concept.
|
53
|
+
Dir.glob("#{options[:concepts_root]}#{options[:name]}/*/*.rb"). # .rb in :concept/operation/*.rb
|
54
|
+
find_all { |file| file =~ /(#{options[:concept_dirs].join("|")})/ } # but only those, no sub-concepts!
|
55
|
+
end
|
56
|
+
|
57
|
+
# operation files should be loaded after callbacks, policies, and the like: [callback.rb, contract.rb, policy.rb, operation.rb]
|
58
|
+
SortOperationLast = ->(input, options) { input.sort { |a, b| (a =~ /operation/ && b !~ /operation/) ? 1 : -1 } }
|
59
|
+
SortCreateFirst = ->(input, options) { input.sort }
|
60
|
+
AddConceptFiles = ->(input, options) { input }
|
61
|
+
|
41
62
|
|
42
63
|
private
|
43
64
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer-loader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|