adequate_exposure 0.2.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.travis.yml +21 -3
- data/Appraisals +19 -0
- data/Gemfile +4 -3
- data/README.md +44 -5
- data/Rakefile +3 -0
- data/adequate_exposure.gemspec +3 -3
- data/gemfiles/rails_5.0.gemfile +12 -0
- data/gemfiles/rails_5.1.gemfile +12 -0
- data/gemfiles/rails_5.2.gemfile +12 -0
- data/gemfiles/rails_6.0.gemfile +12 -0
- data/lib/adequate_exposure.rb +1 -0
- data/lib/adequate_exposure/attribute.rb +22 -0
- data/lib/adequate_exposure/behavior.rb +100 -0
- data/lib/adequate_exposure/context.rb +19 -0
- data/lib/adequate_exposure/controller.rb +33 -3
- data/lib/adequate_exposure/exposure.rb +43 -9
- data/lib/adequate_exposure/flow.rb +44 -58
- data/lib/adequate_exposure/version.rb +1 -1
- data/spec/controller_spec.rb +13 -4
- data/spec/integration_spec.rb +2 -2
- data/spec/support/rails_app.rb +1 -1
- metadata +30 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ce17b7d2fd63e184f9b47eb20d8861407be033749995520b86a435e728c3818b
|
4
|
+
data.tar.gz: 9b430ad3d8bba9e410d44716dd512ca918215dc9c605342a040c7bfc31b18fe2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c3c3ec85721257b46b10aa88a2260fc28a5901fb6e8245a0d9a51846bcccdff6dc8a6950813f82e30a97a3a18efcf933ecc791b6feb3d756e44754bca22d548
|
7
|
+
data.tar.gz: 5574c4f8578bc4a6ea7ec96d19c5f70e9da6380880f97ff61f5153e29d35e8e2461ab4599e5f4de80fb015b66e64860e47fb9d333befb185302cd89fe907c342
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,4 +1,22 @@
|
|
1
|
+
gemfile:
|
2
|
+
- gemfiles/rails_5.0.gemfile
|
3
|
+
- gemfiles/rails_5.1.gemfile
|
4
|
+
- gemfiles/rails_5.2.gemfile
|
5
|
+
- gemfiles/rails_6.0.gemfile
|
6
|
+
|
1
7
|
rvm:
|
2
|
-
-
|
3
|
-
- 2.
|
4
|
-
- 2.
|
8
|
+
- 2.2
|
9
|
+
- 2.3
|
10
|
+
- 2.4
|
11
|
+
- 2.5
|
12
|
+
|
13
|
+
matrix:
|
14
|
+
exclude:
|
15
|
+
- rvm: 2.2
|
16
|
+
gemfile: gemfiles/rails_6.0.gemfile
|
17
|
+
- rvm: 2.2
|
18
|
+
gemfile: gemfiles/rails_5.2.gemfile
|
19
|
+
- rvm: 2.3
|
20
|
+
gemfile: gemfiles/rails_6.0.gemfile
|
21
|
+
- rvm: 2.4
|
22
|
+
gemfile: gemfiles/rails_6.0.gemfile
|
data/Appraisals
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
appraise "rails-6.0" do
|
2
|
+
gem "railties", "~> 6.0.0"
|
3
|
+
gem "activesupport", "~> 6.0.0"
|
4
|
+
end
|
5
|
+
|
6
|
+
appraise "rails-5.2" do
|
7
|
+
gem "railties", "~> 5.2.0"
|
8
|
+
gem "activesupport", "~> 5.2.0"
|
9
|
+
end
|
10
|
+
|
11
|
+
appraise "rails-5.1" do
|
12
|
+
gem "railties", "~> 5.1.0"
|
13
|
+
gem "activesupport", "~> 5.1.0"
|
14
|
+
end
|
15
|
+
|
16
|
+
appraise "rails-5.0" do
|
17
|
+
gem "railties", "~> 5.0.0"
|
18
|
+
gem "activesupport", "~> 5.0.0"
|
19
|
+
end
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Adequate Exposure
|
2
2
|
[](https://rubygems.org/gems/adequate_exposure)
|
3
3
|
[](http://travis-ci.org/rwz/adequate_exposure)
|
4
|
-
[](https://codeclimate.com/github/rwz/adequate_exposure/maintainability)
|
5
5
|
|
6
6
|
Exposing things, adequately.
|
7
7
|
|
8
8
|
Adequate exposure is a lightweight alternative to [Decent
|
9
|
-
Exposure](https://github.com/
|
9
|
+
Exposure](https://github.com/hashrocket/decent_exposure). With its narrowly
|
10
10
|
focused api you can get exactly what you need without all the extra dressing.
|
11
11
|
|
12
12
|
*Note: It is not the intent of the author to imply that Decent Exposure is
|
@@ -34,6 +34,46 @@ ID and try to perform `Thing.find(id)`. If the ID isn't found, it'll call
|
|
34
34
|
`Thing.new(things_params)`. The result will be memoized in an `@exposed_thing`
|
35
35
|
instance variable.
|
36
36
|
|
37
|
+
## Example Controller
|
38
|
+
|
39
|
+
Here's what a standard Rails 6 CRUD controller using Adequate Exposure might look like:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class ThingsController < ApplicationController
|
43
|
+
expose :things, ->{ Thing.all }
|
44
|
+
expose :thing
|
45
|
+
|
46
|
+
def create
|
47
|
+
if thing.save
|
48
|
+
redirect_to thing_path(thing)
|
49
|
+
else
|
50
|
+
render :new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def update
|
55
|
+
if thing.update(thing_params)
|
56
|
+
redirect_to thing_path(thing)
|
57
|
+
else
|
58
|
+
render :edit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def destroy
|
63
|
+
thing.destroy
|
64
|
+
redirect_to things_path
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def thing_params
|
70
|
+
params.require(:thing).permit(:foo, :bar)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
## Under the Hood
|
76
|
+
|
37
77
|
The default resolving workflow is pretty powerful and customizable. It could be
|
38
78
|
expressed with the following pseudocode:
|
39
79
|
|
@@ -120,7 +160,7 @@ with less code:
|
|
120
160
|
|
121
161
|
```ruby
|
122
162
|
expose :comments, from: :post
|
123
|
-
# equivalent to
|
163
|
+
# equivalent to
|
124
164
|
expose :comments, ->{ post.comments }
|
125
165
|
```
|
126
166
|
|
@@ -159,7 +199,7 @@ expose :thing, id: ->{ params[:try_this_id] || params[:or_maybe_that_id] }
|
|
159
199
|
### `find`
|
160
200
|
|
161
201
|
If an ID was provided, Adequate Exposure will try to find the model using it.
|
162
|
-
Default behavior could be expressed with this configuration:
|
202
|
+
Default behavior could be expressed with this configuration:
|
163
203
|
|
164
204
|
```ruby
|
165
205
|
expose :thing, find: ->(id, scope){ scope.find(id) }
|
@@ -273,7 +313,6 @@ expose :thing, with: [:cool_find, :cool_build]
|
|
273
313
|
expose :another_thing, with: :cool_build
|
274
314
|
```
|
275
315
|
|
276
|
-
|
277
316
|
## Contributing
|
278
317
|
|
279
318
|
1. Fork it (https://github.com/rwz/adequate_exposure/fork)
|
data/Rakefile
CHANGED
data/adequate_exposure.gemspec
CHANGED
@@ -12,8 +12,8 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.test_files = spec.files.grep(/\Aspec\//)
|
13
13
|
spec.require_path = "lib"
|
14
14
|
|
15
|
-
spec.required_ruby_version = ">=
|
15
|
+
spec.required_ruby_version = ">= 2.0"
|
16
16
|
|
17
|
-
spec.add_dependency "railties", "
|
18
|
-
spec.add_dependency "activesupport", "
|
17
|
+
spec.add_dependency "railties", ">= 5.0", "< 7"
|
18
|
+
spec.add_dependency "activesupport", ">= 5.0", "< 7"
|
19
19
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal", "~> 2.1"
|
6
|
+
gem "pry", "~> 0.10"
|
7
|
+
gem "rake", "~> 10.3"
|
8
|
+
gem "rspec-rails", "~> 3.9"
|
9
|
+
gem "railties", "~> 5.0.0"
|
10
|
+
gem "activesupport", "~> 5.0.0"
|
11
|
+
|
12
|
+
gemspec path: "../"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal", "~> 2.1"
|
6
|
+
gem "pry", "~> 0.10"
|
7
|
+
gem "rake", "~> 10.3"
|
8
|
+
gem "rspec-rails", "~> 3.9"
|
9
|
+
gem "railties", "~> 5.1.0"
|
10
|
+
gem "activesupport", "~> 5.1.0"
|
11
|
+
|
12
|
+
gemspec path: "../"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal", "~> 2.1"
|
6
|
+
gem "pry", "~> 0.10"
|
7
|
+
gem "rake", "~> 10.3"
|
8
|
+
gem "rspec-rails", "~> 3.9"
|
9
|
+
gem "railties", "~> 5.2.0"
|
10
|
+
gem "activesupport", "~> 5.2.0"
|
11
|
+
|
12
|
+
gemspec path: "../"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal", "~> 2.2"
|
6
|
+
gem "pry", "~> 0.12"
|
7
|
+
gem "rake", "~> 12.0"
|
8
|
+
gem "rspec-rails", "~> 3.9"
|
9
|
+
gem "railties", "~> 6.0.0"
|
10
|
+
gem "activesupport", "~> 6.0.0"
|
11
|
+
|
12
|
+
gemspec path: "../"
|
data/lib/adequate_exposure.rb
CHANGED
@@ -6,6 +6,7 @@ module AdequateExposure
|
|
6
6
|
autoload :Exposure, "adequate_exposure/exposure"
|
7
7
|
autoload :Attribute, "adequate_exposure/attribute"
|
8
8
|
autoload :Context, "adequate_exposure/context"
|
9
|
+
autoload :Behavior, "adequate_exposure/behavior"
|
9
10
|
autoload :Flow, "adequate_exposure/flow"
|
10
11
|
|
11
12
|
ActiveSupport.on_load :action_controller do
|
@@ -2,20 +2,42 @@ module AdequateExposure
|
|
2
2
|
class Attribute
|
3
3
|
attr_reader :name, :fetch, :ivar_name
|
4
4
|
|
5
|
+
# Public: Initialize an Attribute
|
6
|
+
#
|
7
|
+
# options - Hash of options for the Attribute
|
8
|
+
# :name - The String name of the Attribute instance
|
9
|
+
# :fetch - The Proc fetch to calculate
|
10
|
+
# the value of the Attribute instance.
|
11
|
+
# This is only called if the attribute's
|
12
|
+
# instance variable is not defined.
|
13
|
+
# :ivar_name - The String instance variable name that
|
14
|
+
# is associated with the attribute.
|
5
15
|
def initialize(options)
|
6
16
|
@name = options.fetch(:name)
|
7
17
|
@fetch = options.fetch(:fetch)
|
8
18
|
@ivar_name = options.fetch(:ivar_name)
|
9
19
|
end
|
10
20
|
|
21
|
+
# Public: The getter method for the Attribute.
|
22
|
+
#
|
23
|
+
# Returns the name of the Attribute as a Symbol.
|
11
24
|
def getter_method_name
|
12
25
|
name.to_sym
|
13
26
|
end
|
14
27
|
|
28
|
+
# Public: The setter method for the Attribute.
|
29
|
+
#
|
30
|
+
# Returns the name of the attribute as a Symbol with an appended '='.
|
15
31
|
def setter_method_name
|
16
32
|
"#{name}=".to_sym
|
17
33
|
end
|
18
34
|
|
35
|
+
|
36
|
+
# Public: Expose a getter and setter method for the Attribute
|
37
|
+
# on the passed in Controller class.
|
38
|
+
#
|
39
|
+
# klass - The Controller class where the Attribute getter and setter
|
40
|
+
# methods will be exposed.
|
19
41
|
def expose!(klass)
|
20
42
|
attribute = self
|
21
43
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module AdequateExposure
|
2
|
+
module Behavior
|
3
|
+
# Public: Fetches a scope.
|
4
|
+
#
|
5
|
+
# Finds an object. If it isn't found, the object gets instantiated.
|
6
|
+
#
|
7
|
+
# Returns the decorated object.
|
8
|
+
def fetch
|
9
|
+
instance = id ? find(id, computed_scope) : build(build_params, computed_scope)
|
10
|
+
decorate(instance)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Checks a params hash for an id attribute.
|
14
|
+
#
|
15
|
+
# Checks a hash of parameters for keys that represent an object's id.
|
16
|
+
#
|
17
|
+
# Returns the value of the id parameter, if it exists. Otherwise nil.
|
18
|
+
def id
|
19
|
+
params_id_key_candidates.each do |key|
|
20
|
+
value = params[key]
|
21
|
+
return value if value.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: An object query. Essentially, this method is designed to be
|
28
|
+
# overridden.
|
29
|
+
#
|
30
|
+
# model - The Class to be scoped or queried.
|
31
|
+
#
|
32
|
+
# Returns the object scope.
|
33
|
+
def scope(model)
|
34
|
+
model
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Converts a name into a standard Class name.
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
# 'egg_and_hams'.model # => EggAndHam
|
41
|
+
#
|
42
|
+
# Returns a standard Class name.
|
43
|
+
def model
|
44
|
+
name.to_s.classify.constantize
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Find an object on the supplied scope.
|
48
|
+
#
|
49
|
+
# id - The Integer id attribute of the desired object
|
50
|
+
# scope - The collection that will be searched.
|
51
|
+
#
|
52
|
+
# Returns the found object.
|
53
|
+
def find(id, scope)
|
54
|
+
scope.find(id)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Builds a new object on the passed-in scope.
|
58
|
+
#
|
59
|
+
# params - A Hash of attributes for the object to-be built.
|
60
|
+
# scope - The collection that will be searched.
|
61
|
+
#
|
62
|
+
# Returns the new object.
|
63
|
+
def build(params, scope)
|
64
|
+
scope.new(params)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Returns a decorated object. This method is designed to be
|
68
|
+
# overridden.
|
69
|
+
#
|
70
|
+
# Returns the decorated object.
|
71
|
+
def decorate(instance)
|
72
|
+
instance
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Get all the parameters of the current request.
|
76
|
+
#
|
77
|
+
# Returns the controller's parameters for the current request.
|
78
|
+
def build_params
|
79
|
+
if controller.respond_to?(params_method_name, true) && !get_request?
|
80
|
+
controller.send(params_method_name)
|
81
|
+
else
|
82
|
+
{}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def params_id_key_candidates
|
89
|
+
[ "#{model_param_key}_id", "#{name}_id", "id" ].uniq
|
90
|
+
end
|
91
|
+
|
92
|
+
def model_param_key
|
93
|
+
model.name.underscore
|
94
|
+
end
|
95
|
+
|
96
|
+
def computed_scope
|
97
|
+
scope(model)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -2,14 +2,33 @@ module AdequateExposure
|
|
2
2
|
class Context
|
3
3
|
attr_reader :context, :attribute
|
4
4
|
|
5
|
+
# Public: Initialize a context.
|
6
|
+
#
|
7
|
+
# context - The Class where the attribute is defined.
|
8
|
+
# attribute - The attribute that will be accessed by a getter
|
9
|
+
# and setter.
|
5
10
|
def initialize(context, attribute)
|
6
11
|
@context, @attribute = context, attribute
|
7
12
|
end
|
8
13
|
|
14
|
+
# Public: Read an attribute on the context Class.
|
15
|
+
#
|
16
|
+
# Get an attribute's value. If the attribute's instance
|
17
|
+
# variable is not defined, it will create one,
|
18
|
+
# execute attribute#fetch, and assign the result
|
19
|
+
# to the instance variable.
|
20
|
+
#
|
21
|
+
# Returns the attribute's value.
|
9
22
|
def get
|
10
23
|
ivar_defined?? ivar_get : set(fetch_value)
|
11
24
|
end
|
12
25
|
|
26
|
+
# Public: Write to an attribute on the context Class.
|
27
|
+
#
|
28
|
+
# value - The value that will be set to the attribute's
|
29
|
+
# instance variable.
|
30
|
+
#
|
31
|
+
# Returns the attribute's value.
|
13
32
|
def set(value)
|
14
33
|
ivar_set(value)
|
15
34
|
end
|
@@ -2,18 +2,48 @@ module AdequateExposure
|
|
2
2
|
module Controller
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
included
|
5
|
+
included do
|
6
|
+
class_attribute :exposure_configuration,
|
7
|
+
instance_accessor: false, instance_predicate: false
|
8
|
+
end
|
6
9
|
|
7
10
|
module ClassMethods
|
8
|
-
|
9
|
-
|
11
|
+
# Public: Exposes an attribute to a controller Class.
|
12
|
+
#
|
13
|
+
# *args - An Array of attributes for the new exposure. See
|
14
|
+
# Exposure#initialize for attribute details.
|
15
|
+
# block - If supplied, the exposed attribute method executes
|
16
|
+
# the Proc when accessed.
|
17
|
+
#
|
18
|
+
# Returns the helper methods that are now defined on the class
|
19
|
+
# where this method is included.
|
20
|
+
def expose(*args, **options, &block)
|
21
|
+
Exposure.expose! self, *args, **options, &block
|
10
22
|
end
|
11
23
|
|
24
|
+
# Public: Exposes an attribute to a controller Class.
|
25
|
+
# The exposed methods are then set to a before_action
|
26
|
+
# callback.
|
27
|
+
#
|
28
|
+
# name - The String name of the Exposure instance.
|
29
|
+
# *args - An Array of attributes for the new exposure. See
|
30
|
+
# Exposure#initialize for attribute details.
|
31
|
+
# block - If supplied, the exposed attribute method executes
|
32
|
+
# the Proc when accessed.
|
33
|
+
#
|
34
|
+
# Sets the exposed attribute to a before_action callback in the
|
35
|
+
# controller.
|
12
36
|
def expose!(name, *args, &block)
|
13
37
|
expose name, *args, &block
|
14
38
|
before_action name
|
15
39
|
end
|
16
40
|
|
41
|
+
# Public: Configures an Exposure instance for a controller Class.
|
42
|
+
#
|
43
|
+
# name - The String name of the Exposure instance.
|
44
|
+
# options - The Hash of options to configure the Exposure instance.
|
45
|
+
#
|
46
|
+
# Returns the exposure configuration Hash.
|
17
47
|
def exposure_config(name, options)
|
18
48
|
store = self.exposure_configuration ||= {}
|
19
49
|
self.exposure_configuration = store.merge(name => options)
|
@@ -2,14 +2,41 @@ module AdequateExposure
|
|
2
2
|
class Exposure
|
3
3
|
attr_reader :controller, :options
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
# Public: Initializes an Exposure and makes it accessible to a controller.
|
6
|
+
# For each Exposure, a getter and setter is defined.
|
7
|
+
# Those getters and setters are made available to
|
8
|
+
# the controller as helper methods.
|
9
|
+
#
|
10
|
+
# *args - An Array of all parameters for the new Exposure. See
|
11
|
+
# #initialize.
|
12
|
+
# **args - A Hash of options. See #initialize.
|
13
|
+
# block - If supplied, the exposed attribute method executes
|
14
|
+
# the Proc when called.
|
15
|
+
#
|
16
|
+
# Returns a collection of exposed helper methods.
|
17
|
+
def self.expose!(*args, **options, &block)
|
18
|
+
new(*args, **options, &block).expose!
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Initalize an Exposure with a hash of options.
|
22
|
+
#
|
23
|
+
# If a block is given, the Proc is assigned to value
|
24
|
+
# of options[name].
|
25
|
+
#
|
26
|
+
# The `asserts_*` section raise errors if the controller
|
27
|
+
# was initialized with an unacceptable options Hash.
|
28
|
+
#
|
29
|
+
# controller - The Controller class where methods will be exposed.
|
30
|
+
# name - The String name of the Exposure instance.
|
31
|
+
# fetch_block - Proc that will be executed if the exposed
|
32
|
+
# attribute has no value (default: nil).
|
33
|
+
# options - Hash of options for the Behavior of the exposed methods.
|
34
|
+
# block - If supplied, the exposed attribute method executes
|
35
|
+
# the Proc.
|
36
|
+
#
|
37
|
+
# Returns a normalized options Hash.
|
38
|
+
def initialize(controller, name, fetch_block=nil, **options, &block)
|
10
39
|
@controller = controller
|
11
|
-
options = args.extract_options!
|
12
|
-
fetch_block = args.pop
|
13
40
|
@options = options.with_indifferent_access.merge(name: name)
|
14
41
|
|
15
42
|
merge_lambda_option :fetch, fetch_block if fetch_block
|
@@ -24,6 +51,11 @@ module AdequateExposure
|
|
24
51
|
normalize_options
|
25
52
|
end
|
26
53
|
|
54
|
+
# Public: Creates a getter and setter methods for the attribute.
|
55
|
+
# Those methods are made avaiable to the controller as
|
56
|
+
# helper methods.
|
57
|
+
#
|
58
|
+
# Returns a collection of exposed helper methods.
|
27
59
|
def expose!
|
28
60
|
expose_attribute!
|
29
61
|
expose_helper_methods!
|
@@ -76,7 +108,9 @@ module AdequateExposure
|
|
76
108
|
exposure_name = options.fetch(:name)
|
77
109
|
|
78
110
|
if from = options.delete(:from)
|
79
|
-
merge_lambda_option :
|
111
|
+
merge_lambda_option :build, ->{ send(from).send(exposure_name) }
|
112
|
+
merge_lambda_option :model, ->{ nil }
|
113
|
+
merge_lambda_option :id, ->{ nil }
|
80
114
|
end
|
81
115
|
end
|
82
116
|
|
@@ -161,7 +195,7 @@ module AdequateExposure
|
|
161
195
|
end
|
162
196
|
|
163
197
|
def assert_singleton_option(name)
|
164
|
-
if options.except(name, :name).any? && options.key?(name)
|
198
|
+
if options.except(name, :name, :decorate).any? && options.key?(name)
|
165
199
|
fail ArgumentError, "Using #{name.inspect} option with other options doesn't make sense"
|
166
200
|
end
|
167
201
|
end
|
@@ -2,63 +2,39 @@ module AdequateExposure
|
|
2
2
|
class Flow
|
3
3
|
attr_reader :controller, :options, :name
|
4
4
|
|
5
|
+
# Public: Initialize a Flow. This object responds to missing
|
6
|
+
# methods errors and attempts to delegate them to other objects.
|
7
|
+
#
|
8
|
+
# controller - The Controller class where the method was called.
|
9
|
+
# options - The options Hash of the Exposure instance being called.
|
10
|
+
# name - The String name of the Exposure instance.
|
5
11
|
def initialize(controller, options)
|
6
12
|
@controller = controller
|
7
13
|
@options = options
|
8
14
|
@name = options.fetch(:name)
|
9
15
|
end
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
# Public: Attempts to re-delegate a method missing to the
|
18
|
+
# supplied block or the Behavior object.
|
19
|
+
#
|
20
|
+
# name - The String name of the Exposure instance.
|
21
|
+
# *args - The arguments given for the missing method.
|
22
|
+
# block - The Proc invoked by the method.
|
23
|
+
def method_missing(name, *args, &block)
|
24
|
+
if respond_to_missing?(name)
|
25
|
+
handle_flow_method(name, *args, &block)
|
26
|
+
else
|
27
|
+
super
|
16
28
|
end
|
17
29
|
end
|
18
30
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def default_id
|
28
|
-
params_id_key_candidates.each do |key|
|
29
|
-
value = params[key]
|
30
|
-
return value if value.present?
|
31
|
-
end
|
32
|
-
|
33
|
-
nil
|
34
|
-
end
|
35
|
-
|
36
|
-
def default_scope(model)
|
37
|
-
model
|
38
|
-
end
|
39
|
-
|
40
|
-
def default_model
|
41
|
-
name.to_s.classify.constantize
|
42
|
-
end
|
43
|
-
|
44
|
-
def default_find(id, scope)
|
45
|
-
scope.find(id)
|
46
|
-
end
|
47
|
-
|
48
|
-
def default_build(params, scope)
|
49
|
-
scope.new(params)
|
50
|
-
end
|
51
|
-
|
52
|
-
def default_decorate(instance)
|
53
|
-
instance
|
54
|
-
end
|
55
|
-
|
56
|
-
def default_build_params
|
57
|
-
if controller.respond_to?(params_method_name, true) && !get_request?
|
58
|
-
controller.send(params_method_name)
|
59
|
-
else
|
60
|
-
{}
|
61
|
-
end
|
31
|
+
# Public: Checks if the Behavior class can handle the missing method.
|
32
|
+
#
|
33
|
+
# method_name - The name of method that has been called.
|
34
|
+
# include_private - Prevents this method from catching calls to private
|
35
|
+
# method (default: false).
|
36
|
+
def respond_to_missing?(method_name, include_private = false)
|
37
|
+
Behavior.method_defined?(method_name) || super
|
62
38
|
end
|
63
39
|
|
64
40
|
private
|
@@ -73,15 +49,17 @@ module AdequateExposure
|
|
73
49
|
options.fetch(:build_params_method){ "#{name}_params" }
|
74
50
|
end
|
75
51
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
52
|
+
def handle_flow_method(name, *args, &block)
|
53
|
+
fetch_ivar name do
|
54
|
+
if options.key?(name)
|
55
|
+
handle_options_override(name, *args, &block)
|
56
|
+
else
|
57
|
+
handle_default_flow_method(name, *args, &block)
|
58
|
+
end
|
81
59
|
end
|
82
60
|
end
|
83
61
|
|
84
|
-
def
|
62
|
+
def handle_options_override(name, *args)
|
85
63
|
value = options[name]
|
86
64
|
|
87
65
|
if Proc === value
|
@@ -92,12 +70,20 @@ module AdequateExposure
|
|
92
70
|
end
|
93
71
|
end
|
94
72
|
|
95
|
-
def
|
96
|
-
|
73
|
+
def handle_default_flow_method(name, *args, &block)
|
74
|
+
method = Behavior.instance_method(name)
|
75
|
+
method.bind(self).call(*args, &block)
|
97
76
|
end
|
98
77
|
|
99
|
-
|
100
|
-
|
78
|
+
|
79
|
+
def fetch_ivar(name)
|
80
|
+
ivar_name = "@#{name}"
|
81
|
+
|
82
|
+
if instance_variable_defined?(ivar_name)
|
83
|
+
instance_variable_get(ivar_name)
|
84
|
+
else
|
85
|
+
instance_variable_set(ivar_name, yield)
|
86
|
+
end
|
101
87
|
end
|
102
88
|
end
|
103
89
|
end
|
data/spec/controller_spec.rb
CHANGED
@@ -23,8 +23,8 @@ describe AdequateExposure::Controller do
|
|
23
23
|
before{ allow(controller).to receive(:request){ request } }
|
24
24
|
|
25
25
|
%w[expose expose! exposure_config].each do |method_name|
|
26
|
-
define_method method_name do |*args, &block|
|
27
|
-
controller_klass.
|
26
|
+
define_method method_name do |*args, **keyword_args, &block|
|
27
|
+
controller_klass.public_send method_name, *args, **keyword_args, &block
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -348,10 +348,9 @@ describe AdequateExposure::Controller do
|
|
348
348
|
|
349
349
|
context "from option" do
|
350
350
|
it "allows scope to be called from method" do
|
351
|
-
post = double("Post")
|
352
351
|
comments = double("Comments")
|
352
|
+
post = double("Post", comments: comments)
|
353
353
|
allow(controller).to receive(:post).and_return(post)
|
354
|
-
expect(post).to receive(:comments).and_return(comments)
|
355
354
|
expose :comments, from: :post
|
356
355
|
|
357
356
|
expect(controller.comments).to eq(comments)
|
@@ -361,5 +360,15 @@ describe AdequateExposure::Controller do
|
|
361
360
|
action = ->{ expose :thing, from: :foo, parent: :bar }
|
362
361
|
expect(&action).to raise_error(ArgumentError, "Using :from option with other options doesn't make sense")
|
363
362
|
end
|
363
|
+
|
364
|
+
it "should still work with decorate option" do
|
365
|
+
decorated_thing = double("DecoratedThing")
|
366
|
+
thing = double("Thing")
|
367
|
+
foo = double("Foo", thing: thing)
|
368
|
+
expect(controller).to receive(:foo).and_return(foo)
|
369
|
+
expect(controller).to receive(:decorate).with(thing).and_return(decorated_thing)
|
370
|
+
expose :thing, from: :foo, decorate: ->(thing){ decorate(thing) }
|
371
|
+
expect(controller.thing).to eq(decorated_thing)
|
372
|
+
end
|
364
373
|
end
|
365
374
|
end
|
data/spec/integration_spec.rb
CHANGED
@@ -9,11 +9,11 @@ describe BirdsController, type: :controller do
|
|
9
9
|
after{ expect(controller.bird).to eq(mockingbird) }
|
10
10
|
|
11
11
|
it "finds model by id" do
|
12
|
-
get :show, id: "mockingbird"
|
12
|
+
get :show, params: { id: "mockingbird" }
|
13
13
|
end
|
14
14
|
|
15
15
|
it "finds model by bird_id" do
|
16
|
-
get :show, bird_id: "mockingbird"
|
16
|
+
get :show, params: { bird_id: "mockingbird" }
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
data/spec/support/rails_app.rb
CHANGED
metadata
CHANGED
@@ -1,43 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adequate_exposure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pavel Pravosud
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5.0'
|
30
|
+
- - "<"
|
25
31
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
32
|
+
version: '7'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: activesupport
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
|
-
- - "
|
37
|
+
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
39
|
+
version: '5.0'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '7'
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
38
|
-
- - "
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '5.0'
|
50
|
+
- - "<"
|
39
51
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
52
|
+
version: '7'
|
41
53
|
description:
|
42
54
|
email:
|
43
55
|
- pavel@pravosud.com
|
@@ -47,13 +59,19 @@ extra_rdoc_files: []
|
|
47
59
|
files:
|
48
60
|
- ".gitignore"
|
49
61
|
- ".travis.yml"
|
62
|
+
- Appraisals
|
50
63
|
- Gemfile
|
51
64
|
- LICENSE.txt
|
52
65
|
- README.md
|
53
66
|
- Rakefile
|
54
67
|
- adequate_exposure.gemspec
|
68
|
+
- gemfiles/rails_5.0.gemfile
|
69
|
+
- gemfiles/rails_5.1.gemfile
|
70
|
+
- gemfiles/rails_5.2.gemfile
|
71
|
+
- gemfiles/rails_6.0.gemfile
|
55
72
|
- lib/adequate_exposure.rb
|
56
73
|
- lib/adequate_exposure/attribute.rb
|
74
|
+
- lib/adequate_exposure/behavior.rb
|
57
75
|
- lib/adequate_exposure/context.rb
|
58
76
|
- lib/adequate_exposure/controller.rb
|
59
77
|
- lib/adequate_exposure/exposure.rb
|
@@ -75,15 +93,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
93
|
requirements:
|
76
94
|
- - ">="
|
77
95
|
- !ruby/object:Gem::Version
|
78
|
-
version:
|
96
|
+
version: '2.0'
|
79
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
98
|
requirements:
|
81
99
|
- - ">="
|
82
100
|
- !ruby/object:Gem::Version
|
83
101
|
version: '0'
|
84
102
|
requirements: []
|
85
|
-
|
86
|
-
rubygems_version: 2.2.2
|
103
|
+
rubygems_version: 3.1.2
|
87
104
|
signing_key:
|
88
105
|
specification_version: 4
|
89
106
|
summary: Exposing things, adequately
|