adequate_exposure 0.2.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://img.shields.io/gem/v/adequate_exposure.svg)](https://rubygems.org/gems/adequate_exposure)
|
3
3
|
[![Build Status](https://img.shields.io/travis/rwz/adequate_exposure.svg)](http://travis-ci.org/rwz/adequate_exposure)
|
4
|
-
[![
|
4
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/50f8fdfcb0cf57ea3798/maintainability)](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
|