decent_exposure 3.0.0.beta1 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +12 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +2 -0
- data/README.md +58 -7
- data/decent_exposure.gemspec +16 -16
- data/gemfiles/Gemfile.rails-4.2.0 +5 -0
- data/gemfiles/Gemfile.rails-4.2.6 +5 -0
- data/gemfiles/Gemfile.rails-5.0.0 +5 -0
- data/lib/decent_exposure.rb +12 -5
- data/lib/decent_exposure/attribute.rb +0 -1
- data/lib/decent_exposure/behavior.rb +1 -1
- data/lib/decent_exposure/context.rb +2 -2
- data/lib/decent_exposure/controller.rb +4 -4
- data/lib/decent_exposure/exposure.rb +24 -23
- data/lib/decent_exposure/flow.rb +1 -2
- data/lib/decent_exposure/mailer.rb +15 -0
- data/lib/decent_exposure/version.rb +1 -1
- data/lib/generators/decent_exposure/USAGE +13 -0
- data/lib/generators/decent_exposure/scaffold_templates_generator.rb +46 -0
- data/lib/generators/decent_exposure/templates/_form.html.erb +34 -0
- data/lib/generators/decent_exposure/templates/_form.html.haml +15 -0
- data/lib/generators/decent_exposure/templates/controller.rb +41 -0
- data/lib/generators/decent_exposure/templates/edit.html.erb +6 -0
- data/lib/generators/decent_exposure/templates/edit.html.haml +7 -0
- data/lib/generators/decent_exposure/templates/index.html.erb +31 -0
- data/lib/generators/decent_exposure/templates/index.html.haml +25 -0
- data/lib/generators/decent_exposure/templates/new.html.erb +5 -0
- data/lib/generators/decent_exposure/templates/new.html.haml +5 -0
- data/lib/generators/decent_exposure/templates/show.html.erb +11 -0
- data/lib/generators/decent_exposure/templates/show.html.haml +11 -0
- data/spec/{controller_spec.rb → decent_exposure/controller_spec.rb} +45 -39
- data/spec/features/api_birds_controller_spec.rb +70 -0
- data/spec/features/birds_controller_spec.rb +70 -0
- data/spec/features/birds_mailer_spec.rb +54 -0
- data/spec/generators/decent_exposure/scaffold_templates_generator_spec.rb +45 -0
- data/spec/support/rails_app.rb +39 -7
- metadata +57 -35
- data/hashrocket_logo.png +0 -0
- data/spec/integration_spec.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3f21e1f9876dc8a5fb33726db86f7dd57c37f5c02d1947594e5d35378fa3c9fb
|
4
|
+
data.tar.gz: f3df28184866142fdcc357c815e035c4d12181d639cd815e9d2735cc268354d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 340c2ad9f56e884653dc235063e43cf874a344cd01e6f8b5fb5301c993dec5a53f4328703a154c4ae1caeeffd39c5eaea5f4a79eb9976bc34f313530d1d6933a
|
7
|
+
data.tar.gz: e9d9363c1a34280c7084e1662c9428577f68d0617e31436e013f0f0e782067ecb42177849f0710cd8c49f03f2e94e4e09758771faeec5f1848a9fc02cd23eff5
|
data/.travis.yml
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
language: ruby
|
1
2
|
rvm:
|
2
3
|
- 2.2.2
|
3
4
|
- 2.3.0
|
5
|
+
gemfile:
|
6
|
+
- Gemfile
|
7
|
+
- gemfiles/Gemfile.rails-5.0.0
|
8
|
+
- gemfiles/Gemfile.rails-4.2.6
|
9
|
+
- gemfiles/Gemfile.rails-4.2.0
|
10
|
+
notifications:
|
11
|
+
slack:
|
12
|
+
on_success: change
|
13
|
+
on_failure: always
|
14
|
+
rooms:
|
15
|
+
secure: Ch/Euv0+g1BQawHLVXzT9R/eoFxQWBf95SQzufpA19tlwz+7OsGlaOH4YfxVbnanH33XL06AEB1arsDUwlv0lx/I2Cs1LRh4WHAJ7T5hahusF2W3/+RgCDf2eifk1MRswkDpvhxH/sAS6INc4gOKc52q4gAdzU2uytVsdaaCjCY=
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# DecentExposure History/Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
This project adheres to [Semantic Versioning](http://semver.org/).
|
5
|
+
|
6
|
+
## 3.0.2
|
7
|
+
|
8
|
+
* Fix mailers when arguments are not a hash.
|
9
|
+
|
10
|
+
## 3.0.1
|
11
|
+
|
12
|
+
* Allow exposures with a question mark (?).
|
13
|
+
* Remove setter from view helper.
|
14
|
+
* Fix undefined `helper_method` error
|
15
|
+
|
16
|
+
## 3.0.0
|
17
|
+
|
18
|
+
* New codebase, see [api changes document](https://github.com/hashrocket/decent_exposure/wiki/Api-changes-in-version-3).
|
19
|
+
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -7,16 +7,18 @@
|
|
7
7
|
|
8
8
|
### Notice
|
9
9
|
|
10
|
-
DecentExposure `3.0` is a major change from `< 3.0`.
|
10
|
+
DecentExposure `3.0` is a major change from `< 3.0`.
|
11
11
|
|
12
|
-
|
12
|
+
Version `3.0` will support Rails 4 and 5.
|
13
|
+
|
14
|
+
**Be aware... mild API changes ahead.** Check this [API changes in version 3](https://github.com/hashrocket/decent_exposure/wiki/Api-changes-in-version-3)
|
13
15
|
|
14
16
|
### Installation
|
15
17
|
|
16
18
|
Add this line to your application's Gemfile:
|
17
19
|
|
18
20
|
```ruby
|
19
|
-
gem 'decent_exposure'
|
21
|
+
gem 'decent_exposure', '3.0.0'
|
20
22
|
```
|
21
23
|
|
22
24
|
And then execute:
|
@@ -43,12 +45,12 @@ end
|
|
43
45
|
|
44
46
|
Now every time you call `thing` in your controller or view, it will look for an
|
45
47
|
ID and try to perform `Thing.find(id)`. If the ID isn't found, it will call
|
46
|
-
`Thing.new(
|
48
|
+
`Thing.new(thing_params)`. The result will be memoized in an `@exposed_thing`
|
47
49
|
instance variable.
|
48
50
|
|
49
51
|
#### Example Controller
|
50
52
|
|
51
|
-
Here's what a standard Rails
|
53
|
+
Here's what a standard Rails 5 CRUD controller using Decent Exposure might look like:
|
52
54
|
|
53
55
|
```ruby
|
54
56
|
class ThingsController < ApplicationController
|
@@ -307,6 +309,7 @@ decoration process. Initially, this does nothing, but you can obviously change
|
|
307
309
|
that:
|
308
310
|
|
309
311
|
```ruby
|
312
|
+
expose :things, ->{ Thing.all.map{ |thing| ThingDecorator.new(thing) } }
|
310
313
|
expose :thing, decorate: ->(thing){ ThingDecorator.new(thing) }
|
311
314
|
```
|
312
315
|
|
@@ -323,6 +326,54 @@ expose :thing, with: [:cool_find, :cool_build]
|
|
323
326
|
expose :another_thing, with: :cool_build
|
324
327
|
```
|
325
328
|
|
329
|
+
## Rails Mailers
|
330
|
+
|
331
|
+
Mailers and Controllers use the same decent_exposure dsl.
|
332
|
+
|
333
|
+
### Example Mailer
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
class PostMailer < ApplicationMailer
|
337
|
+
expose(:posts, -> { Post.last(10) })
|
338
|
+
expose(:post)
|
339
|
+
|
340
|
+
def top_posts
|
341
|
+
@greeting = "Top Posts"
|
342
|
+
mail to: "to@example.org"
|
343
|
+
end
|
344
|
+
|
345
|
+
def featured_post(id:)
|
346
|
+
@greeting = "Featured Post"
|
347
|
+
mail to: "to@example.org"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
## Rails Scaffold Templates
|
353
|
+
|
354
|
+
If you want to generate rails scaffold templates prepared for `decent_exposure` run:
|
355
|
+
|
356
|
+
```bash
|
357
|
+
rails generate decent_exposure:scaffold_templates [--template_engine erb|haml]
|
358
|
+
```
|
359
|
+
|
360
|
+
This will create the templates in your `lib/templates` folder.
|
361
|
+
|
362
|
+
Make sure you have configured your templates engine for generators in `config/application.rb`:
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
# config/application.rb
|
366
|
+
config.generators do |g|
|
367
|
+
g.template_engine :erb
|
368
|
+
end
|
369
|
+
```
|
370
|
+
|
371
|
+
Now you can run scaffold like:
|
372
|
+
|
373
|
+
```bash
|
374
|
+
rails generate scaffold post title description:text
|
375
|
+
```
|
376
|
+
|
326
377
|
## Contributing
|
327
378
|
|
328
379
|
1. Fork it (https://github.com/hashrocket/decent_exposure/fork)
|
@@ -333,6 +384,6 @@ expose :another_thing, with: :cool_build
|
|
333
384
|
|
334
385
|
## About
|
335
386
|
|
336
|
-
[![Hashrocket logo](hashrocket_logo.
|
387
|
+
[![Hashrocket logo](https://hashrocket.com/hashrocket_logo.svg)](https://hashrocket.com)
|
337
388
|
|
338
|
-
Decent Exposure is supported by the team at [Hashrocket](https://hashrocket.com), a multidisciplinary design & development consultancy. If you'd like to [work with us](https://hashrocket.com/contact
|
389
|
+
Decent Exposure is supported by the team at [Hashrocket](https://hashrocket.com), a multidisciplinary design & development consultancy. If you'd like to [work with us](https://hashrocket.com/contact) or [join our team](https://hashrocket.com/careers), don't hesitate to get in touch.
|
data/decent_exposure.gemspec
CHANGED
@@ -1,30 +1,30 @@
|
|
1
1
|
require File.expand_path("../lib/decent_exposure/version", __FILE__)
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name
|
5
|
-
spec.version
|
6
|
-
spec.authors
|
7
|
-
spec.email
|
8
|
-
spec.summary
|
9
|
-
spec.description =
|
4
|
+
spec.name = "decent_exposure"
|
5
|
+
spec.version = DecentExposure::VERSION
|
6
|
+
spec.authors = ["Pavel Pravosud", "Stephen Caudill"]
|
7
|
+
spec.email = ["info@hashrocket.com"]
|
8
|
+
spec.summary = "A helper for creating declarative interfaces in controllers"
|
9
|
+
spec.description = '
|
10
10
|
DecentExposure helps you program to an interface, rather than an
|
11
11
|
implementation in your Rails controllers. The fact of the matter is that
|
12
12
|
sharing state via instance variables in controllers promotes close coupling
|
13
13
|
with views. DecentExposure gives you a declarative manner of exposing an
|
14
14
|
interface to the state that controllers contain and thereby decreasing
|
15
15
|
coupling and improving your testability and overall design.
|
16
|
-
|
17
|
-
spec.homepage
|
18
|
-
spec.license
|
19
|
-
spec.files
|
20
|
-
spec.test_files
|
16
|
+
'
|
17
|
+
spec.homepage = "https://github.com/hashrocket/decent_exposure"
|
18
|
+
spec.license = "MIT"
|
19
|
+
spec.files = `git ls-files -z`.split("\x0")
|
20
|
+
spec.test_files = spec.files.grep(/\Aspec\//)
|
21
21
|
spec.require_path = "lib"
|
22
22
|
|
23
|
-
spec.required_ruby_version = "
|
23
|
+
spec.required_ruby_version = ">= 2.0"
|
24
24
|
|
25
|
-
spec.add_dependency "
|
26
|
-
spec.
|
25
|
+
spec.add_dependency "activesupport", ">= 4.0"
|
26
|
+
spec.add_development_dependency "railties", ">= 4.0"
|
27
|
+
spec.add_development_dependency "actionmailer"
|
27
28
|
spec.add_development_dependency "rspec-rails", "~> 3.0"
|
28
|
-
spec.add_development_dependency "
|
29
|
-
spec.add_development_dependency "pry"
|
29
|
+
spec.add_development_dependency "standard"
|
30
30
|
end
|
data/lib/decent_exposure.rb
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
require "decent_exposure/version"
|
2
2
|
require "active_support/all"
|
3
|
+
require "generators/decent_exposure/scaffold_templates_generator"
|
3
4
|
|
4
5
|
module DecentExposure
|
5
6
|
autoload :Controller, "decent_exposure/controller"
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
autoload :
|
9
|
-
autoload :
|
10
|
-
autoload :
|
7
|
+
autoload :Mailer, "decent_exposure/mailer"
|
8
|
+
autoload :Exposure, "decent_exposure/exposure"
|
9
|
+
autoload :Attribute, "decent_exposure/attribute"
|
10
|
+
autoload :Context, "decent_exposure/context"
|
11
|
+
autoload :Behavior, "decent_exposure/behavior"
|
12
|
+
autoload :Flow, "decent_exposure/flow"
|
11
13
|
|
12
14
|
ActiveSupport.on_load :action_controller do
|
13
15
|
include Controller
|
14
16
|
end
|
17
|
+
|
18
|
+
ActiveSupport.on_load :action_mailer do
|
19
|
+
include Controller
|
20
|
+
include Mailer
|
21
|
+
end
|
15
22
|
end
|
@@ -20,7 +20,7 @@ module DecentExposure
|
|
20
20
|
#
|
21
21
|
# Returns the attribute's value.
|
22
22
|
def get
|
23
|
-
ivar_defined
|
23
|
+
ivar_defined? ? ivar_get : set(fetch_value)
|
24
24
|
end
|
25
25
|
|
26
26
|
# Public: Write to an attribute on the context Class.
|
@@ -51,7 +51,7 @@ module DecentExposure
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def ivar_name
|
54
|
-
"@#{attribute.ivar_name}"
|
54
|
+
"@#{attribute.ivar_name.gsub("?", "_question_mark_")}"
|
55
55
|
end
|
56
56
|
|
57
57
|
def fetch_value
|
@@ -17,8 +17,8 @@ module DecentExposure
|
|
17
17
|
#
|
18
18
|
# Returns the helper methods that are now defined on the class
|
19
19
|
# where this method is included.
|
20
|
-
def expose(*args, &block)
|
21
|
-
Exposure.expose! self, *args, &block
|
20
|
+
def expose(*args, **options, &block)
|
21
|
+
Exposure.expose! self, *args, **options, &block
|
22
22
|
end
|
23
23
|
|
24
24
|
# Public: Exposes an attribute to a controller Class.
|
@@ -33,8 +33,8 @@ module DecentExposure
|
|
33
33
|
#
|
34
34
|
# Sets the exposed attribute to a before_action callback in the
|
35
35
|
# controller.
|
36
|
-
def expose!(name, *args, &block)
|
37
|
-
expose name, *args, &block
|
36
|
+
def expose!(name, *args, **options, &block)
|
37
|
+
expose name, *args, **options, &block
|
38
38
|
before_action name
|
39
39
|
end
|
40
40
|
|
@@ -13,8 +13,8 @@ module DecentExposure
|
|
13
13
|
# the Proc when called.
|
14
14
|
#
|
15
15
|
# Returns a collection of exposed helper methods.
|
16
|
-
def self.expose!(*args, &block)
|
17
|
-
new(*args, &block).expose!
|
16
|
+
def self.expose!(*args, **options, &block)
|
17
|
+
new(*args, **options, &block).expose!
|
18
18
|
end
|
19
19
|
|
20
20
|
# Public: Initalize an Exposure with a hash of options.
|
@@ -34,7 +34,7 @@ module DecentExposure
|
|
34
34
|
# the Proc.
|
35
35
|
#
|
36
36
|
# Returns a normalized options Hash.
|
37
|
-
def initialize(controller, name, fetch_block=nil, **options, &block)
|
37
|
+
def initialize(controller, name, fetch_block = nil, **options, &block)
|
38
38
|
@controller = controller
|
39
39
|
@options = options.with_indifferent_access.merge(name: name)
|
40
40
|
|
@@ -67,8 +67,9 @@ module DecentExposure
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def expose_helper_methods!
|
70
|
-
|
71
|
-
|
70
|
+
return unless controller.respond_to?(:helper_method)
|
71
|
+
|
72
|
+
controller.helper_method attribute.getter_method_name
|
72
73
|
end
|
73
74
|
|
74
75
|
def normalize_options
|
@@ -85,43 +86,43 @@ module DecentExposure
|
|
85
86
|
|
86
87
|
def normalize_fetch_option
|
87
88
|
normalize_non_proc_option :fetch do |method_name|
|
88
|
-
->{ send(method_name) }
|
89
|
+
-> { send(method_name) }
|
89
90
|
end
|
90
91
|
end
|
91
92
|
|
92
93
|
def normalize_find_by_option
|
93
|
-
if find_by = options.delete(:find_by)
|
94
|
-
merge_lambda_option :find, ->(id, scope){ scope.find_by!(find_by => id) }
|
94
|
+
if (find_by = options.delete(:find_by))
|
95
|
+
merge_lambda_option :find, ->(id, scope) { scope.find_by!(find_by => id) }
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
98
99
|
def normalize_parent_option
|
99
100
|
exposure_name = options.fetch(:name)
|
100
101
|
|
101
|
-
if parent = options.delete(:parent)
|
102
|
-
merge_lambda_option :scope, ->{ send(parent).send(exposure_name.to_s.pluralize) }
|
102
|
+
if (parent = options.delete(:parent))
|
103
|
+
merge_lambda_option :scope, -> { send(parent).send(exposure_name.to_s.pluralize) }
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
106
107
|
def normalize_from_option
|
107
108
|
exposure_name = options.fetch(:name)
|
108
109
|
|
109
|
-
if from = options.delete(:from)
|
110
|
-
merge_lambda_option :build, ->{ send(from).send(exposure_name) }
|
111
|
-
merge_lambda_option :model, ->{ nil }
|
112
|
-
merge_lambda_option :id, ->{ nil }
|
110
|
+
if (from = options.delete(:from))
|
111
|
+
merge_lambda_option :build, -> { send(from).send(exposure_name) }
|
112
|
+
merge_lambda_option :model, -> { nil }
|
113
|
+
merge_lambda_option :id, -> { nil }
|
113
114
|
end
|
114
115
|
end
|
115
116
|
|
116
117
|
def normalize_with_option
|
117
|
-
if configs = options.delete(:with)
|
118
|
-
Array.wrap(configs).each{ |config| reverse_merge_config! config }
|
118
|
+
if (configs = options.delete(:with))
|
119
|
+
Array.wrap(configs).each { |config| reverse_merge_config! config }
|
119
120
|
end
|
120
121
|
end
|
121
122
|
|
122
123
|
def normalize_id_option
|
123
124
|
normalize_non_proc_option :id do |ids|
|
124
|
-
->{ Array.wrap(ids).map{ |id| params[id] }.find(&:present?) }
|
125
|
+
-> { Array.wrap(ids).map { |id| params[id] }.find(&:present?) }
|
125
126
|
end
|
126
127
|
end
|
127
128
|
|
@@ -133,7 +134,7 @@ module DecentExposure
|
|
133
134
|
value
|
134
135
|
end
|
135
136
|
|
136
|
-
->{ model }
|
137
|
+
-> { model }
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
@@ -146,7 +147,7 @@ module DecentExposure
|
|
146
147
|
|
147
148
|
def normalize_scope_options
|
148
149
|
normalize_non_proc_option :scope do |custom_scope|
|
149
|
-
->(model){ model.send(custom_scope) }
|
150
|
+
->(model) { model.send(custom_scope) }
|
150
151
|
end
|
151
152
|
end
|
152
153
|
|
@@ -164,7 +165,7 @@ module DecentExposure
|
|
164
165
|
end
|
165
166
|
|
166
167
|
def merge_lambda_option(name, body)
|
167
|
-
if previous_value = options[name]
|
168
|
+
if (previous_value = options[name]) && (Proc === previous_value)
|
168
169
|
fail ArgumentError, "#{name.to_s.titleize} block is already defined"
|
169
170
|
end
|
170
171
|
|
@@ -177,7 +178,7 @@ module DecentExposure
|
|
177
178
|
|
178
179
|
name = options.fetch(:name)
|
179
180
|
ivar_name = "exposed_#{name}"
|
180
|
-
fetch = ->{ Flow.new(self, local_options).fetch }
|
181
|
+
fetch = -> { Flow.new(self, local_options).fetch }
|
181
182
|
|
182
183
|
Attribute.new(
|
183
184
|
name: name,
|
@@ -188,13 +189,13 @@ module DecentExposure
|
|
188
189
|
end
|
189
190
|
|
190
191
|
def assert_incompatible_options_pair(key1, key2)
|
191
|
-
if options.key?(key1) && options.key?(key2)
|
192
|
+
if options.symbolize_keys.key?(key1) && options.symbolize_keys.key?(key2)
|
192
193
|
fail ArgumentError, "Using #{key1.inspect} option with #{key2.inspect} doesn't make sense"
|
193
194
|
end
|
194
195
|
end
|
195
196
|
|
196
197
|
def assert_singleton_option(name)
|
197
|
-
if options.except(name, :name, :decorate).any? && options.key?(name)
|
198
|
+
if options.symbolize_keys.except(name, :name, :decorate).any? && options.symbolize_keys.key?(name)
|
198
199
|
fail ArgumentError, "Using #{name.inspect} option with other options doesn't make sense"
|
199
200
|
end
|
200
201
|
end
|