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 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