xpose 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02eea2368bfae7bf652a48cf71db7f0357c8ab25
4
- data.tar.gz: 0053e667c4b3ecf3dbf72018b531ac93eaff398d
3
+ metadata.gz: db1cd9178831f764322ecc1fcee8c71cbea41bbf
4
+ data.tar.gz: ef53b5687506f665152a79821410c1b6037ae6b5
5
5
  SHA512:
6
- metadata.gz: ad476f2c9b6c26494c61a1ad15f2b69a35220e72e756bb699ca2051a275cb04f0f6657b2a75df0586e1c04453f94f6a271a91c781ec3f8124441d178ad1eb208
7
- data.tar.gz: 715cda9831883f00b03133bf9c7655412b73ae3e43a44ca93cdadd1b64afed9e268b1358ecb7ad3b48089f69dc910aa2e50993deac29a8f78d8e72550b53d4fa
6
+ metadata.gz: 2d75cf01c927100f4a08c015646dd3a9e492fc3861813e94032dba6074abe78771fd12eaf475dd67bdca60e0eeaddcdc5ad05f7df9b51ae27b1e48f7f630c7b5
7
+ data.tar.gz: ab816dd1c4c781774413ad84c41f58297295e3bce1c7dc3e4fab085b6de2709635ea103e1e113b547fd6ebe73e19e1267b751236b195c943fb9b76e7c289df41
data/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Xpose
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/xpose.svg)](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
- $ bundle
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
 
@@ -2,7 +2,8 @@ require "xpose/version"
2
2
  require "active_support/all"
3
3
 
4
4
  module Xpose
5
- class MissingParameterError < StandardError ; end
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'
@@ -1,29 +1,48 @@
1
1
  module Xpose
2
- module Configuration
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 self.build(**args)
13
- args = DEFAULT_VALUES.merge(args).keep_if do |k, v|
14
- DEFAULT_VALUES.has_key?(k)
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
- OpenStruct.new(args).tap do |conf|
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
- conf.singularized_name = conf.name.singularize
22
- conf.pluralized_name = conf.name.pluralize
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
- conf.decorated_name = "decorated_#{conf.name}"
25
- conf.decorated_method_name = conf.decorated_name.to_sym
26
- conf.decorated_instance_variable_name = :"@decorated_#{conf.method_name}"
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
@@ -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
- define_method inst.conf.method_name do
9
- if instance_variable_defined?(inst.conf.instance_variable_name)
10
- instance_variable_get(inst.conf.instance_variable_name)
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.instance_variable_name, inst.call(self))
14
+ instance_variable_set(inst.conf.ivar_name, inst.exposed_value(self))
13
15
  end
14
16
  end
15
- helper_method(inst.conf.method_name)
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(name, value = nil, **args, &block)
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!(name, value = nil, **args, &block)
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 _decorate(**args)
31
- ::Xpose::Decorated.new(args).tap do |inst|
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
@@ -1,52 +1,54 @@
1
1
  module Xpose
2
2
  class Decorated
3
- attr_accessor :conf
3
+ attr_reader :conf
4
4
 
5
- def initialize(**args)
6
- @conf = ::Xpose::Configuration.build(args)
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 call(instance)
12
- v = instance.send(conf.method_name)
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
- decorator.to_s.singularize.capitalize.constantize.new(v)
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 StandardError.new('Unknown decorator')
20
+ raise UnknownDecoratorError.new(conf.decorator)
23
21
  end
24
22
  end
25
23
 
26
24
  private
27
25
 
28
- def decorate_self
29
- conf.decorated_name = conf.name
30
- conf.decorated_method_name = conf.method_name
31
- conf.decorated_instance_variable_name = conf.instance_variable_name
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?(klass)
38
- klass.new(v)
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 klass
45
- @klass ||= "#{conf.singularized_name.capitalize}Decorator".constantize
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
@@ -3,62 +3,61 @@ module Xpose
3
3
  attr_accessor :conf
4
4
 
5
5
  def initialize(**args)
6
- @conf = ::Xpose::Configuration.build(args)
7
- conf.name.tap do |name|
8
- raise MissingParameterError if name.nil?
9
- end
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 call(instance)
14
+ def decorated_value(instance)
13
15
  @instance = instance
14
- v = if conf.value.nil? && conf.infer_value
15
- (conf.method_name.to_s == conf.pluralized_name ? :collection : :record)
16
- else
17
- conf.value
18
- end
19
- reinterpret_value(v)
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 class_exists?(class_name)
27
- Module.const_get(class_name).is_a?(Class)
28
- rescue NameError
29
- return false
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 klass
33
- @klass ||= conf.singularized_name.capitalize.constantize
40
+ def infer_value
41
+ conf.value == :collection ? infer_collection : infer_record
34
42
  end
35
43
 
36
- def reinterpret_value(v)
37
- if v.respond_to?(:call)
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 infer_collection
49
- klass.send(conf.scope)
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
- source = if instance.respond_to?(conf.pluralized_name)
54
- ->{ instance.send(conf.pluralized_name) }
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
- source.call.new(params)
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
@@ -1,3 +1,3 @@
1
1
  module Xpose
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
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.5
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-02-14 00:00:00.000000000 Z
11
+ date: 2018-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler