trailblazer 2.1.0.beta4 → 2.1.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +194 -502
- data/CHANGES.md +4 -0
- data/CONTRIBUTING.md +170 -0
- data/Gemfile +4 -1
- data/README.md +183 -40
- data/Rakefile +6 -2
- data/lib/trailblazer/version.rb +1 -1
- data/lib/trailblazer.rb +3 -12
- data/test/{operation/dsl → dsl}/contract_test.rb +2 -2
- data/trailblazer.gemspec +3 -3
- metadata +17 -63
- data/lib/trailblazer/operation/contract.rb +0 -82
- data/lib/trailblazer/operation/guard.rb +0 -18
- data/lib/trailblazer/operation/model.rb +0 -65
- data/lib/trailblazer/operation/nested.rb +0 -91
- data/lib/trailblazer/operation/persist.rb +0 -14
- data/lib/trailblazer/operation/policy.rb +0 -44
- data/lib/trailblazer/operation/pundit.rb +0 -38
- data/lib/trailblazer/operation/representer.rb +0 -36
- data/lib/trailblazer/operation/rescue.rb +0 -24
- data/lib/trailblazer/operation/validate.rb +0 -74
- data/lib/trailblazer/operation/wrap.rb +0 -64
- data/test/docs/contract_test.rb +0 -545
- data/test/docs/dry_test.rb +0 -31
- data/test/docs/guard_test.rb +0 -162
- data/test/docs/macro_test.rb +0 -36
- data/test/docs/model_test.rb +0 -75
- data/test/docs/nested_test.rb +0 -300
- data/test/docs/policy_test.rb +0 -2
- data/test/docs/pundit_test.rb +0 -133
- data/test/docs/representer_test.rb +0 -268
- data/test/docs/rescue_test.rb +0 -154
- data/test/docs/wrap_test.rb +0 -219
- data/test/nested_test.rb +0 -293
- data/test/operation/contract_test.rb +0 -290
- data/test/operation/dsl/representer_test.rb +0 -169
- data/test/operation/model_test.rb +0 -54
- data/test/operation/persist_test.rb +0 -51
- data/test/operation/pundit_test.rb +0 -106
- data/test/operation/representer_test.rb +0 -254
data/CHANGES.md
CHANGED
@@ -30,6 +30,10 @@ document Task API and define step API
|
|
30
30
|
deprecate step->(options) ?
|
31
31
|
injectable, per-operation step arguments strategy?
|
32
32
|
|
33
|
+
# 2.1.0.beta5
|
34
|
+
|
35
|
+
* All macros are now cleanly extracted to `trailblazer-macro` and `trailblazer-macro-contract`.
|
36
|
+
|
33
37
|
# 2.1.0.beta4
|
34
38
|
|
35
39
|
* Simple maintenance release to establish `activity-0.5.0`.
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# Contributing to Trailblazer
|
2
|
+
Trailblazer is an open source project and we would love you to help us make it better.
|
3
|
+
|
4
|
+
## Questions/Help
|
5
|
+
If our [guides][guides] nor [docs][api-docs] can help you figure things out, and you're stuck, refrain from posting questions on github issues, and please just find us on the [trailblazer gitter chat][chat] and drop us a line.
|
6
|
+
|
7
|
+
Keep in mind when asking questions though, an example will get you help faster than anything else you do.
|
8
|
+
|
9
|
+
If you file an issue with a question, it will be closed. We're not trying to be mean, don't get us wrong, we're just trying to stay sane.
|
10
|
+
|
11
|
+
## Reporting Issues
|
12
|
+
A well formatted issue is appreciated, and goes a long way in helping us help you.
|
13
|
+
|
14
|
+
* Make sure you have a [GitHub account](https://github.com/signup/free)
|
15
|
+
* Submit a [Github issue][issues-link] by:
|
16
|
+
* Clearly describing the issue
|
17
|
+
* Provide a descriptive summary
|
18
|
+
* Provide sample code where possible (preferably in the form of a test, in a [Gist][gist] for bonus points)
|
19
|
+
* Explain the expected behavior
|
20
|
+
* Explain the actual behavior
|
21
|
+
* Provide steps to reproduce the actual behavior
|
22
|
+
* Provide your application's complete `Gemfile.lock` as text (in a [Gist][gist] for bonus points)
|
23
|
+
* Any relevant stack traces
|
24
|
+
|
25
|
+
If you provide code, make sure it is formatted with the triple backticks (\`).
|
26
|
+
|
27
|
+
At this point, we'd love to tell you how long it will take for us to respond, but we just don't know.
|
28
|
+
|
29
|
+
## Pull requests
|
30
|
+
We accept pull requests to Trailblazer for:
|
31
|
+
|
32
|
+
* Adding documentation
|
33
|
+
* Fixing bugs
|
34
|
+
* Adding new features
|
35
|
+
|
36
|
+
Not all features proposed will be added but we are open to having a conversation about a feature you are championing.
|
37
|
+
|
38
|
+
###Here's a quick guide:
|
39
|
+
#### Fork the Project
|
40
|
+
Fork the [project repository][project-repo-link] on Github and check out your copy.
|
41
|
+
|
42
|
+
```
|
43
|
+
git clone https://github.com/YOUR_HANDLE/trailblazer.git
|
44
|
+
cd trailblazer
|
45
|
+
git remote add upstream https://github.com/trailblazer/trailblazer.git
|
46
|
+
```
|
47
|
+
|
48
|
+
#### Create a Topic Branch
|
49
|
+
Make sure your fork is up-to-date and create a topic branch for your feature or bug fix.
|
50
|
+
```
|
51
|
+
git checkout master
|
52
|
+
git pull upstream master
|
53
|
+
git checkout -b my-feature-branch
|
54
|
+
```
|
55
|
+
|
56
|
+
#### Bundle and Test
|
57
|
+
Run bundle install/update to gather any and all dependencies. Run the tests. This is to make sure your starting point works.
|
58
|
+
|
59
|
+
```
|
60
|
+
bundle install
|
61
|
+
bundle exec rake
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Write Tests
|
65
|
+
Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [test][test-link].
|
66
|
+
|
67
|
+
We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix.
|
68
|
+
|
69
|
+
#### Write Code
|
70
|
+
Implement your feature or bug fix.
|
71
|
+
|
72
|
+
Ruby style is enforced with [RuboCop](https://github.com/bbatsov/rubocop), run `bundle exec rubocop` and fix any style issues highlighted.
|
73
|
+
|
74
|
+
Make sure that `bundle exec rake` completes without errors.
|
75
|
+
|
76
|
+
#### Write Documentation
|
77
|
+
Document any external behavior in the [README](README.md).
|
78
|
+
|
79
|
+
#### Commit Changes
|
80
|
+
Make sure git knows your name and email address:
|
81
|
+
|
82
|
+
```
|
83
|
+
git config --global user.name "Your Name"
|
84
|
+
git config --global user.email "contributor@example.com"
|
85
|
+
```
|
86
|
+
|
87
|
+
Writing good commit logs is important. A commit log should describe what has changed and why, but be brief.
|
88
|
+
|
89
|
+
```
|
90
|
+
git add your_filename.rb (File names to add content from, or fileglobs e.g. *.rb)
|
91
|
+
git commit
|
92
|
+
```
|
93
|
+
|
94
|
+
#### Push
|
95
|
+
```
|
96
|
+
git push origin my-feature-branch
|
97
|
+
```
|
98
|
+
|
99
|
+
#### Make a Pull Request
|
100
|
+
Go to https://github.com/YOUR_GH_HANDLE/trailblazer and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days, but no need to rush us if it takes longer.
|
101
|
+
|
102
|
+
#### Rebase
|
103
|
+
If you've been working on a change for a while, rebase with upstream/master.
|
104
|
+
|
105
|
+
```
|
106
|
+
git fetch upstream
|
107
|
+
git rebase upstream/master
|
108
|
+
git push origin my-feature-branch -f
|
109
|
+
```
|
110
|
+
|
111
|
+
#### Update CHANGELOG Again
|
112
|
+
Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows.
|
113
|
+
|
114
|
+
```
|
115
|
+
* [#123](https://github.com/trailblazer/trailblazer/pull/123): Your brief description - [@your_gh_handle](https://github.com/your_gh_handle).
|
116
|
+
```
|
117
|
+
|
118
|
+
Amend your previous commit and force push the changes.
|
119
|
+
|
120
|
+
```
|
121
|
+
git commit --amend
|
122
|
+
git push origin my-feature-branch -f
|
123
|
+
```
|
124
|
+
|
125
|
+
#### Check on Your Pull Request
|
126
|
+
Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above.
|
127
|
+
|
128
|
+
## Releasing
|
129
|
+
|
130
|
+
When you have release rights, please follow these rules.
|
131
|
+
|
132
|
+
* When tagging a commit for a release, use the format `vX.X.X` for the tag, e.g. `git tag v2.1.0`.
|
133
|
+
* The tagged commit **must contain the line** "Releasing vX.X.X" so it can be quickly spotted later in the commit list.
|
134
|
+
|
135
|
+
#### What now?
|
136
|
+
At this point you're waiting on us. Expect a conversation regarding your pull request; Questions, clarifications, and so on.
|
137
|
+
|
138
|
+
Some things that will increase the chance that your pull request is accepted:
|
139
|
+
* Use Trailblazer idioms and follow the Trailblazer idealogy
|
140
|
+
* Include tests that fail without your code, and pass with it
|
141
|
+
* Update the documentation, guides, etc.
|
142
|
+
|
143
|
+
## What do we need help with?
|
144
|
+
### Helping others!
|
145
|
+
There are a lot of questions from people as they get started using Trailblazer. If you could **please do the following things**, that would really help:
|
146
|
+
|
147
|
+
- Hang out on [the chat][chat]
|
148
|
+
- Watch the [trailblazer repositories][repositories] for issues or requests that you could help with
|
149
|
+
|
150
|
+
### Contributing to community
|
151
|
+
- Create Macros!
|
152
|
+
- Write blog posts!
|
153
|
+
- Record screencasts
|
154
|
+
- Write examples.
|
155
|
+
|
156
|
+
### Contributing to the core
|
157
|
+
- Tests are always helpful!
|
158
|
+
- Any of the issues in GitHub, let us know if you have some time to fix one.
|
159
|
+
|
160
|
+
## Thank You
|
161
|
+
Please do know that we really appreciate and value your time and work.
|
162
|
+
|
163
|
+
[gist]: https://gist.github.com
|
164
|
+
[guides]: https://www.trailblazer.to
|
165
|
+
[api-docs]: https://www.trailblazer.to/api-docs
|
166
|
+
[chat]: https://gitter.im/trailblazer/chat
|
167
|
+
[repositories]: https://github.com/trailblazer
|
168
|
+
[test-link]: https://github.com/trailblazer/trailblazer/tree/master/test
|
169
|
+
[project-repo-link]: https://github.com/trailblazer/trailblazer
|
170
|
+
[issues-link]: https://www.github.com/trailblazer/trailblazer/issues
|
data/Gemfile
CHANGED
@@ -14,13 +14,16 @@ gem "dry-auto_inject"
|
|
14
14
|
gem "dry-matcher"
|
15
15
|
gem "dry-validation"
|
16
16
|
|
17
|
+
# gem "trailblazer-operation", path: "../operation"
|
18
|
+
gem "trailblazer-macro", github: "trailblazer/trailblazer-macro"
|
19
|
+
gem "trailblazer-macro-contract", github: "trailblazer/trailblazer-macro-contract"
|
17
20
|
gem "trailblazer-operation", path: "../operation"
|
18
21
|
# gem "trailblazer-operation", github: "trailblazer/trailblazer-operation"
|
19
22
|
|
20
23
|
gem "minitest-line"
|
21
24
|
|
22
25
|
gem "rubocop", require: false
|
23
|
-
|
26
|
+
gem "trailblazer-activity", path: "../trailblazer-circuit"
|
24
27
|
# gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
|
25
28
|
# gem "trailblazer-activity", path: "../trailblazer-circuit"
|
26
29
|
# gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
|
data/README.md
CHANGED
@@ -42,7 +42,7 @@ Trailblazer offers you a new, more intuitive file layout in applications.
|
|
42
42
|
```
|
43
43
|
app
|
44
44
|
├── concepts
|
45
|
-
│ ├──
|
45
|
+
│ ├── song
|
46
46
|
│ │ ├── operation
|
47
47
|
│ │ │ ├── create.rb
|
48
48
|
│ │ │ ├── update.rb
|
@@ -55,7 +55,7 @@ app
|
|
55
55
|
│ │ ├── view
|
56
56
|
│ │ │ ├── show.haml
|
57
57
|
│ │ │ ├── index.rb
|
58
|
-
│ │ │ ├──
|
58
|
+
│ │ │ ├── song.css.sass
|
59
59
|
```
|
60
60
|
|
61
61
|
Instead of grouping by technology, classes and views are structured by *concept*, and then by technology. A concept can relate to a model, or can be a completely abstract concern such as `invoicing`.
|
@@ -81,7 +81,7 @@ Trailblazer uses Rails routing to map URLs to controllers, because it works.
|
|
81
81
|
|
82
82
|
```ruby
|
83
83
|
Rails.application.routes.draw do
|
84
|
-
resources :
|
84
|
+
resources :songs
|
85
85
|
end
|
86
86
|
```
|
87
87
|
|
@@ -90,9 +90,9 @@ end
|
|
90
90
|
Controllers are lean endpoints for HTTP. They do not contain any business logic. Actions immediately dispatch to an operation.
|
91
91
|
|
92
92
|
```ruby
|
93
|
-
class
|
93
|
+
class SongsController < ApplicationController
|
94
94
|
def create
|
95
|
-
run
|
95
|
+
run Song::Create # Song::Create is an operation class.
|
96
96
|
end
|
97
97
|
end
|
98
98
|
```
|
@@ -100,10 +100,10 @@ end
|
|
100
100
|
The `#run` method invokes the operation. It allows you to run a conditional block of logic if the operation was successful.
|
101
101
|
|
102
102
|
```ruby
|
103
|
-
class
|
103
|
+
class SongsController < ApplicationController
|
104
104
|
def create
|
105
|
-
run
|
106
|
-
return redirect_to(
|
105
|
+
run Song::Create do |op|
|
106
|
+
return redirect_to(song_path op.model) # success!
|
107
107
|
end
|
108
108
|
|
109
109
|
render :new # invalid. re-render form.
|
@@ -126,7 +126,7 @@ Operations don't know about HTTP or the environment. You could use an operation
|
|
126
126
|
An operation is not just a monolithic replacement for your business code. It's a simple orchestrator between the form objects, models, your business code and all other layers needed to get the job done.
|
127
127
|
|
128
128
|
```ruby
|
129
|
-
class
|
129
|
+
class Song::Create < Trailblazer::Operation
|
130
130
|
step :process!
|
131
131
|
|
132
132
|
def process!(options)
|
@@ -140,44 +140,188 @@ Operations only need to define and implement steps, like the `#process!` steps.
|
|
140
140
|
You cannot instantiate them per design. The only way to invoke them is `call`.
|
141
141
|
|
142
142
|
```ruby
|
143
|
-
|
143
|
+
Song::Create.call(whatever: "goes", in: "here")
|
144
144
|
# same as
|
145
|
-
|
145
|
+
Song::Create.(whatever: "goes", in: "here")
|
146
146
|
```
|
147
147
|
|
148
148
|
Their high degree of encapsulation makes them a [replacement for test factories](#test), too.
|
149
149
|
|
150
150
|
[Learn more.](http://trailblazer.to/gems/operation)
|
151
151
|
|
152
|
-
|
152
|
+
### Contract
|
153
|
+
The Contract Macro, covers the contracts for Trailblazer, they are basically Reform objects that you can define and validate inside an operation. Reform is a fantastic tool for deserializing and validating deeply nested hashes, and then, when valid, writing those to the database using your persistence layer such as ActiveRecord.
|
153
154
|
|
154
|
-
|
155
|
+
```ruby
|
156
|
+
# app/concepts/song/contract/create.rb
|
157
|
+
module Song::Contract
|
158
|
+
class Create < Reform::Form
|
159
|
+
property :title
|
160
|
+
property :length
|
161
|
+
|
162
|
+
validates :title, length: 2..33
|
163
|
+
validates :length, numericality: true
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
155
167
|
|
156
|
-
|
168
|
+
The Contract then gets hooked into the operation. using this Macro.
|
169
|
+
```ruby
|
170
|
+
# app/concepts/song/operation/create.rb
|
171
|
+
class Song::Create < Trailblazer::Operation
|
172
|
+
step Model( Song, :new )
|
173
|
+
step Contract::Build( constant: Song::Contract::Create )
|
174
|
+
step Contract::Validate()
|
175
|
+
step Contract::Persist()
|
176
|
+
end
|
177
|
+
```
|
178
|
+
As you can see, using contracts consists of five steps.
|
157
179
|
|
158
|
-
|
180
|
+
Define the contract class (or multiple of them) for the operation.
|
181
|
+
Plug the contract creation into the operation’s pipe using Contract::Build.
|
182
|
+
Run the contract’s validation for the params using Contract::Validate.
|
183
|
+
If successful, write the sane data to the model(s). This will usually happen in the Contract::Persist macro.
|
184
|
+
After the operation has been run, interpret the result. For instance, a controller calling an operation will render a erroring form for invalid input.
|
159
185
|
|
186
|
+
Here’s what the result would look like after running the Create operation with invalid data.
|
160
187
|
```ruby
|
161
|
-
|
162
|
-
|
188
|
+
result = Song::Create.( title: "A" )
|
189
|
+
result.success? #=> false
|
190
|
+
result["contract.default"].errors.messages
|
191
|
+
#=> {:title=>["is too short (minimum is 2 characters)"], :length=>["is not a number"]}
|
192
|
+
```
|
163
193
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
194
|
+
#### Build
|
195
|
+
The Contract::Build macro helps you to instantiate the contract. It is both helpful for a complete workflow, or to create the contract, only, without validating it, e.g. when presenting the form.
|
196
|
+
```ruby
|
197
|
+
class Song::New < Trailblazer::Operation
|
198
|
+
step Model( Song, :new )
|
199
|
+
step Contract::Build( constant: Song::Contract::Create )
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
This macro will grab the model from options["model"] and pass it into the contract’s constructor. The contract is then saved in options["contract.default"].
|
204
|
+
```ruby
|
205
|
+
result = Song::New.()
|
206
|
+
result["model"] #=> #<struct Song title=nil, length=nil>
|
207
|
+
result["contract.default"]
|
208
|
+
#=> #<Song::Contract::Create model=#<struct Song title=nil, length=nil>>
|
209
|
+
```
|
210
|
+
The Build macro accepts the :name option to change the name from default.
|
168
211
|
|
169
|
-
|
170
|
-
|
171
|
-
|
212
|
+
#### Validation
|
213
|
+
The Contract::Validate macro is responsible for validating the incoming params against its contract. That means you have to use Contract::Build beforehand, or create the contract yourself. The macro will then grab the params and throw then into the contract’s validate (or call) method.
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
class Song::ValidateOnly < Trailblazer::Operation
|
217
|
+
step Model( Song, :new )
|
218
|
+
step Contract::Build( constant: Song::Contract::Create )
|
219
|
+
step Contract::Validate()
|
220
|
+
end
|
221
|
+
```
|
222
|
+
Depending on the outcome of the validation, it either stays on the right track, or deviates to left, skipping the remaining steps.
|
223
|
+
```ruby
|
224
|
+
result = Song::ValidateOnly.({}) # empty params
|
225
|
+
result.success? #=> false
|
226
|
+
```
|
227
|
+
|
228
|
+
Note that Validate really only validates the contract, nothing is written to the model, yet. You need to push data to the model manually, e.g. with Contract::Persist.
|
229
|
+
```ruby
|
230
|
+
result = Song::ValidateOnly.({ title: "Rising Force", length: 13 })
|
231
|
+
|
232
|
+
result.success? #=> true
|
233
|
+
result["model"] #=> #<struct Song title=nil, length=nil>
|
234
|
+
result["contract.default"].title #=> "Rising Force"
|
235
|
+
```
|
236
|
+
|
237
|
+
Validate will use options["params"] as the input. You can change the nesting with the :key option.
|
238
|
+
|
239
|
+
Internally, this macro will simply call Form#validate on the Reform object.
|
240
|
+
|
241
|
+
Note: Reform comes with sophisticated deserialization semantics for nested forms, it might be worth reading a bit about Reform to fully understand what you can do in the Validate step.
|
242
|
+
|
243
|
+
##### Key
|
244
|
+
Per default, Contract::Validate will use options["params"] as the data to be validated. Use the key: option if you want to validate a nested hash from the original params structure.
|
245
|
+
```ruby
|
246
|
+
class Song::Create < Trailblazer::Operation
|
247
|
+
step Model( Song, :new )
|
248
|
+
step Contract::Build( constant: Song::Contract::Create )
|
249
|
+
step Contract::Validate( key: "song" )
|
172
250
|
step Contract::Persist( )
|
173
251
|
end
|
174
252
|
```
|
175
253
|
|
176
|
-
|
254
|
+
This automatically extracts the nested "song" hash.
|
255
|
+
```ruby
|
256
|
+
result = Song::Create.({ "song" => { title: "Rising Force", length: 13 } })
|
257
|
+
result.success? #=> true
|
258
|
+
```
|
259
|
+
|
260
|
+
If that key isn’t present in the params hash, the operation fails before the actual validation.
|
261
|
+
```ruby
|
262
|
+
result = Song::Create.({ title: "Rising Force", length: 13 })
|
263
|
+
result.success? #=> false
|
264
|
+
```
|
265
|
+
|
266
|
+
Note: String vs. symbol do matter here since the operation will simply do a hash lookup using the key you provided.
|
267
|
+
|
268
|
+
#### Persist
|
269
|
+
To push validated data from the contract to the model(s), use Persist. Like Validate, this requires a contract to be set up beforehand.
|
270
|
+
```ruby
|
271
|
+
class Song::Create < Trailblazer::Operation
|
272
|
+
step Model( Song, :new )
|
273
|
+
step Contract::Build( constant: Song::Contract::Create )
|
274
|
+
step Contract::Validate()
|
275
|
+
step Contract::Persist()
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
After the step, the contract’s attribute values are written to the model, and the contract will call save on the model.
|
280
|
+
```ruby
|
281
|
+
result = Song::Create.( title: "Rising Force", length: 13 )
|
282
|
+
result.success? #=> true
|
283
|
+
result["model"] #=> #<Song title="Rising Force", length=13>
|
284
|
+
```
|
285
|
+
|
286
|
+
You can also configure the Persist step to call sync instead of Reform’s save.
|
287
|
+
```ruby
|
288
|
+
step Persist( method: :sync )
|
289
|
+
```
|
290
|
+
This will only write the contract’s data to the model without calling save on it.
|
291
|
+
|
292
|
+
##### Name
|
293
|
+
Explicit naming for the contract is possible, too.
|
294
|
+
```ruby
|
295
|
+
|
296
|
+
class Song::Create < Trailblazer::Operation
|
297
|
+
step Model( Song, :new )
|
298
|
+
step Contract::Build( name: "form", constant: Song::Contract::Create )
|
299
|
+
step Contract::Validate( name: "form" )
|
300
|
+
step Contract::Persist( name: "form" )
|
301
|
+
end
|
302
|
+
```
|
177
303
|
|
178
|
-
|
304
|
+
You have to use the name: option to tell each step what contract to use. The contract and its result will now use your name instead of default.
|
305
|
+
```ruby
|
306
|
+
result = Song::Create.({ title: "A" })
|
307
|
+
result["contract.form"].errors.messages #=> {:title=>["is too short (minimum is 2 ch...
|
308
|
+
```
|
179
309
|
|
180
|
-
|
310
|
+
Use this if your operation has multiple contracts.
|
311
|
+
|
312
|
+
#### Result Object
|
313
|
+
The operation will store the validation result for every contract in its own result object.
|
314
|
+
|
315
|
+
The path is result.contract.#{name}.
|
316
|
+
```ruby
|
317
|
+
result = Create.({ length: "A" })
|
318
|
+
|
319
|
+
result["result.contract.default"].success? #=> false
|
320
|
+
result["result.contract.default"].errors #=> Errors object
|
321
|
+
result["result.contract.default"].errors.messages #=> {:length=>["is not a number"]}
|
322
|
+
```
|
323
|
+
|
324
|
+
Each result object responds to success?, failure?, and errors, which is an Errors object.
|
181
325
|
|
182
326
|
## Models
|
183
327
|
|
@@ -186,7 +330,7 @@ Models for persistence can be implemented using any ORM you fancy, for instance
|
|
186
330
|
In Trailblazer, models are completely empty. They solely contain associations and finders. No business logic is allowed in models.
|
187
331
|
|
188
332
|
```ruby
|
189
|
-
class
|
333
|
+
class Song < ActiveRecord::Base
|
190
334
|
belongs_to :thing
|
191
335
|
|
192
336
|
scope :latest, lambda { all.limit(9).order("id DESC") }
|
@@ -200,9 +344,9 @@ Only operations and views/cells can access models directly.
|
|
200
344
|
You can abort running an operation using a policy. "[Pundit](https://github.com/elabs/pundit)-style" policy classes define the rules.
|
201
345
|
|
202
346
|
```ruby
|
203
|
-
class
|
204
|
-
def initialize(user,
|
205
|
-
@user, @
|
347
|
+
class Song::Policy
|
348
|
+
def initialize(user, song)
|
349
|
+
@user, @song = user, song
|
206
350
|
end
|
207
351
|
|
208
352
|
def create?
|
@@ -214,8 +358,8 @@ end
|
|
214
358
|
The rule is enabled via the `::policy` call.
|
215
359
|
|
216
360
|
```ruby
|
217
|
-
class
|
218
|
-
step Policy(
|
361
|
+
class Song::Create < Trailblazer::Operation
|
362
|
+
step Policy( Song::Policy, :create? )
|
219
363
|
end
|
220
364
|
```
|
221
365
|
|
@@ -233,9 +377,9 @@ More complex UI logic happens in _View Models_ as found in [Cells](https://githu
|
|
233
377
|
The operation's form object can be rendered in views, too.
|
234
378
|
|
235
379
|
```ruby
|
236
|
-
class
|
380
|
+
class SongsController < ApplicationController
|
237
381
|
def new
|
238
|
-
form
|
382
|
+
form Song::Create # will assign the form object to @form.
|
239
383
|
end
|
240
384
|
end
|
241
385
|
```
|
@@ -255,12 +399,12 @@ Operations can use representers from [Roar](https://github.com/apotonick/roar) t
|
|
255
399
|
Representers can be inferred automatically from your contract, then may be refined, e.g. with hypermedia or a format like `JSON-API`.
|
256
400
|
|
257
401
|
```ruby
|
258
|
-
class
|
402
|
+
class Song::Create < Trailblazer::Operation
|
259
403
|
representer do
|
260
404
|
# inherited :body
|
261
405
|
include Roar::JSON::HAL
|
262
406
|
|
263
|
-
link(:self) {
|
407
|
+
link(:self) { song_path(represented.id) }
|
264
408
|
end
|
265
409
|
end
|
266
410
|
```
|
@@ -276,8 +420,8 @@ In Trailblazer, you only have operation unit tests and integration smoke tests t
|
|
276
420
|
Operations completely replace the need for leaky factories.
|
277
421
|
|
278
422
|
```ruby
|
279
|
-
describe
|
280
|
-
let(:
|
423
|
+
describe Song::Update do
|
424
|
+
let(:song) { Song::Create.(song: {body: "[That](http://trailblazer.to)!"}) }
|
281
425
|
end
|
282
426
|
```
|
283
427
|
|
@@ -302,4 +446,3 @@ gem "trailblazer-cells"
|
|
302
446
|
```
|
303
447
|
|
304
448
|
Cells is _not_ required per default! Add it if you use it, which is highly recommended.
|
305
|
-
|
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require "bundler/gem_tasks"
|
|
2
2
|
require "rake/testtask"
|
3
3
|
require "rubocop/rake_task"
|
4
4
|
|
5
|
-
task :default => [
|
5
|
+
task :default => %i[test rubocop]
|
6
6
|
|
7
7
|
Rake::TestTask.new(:test) do |test|
|
8
8
|
test.libs << 'test'
|
@@ -16,4 +16,8 @@ Rake::TestTask.new(:testdep) do |test|
|
|
16
16
|
test.verbose = true
|
17
17
|
end
|
18
18
|
|
19
|
-
RuboCop::RakeTask.new
|
19
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
20
|
+
task.patterns = ['lib/**/*.rb', 'test/**/*.rb']
|
21
|
+
task.options << "--display-cop-names"
|
22
|
+
task.fail_on_error = false
|
23
|
+
end
|
data/lib/trailblazer/version.rb
CHANGED
data/lib/trailblazer.rb
CHANGED
@@ -6,17 +6,8 @@ require "trailblazer/dsl"
|
|
6
6
|
require "trailblazer/task"
|
7
7
|
|
8
8
|
require "trailblazer/operation/deprecations"
|
9
|
-
|
10
|
-
require "trailblazer/operation/model"
|
11
|
-
require "trailblazer/operation/contract"
|
12
|
-
require "trailblazer/operation/validate"
|
13
|
-
require "trailblazer/operation/representer"
|
14
|
-
require "trailblazer/operation/policy"
|
15
|
-
require "trailblazer/operation/pundit"
|
16
|
-
require "trailblazer/operation/guard"
|
17
|
-
require "trailblazer/operation/persist"
|
18
|
-
require "trailblazer/operation/nested"
|
19
|
-
require "trailblazer/operation/wrap"
|
20
|
-
require "trailblazer/operation/rescue"
|
21
9
|
require "trailblazer/operation/inject"
|
22
10
|
require "trailblazer/operation/input_output"
|
11
|
+
|
12
|
+
require "trailblazer/macro"
|
13
|
+
require "trailblazer/macro/contract"
|
@@ -38,7 +38,7 @@ class DslContractTest < MiniTest::Spec
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# no inheritance with setter.
|
41
|
-
it { CreateOrFind["contract.default.class"].
|
41
|
+
it { CreateOrFind["contract.default.class"].must_equal nil }
|
42
42
|
|
43
43
|
# ---
|
44
44
|
# Op::contract Constant
|
@@ -197,7 +197,7 @@ class DslContractTest < MiniTest::Spec
|
|
197
197
|
form.sync
|
198
198
|
|
199
199
|
song.genre.must_equal "Punkrock"
|
200
|
-
song.band.
|
200
|
+
song.band.must_equal nil
|
201
201
|
end
|
202
202
|
end
|
203
203
|
|
data/trailblazer.gemspec
CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_dependency "trailblazer-operation", ">= 0.2.
|
21
|
-
spec.add_dependency "trailblazer-
|
20
|
+
spec.add_dependency "trailblazer-operation", ">= 0.2.4", "< 0.3.0"
|
21
|
+
spec.add_dependency "trailblazer-macro", ">= 2.1.0.beta2", "< 2.2.0"
|
22
|
+
spec.add_dependency "trailblazer-macro-contract", ">= 2.1.0.beta2", "< 2.2.0"
|
22
23
|
|
23
|
-
spec.add_dependency "reform", ">= 2.2.0", "< 3.0.0"
|
24
24
|
spec.add_dependency "declarative"
|
25
25
|
|
26
26
|
spec.add_development_dependency "activemodel" # for Reform::AM::V
|