trailblazer 1.1.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
  4. data/.rubocop.yml +20 -0
  5. data/.rubocop_todo.yml +556 -0
  6. data/.travis.yml +6 -7
  7. data/CHANGES.md +224 -0
  8. data/COMM-LICENSE +62 -0
  9. data/CONTRIBUTING.md +179 -0
  10. data/Gemfile +0 -10
  11. data/LICENSE +9 -0
  12. data/README.md +68 -189
  13. data/Rakefile +3 -3
  14. data/lib/trailblazer/version.rb +3 -1
  15. data/lib/trailblazer.rb +3 -6
  16. data/test/test_helper.rb +32 -3
  17. data/trailblazer.gemspec +11 -15
  18. metadata +28 -132
  19. data/LICENSE.txt +0 -22
  20. data/TODO.md +0 -11
  21. data/doc/Trb-The-Stack.png +0 -0
  22. data/doc/trb.jpg +0 -0
  23. data/gemfiles/Gemfile.rails.lock +0 -130
  24. data/gemfiles/Gemfile.reform-2.0 +0 -6
  25. data/gemfiles/Gemfile.reform-2.1 +0 -7
  26. data/lib/trailblazer/autoloading.rb +0 -15
  27. data/lib/trailblazer/endpoint.rb +0 -31
  28. data/lib/trailblazer/operation/builder.rb +0 -26
  29. data/lib/trailblazer/operation/callback.rb +0 -53
  30. data/lib/trailblazer/operation/collection.rb +0 -6
  31. data/lib/trailblazer/operation/controller.rb +0 -72
  32. data/lib/trailblazer/operation/dispatch.rb +0 -3
  33. data/lib/trailblazer/operation/model/dsl.rb +0 -29
  34. data/lib/trailblazer/operation/model/external.rb +0 -34
  35. data/lib/trailblazer/operation/model.rb +0 -50
  36. data/lib/trailblazer/operation/module.rb +0 -29
  37. data/lib/trailblazer/operation/policy/guard.rb +0 -35
  38. data/lib/trailblazer/operation/policy.rb +0 -85
  39. data/lib/trailblazer/operation/representer.rb +0 -98
  40. data/lib/trailblazer/operation/resolver.rb +0 -30
  41. data/lib/trailblazer/operation/uploaded_file.rb +0 -77
  42. data/lib/trailblazer/operation.rb +0 -175
  43. data/test/callback_test.rb +0 -104
  44. data/test/collection_test.rb +0 -57
  45. data/test/model_test.rb +0 -148
  46. data/test/module_test.rb +0 -93
  47. data/test/operation/builder_test.rb +0 -41
  48. data/test/operation/contract_test.rb +0 -30
  49. data/test/operation/controller_test.rb +0 -111
  50. data/test/operation/dsl/callback_test.rb +0 -118
  51. data/test/operation/dsl/contract_test.rb +0 -104
  52. data/test/operation/dsl/representer_test.rb +0 -142
  53. data/test/operation/external_model_test.rb +0 -71
  54. data/test/operation/guard_test.rb +0 -152
  55. data/test/operation/policy_test.rb +0 -97
  56. data/test/operation/reject_test.rb +0 -34
  57. data/test/operation/resolver_test.rb +0 -83
  58. data/test/operation_test.rb +0 -275
  59. data/test/representer_test.rb +0 -238
  60. data/test/rollback_test.rb +0 -47
data/README.md CHANGED
@@ -1,25 +1,34 @@
1
1
  # Trailblazer
2
2
 
3
- _Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation, an intuitive code structure and gives you an object-oriented architecture._
3
+ _Trailblazer provides new high-level abstractions for Ruby frameworks. It gently enforces encapsulation, an intuitive code structure and approaches the modeling of complex business workflows with a functional mind-set._
4
4
 
5
5
  [![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat)
6
6
  [![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)
7
7
  [![Gem Version](https://badge.fury.io/rb/trailblazer.svg)](http://badge.fury.io/rb/trailblazer)
8
+ [![Open Source Helpers](https://www.codetriage.com/trailblazer/trailblazer/badges/users.svg)](https://www.codetriage.com/trailblazer/trailblazer)
8
9
 
10
+ ## Documentation
11
+
12
+ **This document discusses Trailblazer 2.1.** An overview about the additions are [on our website](http://2019.trailblazer.to/2.1/docs/trailblazer.html#trailblazer-2-1-migration).
13
+
14
+ We're working on several new example applications!
15
+
16
+ * *Refactoring to Trailblazer* discusses how the cfp-app is converted into a TRB app.
17
+ * *BPMN and workflows* shows in-detail how the new 2.1 features in Trailblazer are used.
18
+
19
+ The [1.x documentation is here](http://trailblazer.to/gems/operation/1.1/).
9
20
 
10
21
  ## Trailblazer In A Nutshell
11
22
 
12
23
  1. All business logic is encapsulated in [operations](#operation) (service objects).
13
- * An optional Reform [form](#validations) object in the operation deserializes and validates input. The form object can also be used for rendering.
14
- * An optional [policy](#policies) object blocks unauthorized users from running the operation.
15
- * Optional [callback](#callbacks) objects allow declaring post-processing logic.
16
24
  3. [Controllers](#controllers) instantly delegate to an operation. No business code in controllers, only HTTP-specific logic.
17
25
  4. [Models](#models) are persistence-only and solely define associations and scopes. No business code is to be found here. No validations, no callbacks.
18
26
  5. The presentation layer offers optional [view models](#views) (Cells) and [representers](#representers) for document APIs.
27
+ 6. More complex business flows and life-cycles are modeled using workflows.
19
28
 
20
29
  Trailblazer is designed to handle different contexts like user roles by applying [inheritance](#inheritance) between and [composing](#composing) of operations, form objects, policies, representers and callbacks.
21
30
 
22
- Wanna see some code? Jump [right here](#controllers)!
31
+ Want code? Jump [right here](#controllers)!
23
32
 
24
33
  ## Mission
25
34
 
@@ -31,36 +40,33 @@ Again, you can pick which layers you want. Trailblazer doesn't impose technical
31
40
 
32
41
  Trailblazer is no "complex web of objects and indirection". It solves many problems that have been around for years with a cleanly layered architecture. Only use what you like. And that's the bottom line.
33
42
 
34
- ## Trailblazer Likes 'Em All
35
-
36
- Since Trailblazer decouples the High-Level Stack from the framework, it runs with virtually any Ruby framework. We are constantly working on documenting how to do that.
37
-
38
- * Trailblazer with Rails [Book](http://trailblazer.to/books/trailblazer.html) | [Repository](https://github.com/apotonick/gemgem-trbrb)
39
- * Trailblazer with Sinatra [Guide](http://trailblazer.to/guides/sinatra/getting-started.html) | [Repository](https://github.com/apotonick/gemgem-sinatra)
40
- * Trailblazer with Hanami - coming soon!
41
- * Trailblazer with Roda - coming soon!
42
- * Trailblazer with Grape - coming _very_ soon!
43
+ ## Concepts over Technology
43
44
 
44
-
45
- ## A Concept-Driven OOP Framework
46
-
47
- Trailblazer offers you a new, more intuitive file layout in Rails apps where you structure files by *concepts*.
45
+ Trailblazer offers you a new, more intuitive file layout in applications.
48
46
 
49
47
  ```
50
48
  app
51
49
  ├── concepts
52
- │ ├── comment
53
- │ │ ├── cell.rb
54
- │ │ ├── views
50
+ │ ├── song
51
+ │ │ ├── operation
52
+ │ │ ├── create.rb
53
+ │ │ │ ├── update.rb
54
+ │ │ ├── contract
55
+ │ │ │ ├── create.rb
56
+ │ │ │ ├── update.rb
57
+ │ │ ├── cell
58
+ │ │ │ ├── show.rb
59
+ │ │ │ ├── index.rb
60
+ │ │ ├── view
55
61
  │ │ │ ├── show.haml
56
- │ │ │ ├── list.haml
57
- │ │ ├── assets
58
- │ │ │ ├── comment.css.sass
59
- │ │ ├── operation.rb
60
- │ │ ├── twin.rb
62
+ │ │ │ ├── index.rb
63
+ │ │ ├── song.css.sass
61
64
  ```
62
65
 
63
- Files, classes and views that logically belong to one _concept_ are kept in one place. You are free to use additional namespaces within a concept. Trailblazer tries to keep it as simple as possible, though.
66
+ 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`.
67
+
68
+ Within a concept, you can have any level of nesting. For example, `invoicing/pdf/` could be one.
69
+
64
70
 
65
71
  ## Architecture
66
72
 
@@ -68,7 +74,7 @@ Trailblazer extends the conventional MVC stack in Rails. Keep in mind that addin
68
74
 
69
75
  The opposite is the case: Controller, view and model become lean endpoints for HTTP, rendering and persistence. Redundant code gets eliminated by putting very little application code into the right layer.
70
76
 
71
- ![The Trailblazer stack.](https://raw.github.com/apotonick/trailblazer/master/doc/Trb-The-Stack.png)
77
+ ![The Trailblazer stack.](https://raw.github.com/apotonick/trailblazer/master/doc/operation-2017.png)
72
78
 
73
79
  ## Routing
74
80
 
@@ -76,7 +82,7 @@ Trailblazer uses Rails routing to map URLs to controllers, because it works.
76
82
 
77
83
  ```ruby
78
84
  Rails.application.routes.draw do
79
- resources :comments
85
+ resources :songs
80
86
  end
81
87
  ```
82
88
 
@@ -85,23 +91,25 @@ end
85
91
  Controllers are lean endpoints for HTTP. They do not contain any business logic. Actions immediately dispatch to an operation.
86
92
 
87
93
  ```ruby
88
- class CommentsController < ApplicationController
94
+ class SongsController < ApplicationController
89
95
  def create
90
- run Comment::Create # Comment::Create is an operation class.
96
+ run Song::Create # Song::Create is an operation class.
91
97
  end
98
+ end
92
99
  ```
93
100
 
94
101
  The `#run` method invokes the operation. It allows you to run a conditional block of logic if the operation was successful.
95
102
 
96
103
  ```ruby
97
- class CommentsController < ApplicationController
104
+ class SongsController < ApplicationController
98
105
  def create
99
- run Comment::Create do |op|
100
- return redirect_to(comment_path op.model) # success!
106
+ run Song::Create do |op|
107
+ return redirect_to(song_path op.model) # success!
101
108
  end
102
109
 
103
110
  render :new # invalid. re-render form.
104
111
  end
112
+ end
105
113
  ```
106
114
 
107
115
  Again, the controller only dispatchs to the operation and handles successful/invalid processing on the HTTP level. For instance by redirecting, setting flash messages, or signing in a user.
@@ -112,86 +120,39 @@ Again, the controller only dispatchs to the operation and handles successful/inv
112
120
 
113
121
  Operations encapsulate business logic and are the heart of a Trailblazer architecture.
114
122
 
115
- Operations don't know about HTTP or the environment. You could use an operation in Rails, Hanami, or Roda, it wouldn't know. This makes them an ideal replacement for test factories.
116
-
117
- An operation is not just a monolithic replacement for your business code. It's a simple orchestrator between the form object, models and your business code.
118
-
119
- ```ruby
120
- class Comment::Create < Trailblazer::Operation
121
- def process(params)
122
- # do whatever you feel like.
123
- end
124
- end
125
- ```
126
-
127
- Operations only need to implement `#process` which receives the params from the caller.
128
-
129
- [Learn more.](http://trailblazer.to/gems/operation)
123
+ The bare bones operation without any Trailblazery is implemented in [the `trailblazer-operation` gem](https://github.com/trailblazer/trailblazer-operation) and can be used without our stack.
130
124
 
131
- ## Validations
125
+ Operations don't know about HTTP or the environment. You could use an operation in Rails, Hanami, or Roda, it wouldn't know.
132
126
 
133
- In Trailblazer, an operation (usually) has a form object which is simply a `Reform::Form` class. All the [API documented in Reform](https://github.com/apotonick/reform) can be applied and used.
134
-
135
- The operation makes use of the form object using the `#validate` method.
127
+ 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.
136
128
 
137
129
  ```ruby
138
- class Comment::Create < Trailblazer::Operation
139
- contract do
140
- # this is a Reform::Form class!
141
- property :body, validates: {presence: true}
142
- end
130
+ class Song::Create < Trailblazer::Operation
131
+ step :model
132
+ step :validate
143
133
 
144
- def process(params)
145
- @model = Comment.new
134
+ def model(ctx, **)
135
+ # do whatever you feel like.
136
+ ctx[:model] = Song.new
137
+ end
146
138
 
147
- validate(params[:comment], @model) do |f|
148
- f.save
149
- end
139
+ def validate(ctx, params:, **)
140
+ # ..
150
141
  end
151
142
  end
152
143
  ```
153
144
 
154
- The contract (aka _form_) is defined in the `::contract` block. You can implement nested forms, default values, validations, and everything else Reform provides.
155
-
156
- In the `#process` method you can define your business logic.
145
+ Operations define the flow of their logic using the DSL and implement the particular steps with pure Ruby.
157
146
 
158
- [Learn more.](http://trailblazer.to/gems/operation/api.html)
159
-
160
- ## Callbacks
161
-
162
- Post-processing logic (also known as _callbacks_) is configured in operations.
163
-
164
- Callbacks can be defined in groups. They use the form object's state tracking to find out whether they should be run.
147
+ You cannot instantiate them per design. The only way to invoke them is `call`.
165
148
 
166
149
  ```ruby
167
- class Comment::Create < Trailblazer::Operation
168
- include Callback
169
- callback(:after_save) do
170
- on_change :markdownize_body! # this is only run when the form object has changed.
171
- end
150
+ Song::Create.(params: {whatever: "goes", in: "here"})
172
151
  ```
173
152
 
174
- Callbacks are never triggered automatically, you have to invoke them! This is called _Imperative Callback_.
153
+ Their high degree of encapsulation makes them a [replacement for test factories](#test), too.
175
154
 
176
- ```ruby
177
- class Comment::Create < Trailblazer::Operation
178
- include Callback
179
- def process(params)
180
- validate(params) do
181
- contract.save
182
- callback!(:after_save) # run markdownize_body!, but only if form changed.
183
- end
184
- end
185
-
186
- def markdownize_body!(comment)
187
- comment.body = Markdownize.(comment.body)
188
- end
189
- end
190
- ```
191
-
192
- No magical triggering of unwanted logic anymore, but explicit invocations where you want it.
193
-
194
- [Learn more.](http://trailblazer.to/gems/operation/callback.html)
155
+ [Learn more.](http://trailblazer.to/gems/operation)
195
156
 
196
157
  ## Models
197
158
 
@@ -200,7 +161,7 @@ Models for persistence can be implemented using any ORM you fancy, for instance
200
161
  In Trailblazer, models are completely empty. They solely contain associations and finders. No business logic is allowed in models.
201
162
 
202
163
  ```ruby
203
- class Comment < ActiveRecord::Base
164
+ class Song < ActiveRecord::Base
204
165
  belongs_to :thing
205
166
 
206
167
  scope :latest, lambda { all.limit(9).order("id DESC") }
@@ -209,78 +170,6 @@ end
209
170
 
210
171
  Only operations and views/cells can access models directly.
211
172
 
212
- ## Policies
213
-
214
- You can abort running an operation using a policy. "[Pundit](https://github.com/elabs/pundit)-style" policy classes define the rules.
215
-
216
- ```ruby
217
- class Comment::Policy
218
- def initialize(user, comment)
219
- @user, @comment = user, comment
220
- end
221
-
222
- def create?
223
- @user.admin?
224
- end
225
- end
226
- ```
227
-
228
- The rule is enabled via the `::policy` call.
229
-
230
- ```ruby
231
- class Comment::Create < Trailblazer::Operation
232
- include Policy
233
-
234
- policy Comment::Policy, :create?
235
- ```
236
-
237
- The policy is evaluated in `#setup!`, raises an exception if `false` and suppresses running `#process`.
238
-
239
- [Learn more.](http://trailblazer.to/gems/operation/policy.html)
240
-
241
- ## Views
242
-
243
- View rendering can happen using the controller as known from Rails. This is absolutely fine for simple views.
244
-
245
- More complex UI logic happens in _View Models_ as found in [Cells](https://github.com/apotonick/cells). View models also replace helpers.
246
-
247
- The operation's form object can be rendered in views, too.
248
-
249
- ```ruby
250
- class CommentsController < ApplicationController
251
- def new
252
- form Comment::Create # will assign the form object to @form.
253
- end
254
- ```
255
-
256
- Since Reform objects can be passed to form builders, you can use the operation to render and process the form!
257
-
258
- ```haml
259
- = simple_form_for @form do |f|
260
- = f.input :body
261
- ```
262
-
263
-
264
- ## Representers
265
-
266
- Operations can use representers from [Roar](https://github.com/apotonick/roar) to serialize and parse JSON and XML documents for APIs.
267
-
268
- Representers can be inferred automatically from your contract, then may be refined, e.g. with hypermedia or a format like `JSON-API`.
269
-
270
- ```ruby
271
- class Comment::Create < Trailblazer::Operation
272
- representer do
273
- # inherited :body
274
- include Roar::JSON::HAL
275
-
276
- link(:self) { comment_path(represented.id) }
277
- end
278
- ```
279
-
280
- The operation can then parse incoming JSON documents in `validate` and render a document via `to_json`.
281
-
282
- [Learn more.](http://trailblazer.to/gems/operation/representer.html)
283
-
284
173
  ## Tests
285
174
 
286
175
  In Trailblazer, you only have operation unit tests and integration smoke tests to test the operation/controller wiring.
@@ -288,19 +177,18 @@ In Trailblazer, you only have operation unit tests and integration smoke tests t
288
177
  Operations completely replace the need for leaky factories.
289
178
 
290
179
  ```ruby
291
- describe Comment::Update do
292
- let(:comment) { Comment::Create.(comment: {body: "[That](http://trailblazer.to)!"}) }
180
+ describe Song::Update do
181
+ let(:song) { Song::Create.(song: {body: "[That](http://trailblazer.to)!"}) }
182
+ end
293
183
  ```
294
184
 
295
- ## More
185
+ ## Workflows
296
186
 
297
- Trailblazer has many more architectural features such as
187
+ Operations are a great way to clean up controllers and models. However, Trailblazer goes further and provides an approach to model entire life-cycles of business objects, such as "a song" or "the root user".
298
188
 
299
- * Polymorphic builders and operations
300
- * Inheritance and composition support
301
- * Polymorphic views
189
+ Those workflows dramatically reduce the usage of control flow logic in your code and allow for visually designing and discussing flows.
302
190
 
303
- Check the project website and the book.
191
+ Learn more about BPMN and workflows [on our website](https://2019.trailblazer.to/docs/workflow).
304
192
 
305
193
  ## Installation
306
194
 
@@ -309,16 +197,7 @@ The obvious needs to be in your `Gemfile`.
309
197
  ```ruby
310
198
  gem "trailblazer"
311
199
  gem "trailblazer-rails" # if you are in rails.
312
- gem "cells"
200
+ gem "trailblazer-cells"
313
201
  ```
314
202
 
315
203
  Cells is _not_ required per default! Add it if you use it, which is highly recommended.
316
-
317
- ## The Book
318
-
319
- ![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
320
-
321
- Please buy it: [Trailblazer - A new architecture for Rails](https://leanpub.com/trailblazer).
322
-
323
- The [demo application](https://github.com/apotonick/gemgem-trbrb) implements what we discuss in the book.
324
-
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
- task :default => [:test]
4
+ task :default => %i[test]
5
5
 
6
6
  Rake::TestTask.new(:test) do |test|
7
7
  test.libs << 'test'
8
- test.test_files = FileList['test/**/*_test.rb']
8
+ test.test_files = FileList['test/**/*_test.rb'] - FileList["test/deprecation/*_test.rb"]
9
9
  test.verbose = true
10
- end
10
+ end
@@ -1,3 +1,5 @@
1
1
  module Trailblazer
2
- VERSION = "1.1.1"
2
+ module Version
3
+ VERSION = "2.1.0"
4
+ end
3
5
  end
data/lib/trailblazer.rb CHANGED
@@ -1,7 +1,4 @@
1
- require "trailblazer/operation"
2
1
  require "trailblazer/version"
3
- require "uber/inheritable_attr"
4
-
5
- module Trailblazer
6
- # Your code goes here...
7
- end
2
+ require "trailblazer/operation"
3
+ require "trailblazer/macro"
4
+ require "trailblazer/macro/contract"
data/test/test_helper.rb CHANGED
@@ -1,9 +1,38 @@
1
- require 'trailblazer'
2
- require 'minitest/autorun'
1
+ require "pp"
2
+ require "delegate"
3
+ require "trailblazer"
4
+ require "minitest/autorun"
3
5
 
6
+ # TODO: convert tests to non-rails.
7
+ require "reform"
4
8
  require "reform/form/active_model/validations"
5
9
  Reform::Form.class_eval do
6
10
  include Reform::Form::ActiveModel::Validations
7
11
  end
12
+ # require "trailblazer/deprecation/context.rb"
8
13
 
9
- require "trailblazer/operation/model"
14
+ module Mock
15
+ class Result
16
+ def initialize(bool); @bool = bool end
17
+ def success?; @bool end
18
+ def errors; ["hihi"] end
19
+ end
20
+ end
21
+
22
+ module Test
23
+ module ReturnCall
24
+ def self.included(includer)
25
+ includer._insert :_insert, ReturnResult, {replace: Trailblazer::Operation::Result::Build}, ReturnResult, ""
26
+ end
27
+ end
28
+ ReturnResult = ->(last, input, options) { input }
29
+ end
30
+
31
+ Minitest::Spec::Operation = Trailblazer::Operation
32
+
33
+ Memo = Struct.new(:id, :body) do
34
+ def self.find(id)
35
+ return new(id, "Yo!") if id
36
+ nil
37
+ end
38
+ end
data/trailblazer.gemspec CHANGED
@@ -4,31 +4,27 @@ require 'trailblazer/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "trailblazer"
7
- spec.version = Trailblazer::VERSION
7
+ spec.version = Trailblazer::Version::VERSION
8
8
  spec.authors = ["Nick Sutterer"]
9
9
  spec.email = ["apotonick@gmail.com"]
10
- spec.description = %q{A high-level, modular architecture for Ruby framworks with domain and form objects, view models, twin decorators and representers.}
10
+ spec.description = %q{A high-level architecture introducing new abstractions such as operations and control flow, form objects and policies.}
11
11
  spec.summary = %q{A high-level architecture for Ruby and Rails.}
12
12
  spec.homepage = "http://trailblazer.to"
13
- spec.license = "MIT"
13
+ spec.license = "LGPL-3.0"
14
14
 
15
- spec.files = `git ls-files`.split($/)
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|doc)/})
17
+ end
18
+ spec.test_files = `git ls-files -z test`.split("\x0")
18
19
  spec.require_paths = ["lib"]
19
20
 
20
-
21
- spec.add_dependency "uber", ">= 0.0.15"
22
- spec.add_dependency "reform", ">= 2.0.0", "< 3.0.0"
23
- spec.add_dependency "declarative"
24
-
25
- spec.add_development_dependency "activemodel" # for Reform::AM::V
21
+ spec.add_dependency "trailblazer-macro", ">= 2.1.0", "< 2.2.0"
22
+ spec.add_dependency "trailblazer-macro-contract", ">= 2.1.0", "< 2.2.0"
23
+ spec.add_dependency "trailblazer-operation" # TODO: why do we need this here?
26
24
 
27
25
  spec.add_development_dependency "bundler"
28
26
  spec.add_development_dependency "rake"
29
27
  spec.add_development_dependency "minitest"
30
- spec.add_development_dependency "sqlite3"
31
- spec.add_development_dependency "database_cleaner"
32
28
 
33
- spec.add_development_dependency "roar"
29
+ spec.required_ruby_version = '>= 2.1.0'
34
30
  end