deserializer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +106 -0
- data/MIT-LICENSE +20 -0
- data/README.md +268 -0
- data/Rakefile +37 -0
- data/deserializer.gemspec +21 -0
- data/lib/deserializer.rb +6 -0
- data/lib/deserializer/base.rb +98 -0
- data/lib/deserializer/deserializer_error.rb +12 -0
- data/lib/deserializer/version.rb +3 -0
- data/test/deserializer_test.rb +7 -0
- data/test/dummy/README.rdoc +0 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/test_helper.rb +20 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e0dd737dc7e4c34c7238b6dc22a16077e17c22d3
|
4
|
+
data.tar.gz: 1d3b4c23174de67667241f4dc91109222bc61d41
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d9c5cde9d336f5568761d3d91b39cc697dead376ac6d7f3260da7901aaac333961a71fe7ff1fb0eb7e72446de88b9282e3c850b9c3198aa89fbb92404f2713f0
|
7
|
+
data.tar.gz: bcedd5145f9015da536d2927bed7fcca82d0b39796e78dc7fd6877da719ce2b9f1e5e31a7d5fd2b60c34b4dd5e46fa4d720829046f4eca2c8691a7fdb07e4fa2
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in deserializer.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
# Declare any dependencies that are still in development here instead of in
|
9
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
10
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
+
# your gem to rubygems.org.
|
12
|
+
|
13
|
+
# To use a debugger
|
14
|
+
# gem 'byebug', group: [:development, :test]
|
15
|
+
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
deserializer (0.0.1)
|
5
|
+
rails (~> 4.2.1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (4.2.3)
|
11
|
+
actionpack (= 4.2.3)
|
12
|
+
actionview (= 4.2.3)
|
13
|
+
activejob (= 4.2.3)
|
14
|
+
mail (~> 2.5, >= 2.5.4)
|
15
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
16
|
+
actionpack (4.2.3)
|
17
|
+
actionview (= 4.2.3)
|
18
|
+
activesupport (= 4.2.3)
|
19
|
+
rack (~> 1.6)
|
20
|
+
rack-test (~> 0.6.2)
|
21
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
22
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
23
|
+
actionview (4.2.3)
|
24
|
+
activesupport (= 4.2.3)
|
25
|
+
builder (~> 3.1)
|
26
|
+
erubis (~> 2.7.0)
|
27
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
28
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
29
|
+
activejob (4.2.3)
|
30
|
+
activesupport (= 4.2.3)
|
31
|
+
globalid (>= 0.3.0)
|
32
|
+
activemodel (4.2.3)
|
33
|
+
activesupport (= 4.2.3)
|
34
|
+
builder (~> 3.1)
|
35
|
+
activerecord (4.2.3)
|
36
|
+
activemodel (= 4.2.3)
|
37
|
+
activesupport (= 4.2.3)
|
38
|
+
arel (~> 6.0)
|
39
|
+
activesupport (4.2.3)
|
40
|
+
i18n (~> 0.7)
|
41
|
+
json (~> 1.7, >= 1.7.7)
|
42
|
+
minitest (~> 5.1)
|
43
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
44
|
+
tzinfo (~> 1.1)
|
45
|
+
arel (6.0.2)
|
46
|
+
builder (3.2.2)
|
47
|
+
erubis (2.7.0)
|
48
|
+
globalid (0.3.5)
|
49
|
+
activesupport (>= 4.1.0)
|
50
|
+
i18n (0.7.0)
|
51
|
+
json (1.8.3)
|
52
|
+
loofah (2.0.2)
|
53
|
+
nokogiri (>= 1.5.9)
|
54
|
+
mail (2.6.3)
|
55
|
+
mime-types (>= 1.16, < 3)
|
56
|
+
mime-types (2.6.1)
|
57
|
+
mini_portile (0.6.2)
|
58
|
+
minitest (5.7.0)
|
59
|
+
nokogiri (1.6.6.2)
|
60
|
+
mini_portile (~> 0.6.0)
|
61
|
+
rack (1.6.4)
|
62
|
+
rack-test (0.6.3)
|
63
|
+
rack (>= 1.0)
|
64
|
+
rails (4.2.3)
|
65
|
+
actionmailer (= 4.2.3)
|
66
|
+
actionpack (= 4.2.3)
|
67
|
+
actionview (= 4.2.3)
|
68
|
+
activejob (= 4.2.3)
|
69
|
+
activemodel (= 4.2.3)
|
70
|
+
activerecord (= 4.2.3)
|
71
|
+
activesupport (= 4.2.3)
|
72
|
+
bundler (>= 1.3.0, < 2.0)
|
73
|
+
railties (= 4.2.3)
|
74
|
+
sprockets-rails
|
75
|
+
rails-deprecated_sanitizer (1.0.3)
|
76
|
+
activesupport (>= 4.2.0.alpha)
|
77
|
+
rails-dom-testing (1.0.6)
|
78
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
79
|
+
nokogiri (~> 1.6.0)
|
80
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
81
|
+
rails-html-sanitizer (1.0.2)
|
82
|
+
loofah (~> 2.0)
|
83
|
+
railties (4.2.3)
|
84
|
+
actionpack (= 4.2.3)
|
85
|
+
activesupport (= 4.2.3)
|
86
|
+
rake (>= 0.8.7)
|
87
|
+
thor (>= 0.18.1, < 2.0)
|
88
|
+
rake (10.4.2)
|
89
|
+
sprockets (3.2.0)
|
90
|
+
rack (~> 1.0)
|
91
|
+
sprockets-rails (2.3.2)
|
92
|
+
actionpack (>= 3.0)
|
93
|
+
activesupport (>= 3.0)
|
94
|
+
sprockets (>= 2.8, < 4.0)
|
95
|
+
sqlite3 (1.3.10)
|
96
|
+
thor (0.19.1)
|
97
|
+
thread_safe (0.3.5)
|
98
|
+
tzinfo (1.2.2)
|
99
|
+
thread_safe (~> 0.1)
|
100
|
+
|
101
|
+
PLATFORMS
|
102
|
+
ruby
|
103
|
+
|
104
|
+
DEPENDENCIES
|
105
|
+
deserializer!
|
106
|
+
sqlite3
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Greg Orlov
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
# Deserializer
|
2
|
+
|
3
|
+
Deserialization of complex parameters into a hash that an AR model can take.
|
4
|
+
|
5
|
+
Lets you have a reverse ActiveModel::Sereializer-like interface that allows for easy create and update without having to write heavy controllers.
|
6
|
+
|
7
|
+
## Problem
|
8
|
+
|
9
|
+
Let's say we have a API create endpoint that takes json that looks something like
|
10
|
+
|
11
|
+
```json
|
12
|
+
{
|
13
|
+
"restaurant_id" : 13,
|
14
|
+
"user_id" : 6,
|
15
|
+
"dish_name" : "risotto con funghi",
|
16
|
+
"description" : "repulsive beyond belief",
|
17
|
+
"ratings" : {
|
18
|
+
"taste" : "terrible",
|
19
|
+
"color" : "horrendous",
|
20
|
+
"texture" : "vile",
|
21
|
+
"smell" : "delightful, somehow"
|
22
|
+
}
|
23
|
+
}
|
24
|
+
```
|
25
|
+
|
26
|
+
that goes into a flat DishReview model that looks like
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
t.belongs_to :restaurant
|
30
|
+
t.belongs_to :user
|
31
|
+
# field name different from API
|
32
|
+
t.string :name
|
33
|
+
t.string :description
|
34
|
+
t.string :taste
|
35
|
+
t.string :color
|
36
|
+
t.string :texture
|
37
|
+
t.string :smell
|
38
|
+
```
|
39
|
+
|
40
|
+
what do we do?
|
41
|
+
|
42
|
+
Normally, we'd have some params we permit, do some parsing and feed those into `DishReview.new`, like
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
class DishReviewController < BaseController
|
46
|
+
|
47
|
+
def create
|
48
|
+
review_params = get_review_params(params)
|
49
|
+
@review = ProfessionalReview.new(review_params)
|
50
|
+
if @review.save
|
51
|
+
# return review
|
52
|
+
else
|
53
|
+
# return sad errors splody
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# rest of RUD
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def permitted_params
|
62
|
+
[
|
63
|
+
:restaurant_id,
|
64
|
+
:user_id
|
65
|
+
:dish_name,
|
66
|
+
:description,
|
67
|
+
:taste,
|
68
|
+
:color,
|
69
|
+
:texture,
|
70
|
+
:smell
|
71
|
+
]
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_review_params(params)
|
75
|
+
review_params = params.require(:review)
|
76
|
+
|
77
|
+
review_params[:name] ||= review_params.delete(:dish_name)
|
78
|
+
|
79
|
+
ratings = review_params.delete(:ratings)
|
80
|
+
if (ratings.present?)
|
81
|
+
ratings.each{|rating, value| review_params[rating] = value if valid_rating?(rating) }
|
82
|
+
end
|
83
|
+
|
84
|
+
review_params.permit(permitted_params)
|
85
|
+
end
|
86
|
+
|
87
|
+
def valid_rating?(rating)
|
88
|
+
@@ratings ||= ["overall", "trusthworthy", "responsive", "knowledgeable", "communication"]
|
89
|
+
|
90
|
+
@@ratings.include? rating
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
and that's fine, but kind of annoying, and you have to do this for every action. It makes the controllers heavy, hard to parse, fragile, and really do things that are no longer controller-y.
|
97
|
+
|
98
|
+
So what we have here is a wrapper that lets us get away from polluting the controller with all of this parsing and lets us build deserializers that look very much like our serializers.
|
99
|
+
|
100
|
+
## Usage
|
101
|
+
|
102
|
+
Deserializer acts and looks pretty mich identical to ActiveModel::Serializer. It has attributes, attribute, and the has_one association. It does not currently support has_many, as that's an odd thing for a write endpoint to support, but can easily be added.
|
103
|
+
|
104
|
+
### Deserializer functions
|
105
|
+
|
106
|
+
#### from_params
|
107
|
+
`MyDeserializer.from_params(params)` created the json that your AR model will then consume.
|
108
|
+
```ruby
|
109
|
+
@review = DishReview.new( MyApi::V1::DishReviewDeserailzer.from_params(params) )
|
110
|
+
```
|
111
|
+
|
112
|
+
#### permitted_params
|
113
|
+
If you're using strong params, this lets you avoid having multiple definitions in fragile arrays. Just call ` MyDeserailzer.permitted_params` and you'll have the full array of keys you expect params to have.
|
114
|
+
|
115
|
+
### Deserializer Definition
|
116
|
+
To define a deserializer, you inherit from `Deserializer::Base` and define it in much the same way you would an `ActiveModel::Serializer`.
|
117
|
+
|
118
|
+
#### attributes
|
119
|
+
This is straight 1:1 mapping from params to the model, so
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
class PostDeserializer < Deserializer::Base
|
123
|
+
attributes :title,
|
124
|
+
:body
|
125
|
+
end
|
126
|
+
```
|
127
|
+
with params `{"title" => "lorem", "body" => "ipsum"}`, will give you a hash of `{title: "lorem", body: "ipsum"}`.
|
128
|
+
|
129
|
+
#### attribute
|
130
|
+
`attribute` is the singular version of `attributes`, but like `ActiveModel::Serializer` it can take a `:key`
|
131
|
+
```ruby
|
132
|
+
class PostDeserializer < Deserializer::Base
|
133
|
+
attribute :title
|
134
|
+
attribute :body, key: :text
|
135
|
+
end
|
136
|
+
```
|
137
|
+
It is symmetric with `ActiveModel::Serializer`, so that :text is what it will get in params, but :body is what it will insert into the result.
|
138
|
+
|
139
|
+
For example with params of `{"title" => "lorem", "text" => "ipsum"}` this desrerializer will produce `{title: "lorem", body: "ipsum"}`.
|
140
|
+
|
141
|
+
|
142
|
+
#### has_one
|
143
|
+
NOTE: This is the only association currently supported by `Deserializer`.
|
144
|
+
`has_one` expects the param and its deserializer. So for params `{"ratings" => {"taste" => "bad", "smell" => "good"}}`
|
145
|
+
```ruby
|
146
|
+
class DishDeserializer < Deserializer::Base
|
147
|
+
# probably other stuff
|
148
|
+
has_one :ratings, deserializer: RatingsDeserializer
|
149
|
+
end
|
150
|
+
|
151
|
+
class RatingsDeserializer < Deserializer::Base
|
152
|
+
attributes :taste,
|
153
|
+
:smell
|
154
|
+
end
|
155
|
+
```
|
156
|
+
you would get `{ratings: {taste: "bad", smell: "good"}}`
|
157
|
+
|
158
|
+
#### Overriding Attribute Methods
|
159
|
+
So let's say in the example above, your internal representation of ratings inside `Dish` is actually called `scores`, you can do
|
160
|
+
```ruby
|
161
|
+
class DishDeserializer < Deserializer::Base
|
162
|
+
has_one :ratings, deserializer: RatingsDeserializer
|
163
|
+
|
164
|
+
def ratings
|
165
|
+
:scores
|
166
|
+
end
|
167
|
+
end
|
168
|
+
```
|
169
|
+
which will give you `{scores: {taste: "bad", smell: "good"}}`
|
170
|
+
|
171
|
+
or, if you want to deserialize `ratings` into your `dish` object, you can use `object`
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
class DishDeserializer < Deserializer::Base
|
175
|
+
has_one :ratings, deserializer: RatingsDeserializer
|
176
|
+
|
177
|
+
def ratings
|
178
|
+
object
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
which will give you `{taste: "bad", smell: "good"}`
|
183
|
+
|
184
|
+
or you can deserialize into another subobject by doing
|
185
|
+
```ruby
|
186
|
+
class DishDeserializer < Deserializer::Base
|
187
|
+
has_one :colors, deserializer: ColorsDeserializer
|
188
|
+
has_one :ratings, deserializer: RatingsDeserializer
|
189
|
+
|
190
|
+
def colors
|
191
|
+
object[:ratings]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
195
|
+
which, given params
|
196
|
+
```
|
197
|
+
{
|
198
|
+
"ratings" =>
|
199
|
+
{
|
200
|
+
"taste" => "bad",
|
201
|
+
"smell" => "good"
|
202
|
+
},
|
203
|
+
"colors" =>
|
204
|
+
{
|
205
|
+
"color" => "red"
|
206
|
+
}
|
207
|
+
}
|
208
|
+
```
|
209
|
+
, will give you `{ratings: {taste: "bad", smell: "good", color: "red"}}`
|
210
|
+
|
211
|
+
### Example
|
212
|
+
|
213
|
+
So the example above will combine all of those to look like
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
module MyApi
|
217
|
+
module V1
|
218
|
+
class DishReviewDeserializer < Deserializer::Base
|
219
|
+
attributes :restaurant_id
|
220
|
+
:user_id
|
221
|
+
:description
|
222
|
+
|
223
|
+
attribute :name, key: :dish_name
|
224
|
+
|
225
|
+
has_one :ratings, :deserializer => RatingsDeserializer
|
226
|
+
|
227
|
+
def ratings
|
228
|
+
object
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
where RatingsDeserializer looks like
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
module MyApi
|
240
|
+
module V1
|
241
|
+
class RatingsDeserializer < Deserializer::Base
|
242
|
+
|
243
|
+
attributes :taste,
|
244
|
+
:color,
|
245
|
+
:texture,
|
246
|
+
:smell
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
All of this allows your controller to be so very small, like
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
class DishReviewsController < YourApiController::Base
|
256
|
+
def create
|
257
|
+
@review = DishReview.new( MyApi::V1::DishReviewDeserailzer.from_params(params) )
|
258
|
+
|
259
|
+
if @review.save
|
260
|
+
# return review
|
261
|
+
else
|
262
|
+
# return sad errors splody
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# rest of RUD
|
267
|
+
end
|
268
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Deserializer'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|