xpose 0.1.5 → 0.1.6
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 +4 -4
- data/README.md +144 -1
- data/lib/xpose.rb +2 -1
- data/lib/xpose/configuration.rb +33 -14
- data/lib/xpose/controller.rb +13 -18
- data/lib/xpose/decorated.rb +25 -23
- data/lib/xpose/exposed.rb +42 -37
- data/lib/xpose/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db1cd9178831f764322ecc1fcee8c71cbea41bbf
|
4
|
+
data.tar.gz: ef53b5687506f665152a79821410c1b6037ae6b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d75cf01c927100f4a08c015646dd3a9e492fc3861813e94032dba6074abe78771fd12eaf475dd67bdca60e0eeaddcdc5ad05f7df9b51ae27b1e48f7f630c7b5
|
7
|
+
data.tar.gz: ab816dd1c4c781774413ad84c41f58297295e3bce1c7dc3e4fab085b6de2709635ea103e1e113b547fd6ebe73e19e1267b751236b195c943fb9b76e7c289df41
|
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Xpose
|
2
2
|
|
3
|
+
[](https://rubygems.org/gems/xpose)
|
4
|
+
|
5
|
+
## Presentation
|
6
|
+
|
7
|
+
**Xpose** provides a set of helpers that help you write smaller, cleaner controllers.
|
8
|
+
|
3
9
|
## Installation
|
4
10
|
|
5
11
|
Add this line to your application's Gemfile:
|
@@ -10,7 +16,144 @@ gem 'xpose'
|
|
10
16
|
|
11
17
|
And then execute:
|
12
18
|
|
13
|
-
|
19
|
+
```shell
|
20
|
+
bundle install
|
21
|
+
```
|
22
|
+
|
23
|
+
## Basic usage
|
24
|
+
|
25
|
+
Here is a basic scaffolded controller in Rails (comments removed):
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class ArticlesController < ApplicationController
|
29
|
+
before_action :set_article, only: [:show, :edit, :update, :destroy]
|
30
|
+
|
31
|
+
def index
|
32
|
+
@articles = Article.all
|
33
|
+
end
|
34
|
+
|
35
|
+
def show
|
36
|
+
end
|
37
|
+
|
38
|
+
def new
|
39
|
+
@article = Article.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def edit
|
43
|
+
end
|
44
|
+
|
45
|
+
def create
|
46
|
+
@article = Article.new(article_params)
|
47
|
+
|
48
|
+
respond_to do |format|
|
49
|
+
if @article.save
|
50
|
+
format.html { redirect_to @article, notice: 'Article was successfully created.' }
|
51
|
+
format.json { render :show, status: :created, location: @article }
|
52
|
+
else
|
53
|
+
format.html { render :new }
|
54
|
+
format.json { render json: @article.errors, status: :unprocessable_entity }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def update
|
60
|
+
respond_to do |format|
|
61
|
+
if @article.update(article_params)
|
62
|
+
format.html { redirect_to @article, notice: 'Article was successfully updated.' }
|
63
|
+
format.json { render :show, status: :ok, location: @article }
|
64
|
+
else
|
65
|
+
format.html { render :edit }
|
66
|
+
format.json { render json: @article.errors, status: :unprocessable_entity }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def destroy
|
72
|
+
@article.destroy
|
73
|
+
respond_to do |format|
|
74
|
+
format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
|
75
|
+
format.json { head :no_content }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def set_article
|
81
|
+
@article = Article.find(params[:id])
|
82
|
+
end
|
83
|
+
|
84
|
+
def article_params
|
85
|
+
params.require(:article).permit(:name, :content)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Here is the Xpose-friendly equivalent:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class ArticlesController < ApplicationController
|
94
|
+
expose :articles
|
95
|
+
expose :article
|
96
|
+
|
97
|
+
def index
|
98
|
+
end
|
99
|
+
|
100
|
+
def show
|
101
|
+
end
|
102
|
+
|
103
|
+
def new
|
104
|
+
end
|
105
|
+
|
106
|
+
def create
|
107
|
+
if article.save
|
108
|
+
redirect_to article, notice: 'Article was successfully created.'
|
109
|
+
else
|
110
|
+
render :new
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def edit
|
115
|
+
end
|
116
|
+
|
117
|
+
def update
|
118
|
+
if article.update(article_params)
|
119
|
+
redirect_to article, notice: 'Article was successfully updated.'
|
120
|
+
else
|
121
|
+
render :edit
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def destroy
|
126
|
+
article.destroy
|
127
|
+
redirect_to articles_url, notice: 'Article was successfully destroyed.'
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def article_params
|
133
|
+
params.require(:article).permit(:name)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
## Helpers
|
139
|
+
|
140
|
+
### expose
|
141
|
+
|
142
|
+
`expose` provides powerful tools to help you minimize your controllers.
|
143
|
+
|
144
|
+
Default values:
|
145
|
+
`expose :name, :infer, scope: :all, decorate: true, decorator: :infer`
|
146
|
+
|
147
|
+
Examples:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
expose :articles
|
151
|
+
expose :articles, scope: :visible
|
152
|
+
expose :article
|
153
|
+
expose :forthy_two, 42, decorate: false
|
154
|
+
expose :bob, "Bob", decorator: -> { |v| v.length }
|
155
|
+
|
156
|
+
```
|
14
157
|
|
15
158
|
## Usage
|
16
159
|
|
data/lib/xpose.rb
CHANGED
@@ -2,7 +2,8 @@ require "xpose/version"
|
|
2
2
|
require "active_support/all"
|
3
3
|
|
4
4
|
module Xpose
|
5
|
-
class
|
5
|
+
class UnknownOptionsError < StandardError ; end
|
6
|
+
class MissingOptionError < StandardError ; end
|
6
7
|
class UnknownDecoratorError < StandardError ; end
|
7
8
|
|
8
9
|
autoload :Configuration, 'xpose/configuration.rb'
|
data/lib/xpose/configuration.rb
CHANGED
@@ -1,29 +1,48 @@
|
|
1
1
|
module Xpose
|
2
|
-
|
2
|
+
class Configuration
|
3
|
+
|
3
4
|
DEFAULT_VALUES = {
|
4
5
|
name: nil,
|
5
6
|
value: nil,
|
6
7
|
decorate: true,
|
7
8
|
decorator: :infer,
|
8
|
-
infer_value: true,
|
9
9
|
scope: :all
|
10
|
+
# source: :infer (:infer, :method, : .call : ...)
|
10
11
|
}.freeze
|
11
12
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
13
|
+
def initialize(**options)
|
14
|
+
@options = options
|
15
|
+
permit_options! unless options.fetch(:permissive, false)
|
16
|
+
build_config
|
17
|
+
build_internal_defaults
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
config.send(method, *args, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def model
|
25
|
+
config.singularized_name.capitalize.constantize
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_accessor :config
|
31
|
+
|
32
|
+
def permit_options!
|
33
|
+
(@options.keys - DEFAULT_VALUES.keys).tap do |unknown_keys|
|
34
|
+
raise UnknownOptionsError.new(unknown_keys) unless unknown_keys.empty?
|
15
35
|
end
|
16
|
-
|
17
|
-
conf.name = conf.name.to_s
|
18
|
-
conf.method_name = conf.name.to_sym
|
19
|
-
conf.instance_variable_name = :"@#{conf.method_name}"
|
36
|
+
end
|
20
37
|
|
21
|
-
|
22
|
-
|
38
|
+
def build_config
|
39
|
+
@config = OpenStruct.new(DEFAULT_VALUES.merge(@options)).tap do |c|
|
40
|
+
raise MissingOptionsError.new(:name) if c.name.blank?
|
23
41
|
|
24
|
-
|
25
|
-
|
26
|
-
|
42
|
+
c.name = c.name.to_sym
|
43
|
+
c.ivar_name = :"@#{c.name}"
|
44
|
+
c.singularized_name = c.name.to_s.singularize
|
45
|
+
c.pluralized_name = c.singularized_name.pluralize
|
27
46
|
end
|
28
47
|
end
|
29
48
|
end
|
data/lib/xpose/controller.rb
CHANGED
@@ -5,36 +5,31 @@ module Xpose
|
|
5
5
|
module ClassMethods
|
6
6
|
def _expose(**args)
|
7
7
|
::Xpose::Exposed.new(args).tap do |inst|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
@@exposed ||= {}
|
9
|
+
@@exposed[inst.conf.name] = inst
|
10
|
+
define_method inst.conf.name do
|
11
|
+
if instance_variable_defined?(inst.conf.ivar_name)
|
12
|
+
instance_variable_get(inst.conf.ivar_name)
|
11
13
|
else
|
12
|
-
instance_variable_set(inst.conf.
|
14
|
+
instance_variable_set(inst.conf.ivar_name, inst.exposed_value(self))
|
13
15
|
end
|
14
16
|
end
|
15
|
-
helper_method(inst.conf.
|
16
|
-
self._decorate(args) if inst.conf[:decorate]
|
17
|
+
helper_method(inst.conf.name)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
def expose(
|
21
|
+
def expose(names, value = nil, **args, &block)
|
21
22
|
value = value || args.fetch(:value, nil) || block
|
22
|
-
_expose({ name: name, value: value }.merge(args))
|
23
|
+
[names].flatten.each { |name| _expose({ name: name, value: value }.merge(args)) }
|
23
24
|
end
|
24
25
|
|
25
|
-
def expose!(
|
26
|
+
def expose!(names, value = nil, **args, &block)
|
26
27
|
expose(name, value, args, &block)
|
27
|
-
before_action(name)
|
28
|
+
[names].flatten.each { |name| before_action(name) }
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
|
32
|
-
_expose({ name: inst.conf.decorated_name, value: -> { inst.call(self) }, decorate: false })
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def decorate(name, **args, &block)
|
37
|
-
_decorate({ name: name }.merge(args))
|
31
|
+
def exposed
|
32
|
+
@@exposed ||= {}
|
38
33
|
end
|
39
34
|
end
|
40
35
|
end
|
data/lib/xpose/decorated.rb
CHANGED
@@ -1,52 +1,54 @@
|
|
1
1
|
module Xpose
|
2
2
|
class Decorated
|
3
|
-
|
3
|
+
attr_reader :conf
|
4
4
|
|
5
|
-
def initialize(**
|
6
|
-
@conf = ::Xpose::Configuration.
|
7
|
-
raise MissingParameter if conf.name.nil?
|
8
|
-
decorate_self if conf.decorate == :self
|
5
|
+
def initialize(**options)
|
6
|
+
@conf = ::Xpose::Configuration.new(options.merge(permissive: true))
|
9
7
|
end
|
10
8
|
|
11
|
-
def
|
12
|
-
v
|
9
|
+
def value(instance, v)
|
10
|
+
return v unless shall_decorate?(instance, v)
|
13
11
|
if conf.decorator == :infer
|
14
12
|
infer(v)
|
15
13
|
elsif Class === conf.decorator
|
16
|
-
decorator.new(v)
|
17
|
-
elsif decorator.respond_to?(:call)
|
18
|
-
decorator.call(v)
|
19
|
-
elsif Symbol === decorator
|
20
|
-
|
14
|
+
conf.decorator.new(v)
|
15
|
+
elsif conf.decorator.respond_to?(:call)
|
16
|
+
conf.decorator.call(v)
|
17
|
+
elsif Symbol === conf.decorator && class_exists?(klass_from_symbol)
|
18
|
+
klass_from_symbol.new(v)
|
21
19
|
else
|
22
|
-
raise
|
20
|
+
raise UnknownDecoratorError.new(conf.decorator)
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
26
24
|
private
|
27
25
|
|
28
|
-
def
|
29
|
-
conf.
|
30
|
-
|
31
|
-
|
26
|
+
def shall_decorate?(instance, v)
|
27
|
+
return conf.decorate if [true, false].include?(conf.decorate)
|
28
|
+
raise UnknownOptionsError.new(:decorate) unless conf.decorate.respond_to?(:call)
|
29
|
+
instance.instance_exec &conf.decorate
|
32
30
|
end
|
33
31
|
|
34
32
|
def infer(v)
|
35
33
|
if v.respond_to?(:decorate)
|
36
34
|
v.decorate
|
37
|
-
elsif class_exists?(
|
38
|
-
|
35
|
+
elsif class_exists?(klass_from_model)
|
36
|
+
klass_from_model.new(v)
|
39
37
|
else
|
40
|
-
raise UnknownDecoratorError
|
38
|
+
raise UnknownDecoratorError.new(conf.decorator)
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
44
|
-
def
|
45
|
-
|
42
|
+
def klass_from_symbol
|
43
|
+
conf.decorator.to_s.singularize.capitalize.constantize
|
44
|
+
end
|
45
|
+
|
46
|
+
def klass_from_model
|
47
|
+
"#{conf.model}Decorator".constantize
|
46
48
|
end
|
47
49
|
|
48
50
|
def class_exists?(class_name)
|
49
|
-
Module.const_get(class_name).is_a?(Class)
|
51
|
+
Module.const_get(class_name.to_s).is_a?(Class)
|
50
52
|
rescue NameError
|
51
53
|
return false
|
52
54
|
end
|
data/lib/xpose/exposed.rb
CHANGED
@@ -3,62 +3,61 @@ module Xpose
|
|
3
3
|
attr_accessor :conf
|
4
4
|
|
5
5
|
def initialize(**args)
|
6
|
-
@conf = ::Xpose::Configuration.
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
@conf = ::Xpose::Configuration.new(args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def value(instance)
|
10
|
+
@instance = instance
|
11
|
+
@value ||= interpret_value
|
10
12
|
end
|
11
13
|
|
12
|
-
def
|
14
|
+
def decorated_value(instance)
|
13
15
|
@instance = instance
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
@decorated_value ||=
|
17
|
+
if value(instance) && conf.decorate
|
18
|
+
::Xpose::Decorated.new(conf.to_h).value(instance, value(instance))
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def exposed_value(instance)
|
25
|
+
decorated_value(instance) || value(instance)
|
20
26
|
end
|
21
27
|
|
22
28
|
private
|
23
29
|
|
24
30
|
attr_reader :instance
|
25
31
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
def interpret_value
|
33
|
+
if conf.value.respond_to?(:call)
|
34
|
+
instance.instance_exec &conf.value
|
35
|
+
else
|
36
|
+
infer_value
|
37
|
+
end
|
30
38
|
end
|
31
39
|
|
32
|
-
def
|
33
|
-
|
40
|
+
def infer_value
|
41
|
+
conf.value == :collection ? infer_collection : infer_record
|
34
42
|
end
|
35
43
|
|
36
|
-
def
|
37
|
-
|
38
|
-
instance.instance_exec &v
|
39
|
-
elsif v == :collection
|
40
|
-
infer_collection
|
41
|
-
elsif v == :record
|
42
|
-
infer_record
|
43
|
-
else
|
44
|
-
v
|
45
|
-
end
|
44
|
+
def infer_collection
|
45
|
+
conf.model.send(conf.scope)
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
49
|
-
|
48
|
+
def record_source
|
49
|
+
if instance.respond_to?(conf.pluralized_name)
|
50
|
+
instance.class.exposed[conf.pluralized_name.to_sym].value(instance)
|
51
|
+
else
|
52
|
+
conf.model.send(conf.scope)
|
53
|
+
end
|
50
54
|
end
|
51
55
|
|
52
56
|
def infer_record
|
53
|
-
|
54
|
-
|
55
|
-
else
|
56
|
-
->{ klass.send(conf.scope) }
|
57
|
-
end
|
58
|
-
if instance.respond_to?(:params) && instance.params.has_key?(:id)
|
59
|
-
source.call.find(instance.params[:id])
|
57
|
+
if instance.respond_to?(:params, true) && instance.params.has_key?(:id)
|
58
|
+
record_source.find(instance.params[:id])
|
60
59
|
else
|
61
|
-
|
60
|
+
record_source.new(params)
|
62
61
|
end
|
63
62
|
end
|
64
63
|
|
@@ -72,5 +71,11 @@ module Xpose
|
|
72
71
|
end
|
73
72
|
{}
|
74
73
|
end
|
74
|
+
|
75
|
+
def class_exists?(class_name)
|
76
|
+
Module.const_get(class_name).is_a?(Class)
|
77
|
+
rescue NameError
|
78
|
+
return false
|
79
|
+
end
|
75
80
|
end
|
76
81
|
end
|
data/lib/xpose/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xpose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Younes SERRAJ
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|