excom 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7f560f7894a0530703e6a5ecb6f472e61b4ae37b
4
- data.tar.gz: 764f807e1ccbb3f3daf38dfda5cca89b42a7b22c
3
+ metadata.gz: cf41e297a57b63652c79c3455846ceef0398f1ea
4
+ data.tar.gz: 27447ccbbcee3505e65f4a7d78908437f7197d6a
5
5
  SHA512:
6
- metadata.gz: bf2e04d3f7f91341768d54ce4033041ba3787529e45390beaf4035e2d72bfed4c5454e6fecab3c00cc6ff9f583cc8da0b19863a970a0ad941467ef464cc42670
7
- data.tar.gz: 06d5a6496d1a653ac920b51f9712af6a4321235a41624c18931c46170936b953a847ad3e014660df6356f89368ffd2dd470269478d59a6386cf35d20b4d47b27
6
+ metadata.gz: 3cbfb293d339f11984aab4b78f86ad1b93f70ba0721dbabcaecd4e175a8ec56bc8ffb583fcf88b2256cd85abc83f1e386c9ec5015acb4e6b16a5eff4437689d8
7
+ data.tar.gz: 994147da0552e012f35bed28e7c19933f06ea1a88e28cef013d8869a8a9d4cbf9bd1b7d3b59c86584ea9b6c62bf3a698bfd8c03f83d63cc6c3267e728ef93e8e
data/README.md CHANGED
@@ -186,36 +186,36 @@ class Posts::Archive < Excom::Command
186
186
  end
187
187
  ```
188
188
 
189
- - [`:sentry`](https://github.com/akuzko/excom/wiki/Plugins#sentry) - Allows you to provide Sentry classes
190
- for commands that use this plugin. Each Sentry class hosts logic responsible for allowing or denying
191
- corresponding command's execution or related checks. Much like [pundit](https://github.com/elabs/pundit)
192
- Policies, but more. Where pundit governs only authorization logic, Excom's Sentries can deny execution
193
- with any reason you find appropriate.
189
+ - [`:sentry`](https://github.com/akuzko/excom/wiki/Plugins#sentry) - Allows you to define sentry logic that
190
+ will allow or deny command's execution or other related checks. This logic can be defined inline in command
191
+ classes or in dedicated Sentry classes. Much like [pundit](https://github.com/elabs/pundit) Policies, but
192
+ more. Where pundit governs only authorization logic, Excom's Sentries can deny execution with any reason
193
+ you find appropriate.
194
194
 
195
195
  ```rb
196
196
  class Posts::Destroy < Excom::Command
197
197
  use :context
198
198
  use :sentry
199
+
199
200
  args :post
200
201
 
201
202
  def run
202
203
  post.destroy
203
204
  end
204
- end
205
205
 
206
- class Posts::DestroySentry < Excom::Sentry
207
- delegate :post, :context, to: :command
208
- deny_with :unauthorized
209
-
210
- def execute?
211
- # only author can destroy a post
212
- post.author_id == context[:current_user].id
213
- end
206
+ sentry delegate: [:context] do
207
+ deny_with :unauthorized
214
208
 
215
- deny_with :unprocessable_entity do
216
209
  def execute?
217
- # disallow to destroy posts that are older than 1 hour
218
- (post.created_at + 1.hour).past?
210
+ # only author can destroy a post
211
+ post.author_id == context[:current_user].id
212
+ end
213
+
214
+ deny_with :unprocessable_entity do
215
+ def execute?
216
+ # disallow to destroy posts that are older than 1 hour
217
+ (post.created_at + 1.hour).past?
218
+ end
219
219
  end
220
220
  end
221
221
  end
data/bin/console CHANGED
@@ -3,11 +3,33 @@
3
3
  require "bundler/setup"
4
4
  require "excom"
5
5
 
6
- class Command < Excom::Command
6
+ Excom::Sentry.deny_with :unauthorized
7
+
8
+ class Show < Excom::Command
7
9
  use :sentry, class: 'MySentry'
8
- use :assertions
9
10
  use :caching
10
11
 
12
+ args :foo
13
+
14
+ def run
15
+ {foo: foo}
16
+ end
17
+ end
18
+
19
+ class MySentry < Excom::Sentry
20
+ def execute?
21
+ foo != 5
22
+ end
23
+
24
+ def save?
25
+ sentry(:save).execute?
26
+ end
27
+ end
28
+
29
+ class Save < Excom::Command
30
+ use :sentry, delegate: [:threshold]
31
+ use :assertions
32
+
11
33
  args :foo
12
34
  opts :bar, :baz
13
35
 
@@ -21,18 +43,24 @@ class Command < Excom::Command
21
43
  result ok: foo * 2
22
44
  assert { foo > bar }
23
45
  end
24
- end
25
46
 
26
- class MySentry < Excom::Sentry
27
- deny_with :unauthorized
28
-
29
- def execute?
30
- command.foo != 5
47
+ def threshold
48
+ 0
31
49
  end
32
50
 
33
- deny_with :unprocessable_entity do
34
- def bar?
35
- command.bar && command.bar > 0
51
+ sentry do
52
+ def execute?
53
+ foo != 6
54
+ end
55
+
56
+ deny_with :unprocessable_entity do
57
+ def execute?
58
+ foo > 0
59
+ end
60
+
61
+ def bar?
62
+ bar && bar > threshold
63
+ end
36
64
  end
37
65
  end
38
66
  end
@@ -14,7 +14,16 @@ module Excom
14
14
  extension.used(self, **opts)
15
15
  end
16
16
 
17
+ plugins[name] = Reflection.new(extension, opts)
18
+
17
19
  extension
18
20
  end
21
+
22
+ def plugins
23
+ @plugins ||= {}
24
+ end
25
+ alias :extensions :plugins
26
+
27
+ Reflection = Struct.new(:extension, :options)
19
28
  end
20
29
  end
@@ -1,6 +1,23 @@
1
1
  module Excom
2
2
  module Plugins::Sentry
3
3
  class Sentinel
4
+ def self.inherited(sentry_class)
5
+ return unless self == Sentinel
6
+
7
+ sentry_class.denial_reason = denial_reason
8
+ sentry_class.const_set(:Delegations, Module.new)
9
+ sentry_class.send(:include, sentry_class::Delegations)
10
+ end
11
+
12
+ def self.command_class=(klass)
13
+ sentinels.each{ |s| s.command_class = klass }
14
+ @command_class = klass
15
+ end
16
+
17
+ def self.command_class
18
+ @command_class
19
+ end
20
+
4
21
  def self.deny_with(reason)
5
22
  return self.denial_reason = reason unless block_given?
6
23
 
@@ -35,6 +52,10 @@ module Excom
35
52
  @sentinels ||= []
36
53
  end
37
54
 
55
+ def self.delegations
56
+ const_get(:Delegations)
57
+ end
58
+
38
59
  attr_reader :command
39
60
 
40
61
  def initialize(command)
@@ -52,13 +73,7 @@ module Excom
52
73
  end
53
74
 
54
75
  def sentry(klass)
55
- unless Class === klass
56
- klass_name = self.class.name.sub(/[^:]+\Z/, ''.freeze) + "_#{klass}".gsub!(/(_([a-z]))/){ $2.upcase } + 'Sentry'.freeze
57
- klass = klass_name.respond_to?(:constantize) ?
58
- klass_name.constantize :
59
- klass_name.split('::'.freeze).reduce(Object){ |obj, name| obj.const_get(name) }
60
- end
61
-
76
+ klass = derive_sentry_class(klass) unless Class === klass
62
77
  klass.new(command)
63
78
  end
64
79
 
@@ -82,7 +97,47 @@ module Excom
82
97
  end
83
98
  end
84
99
 
85
- def method_missing(name, *)
100
+ private def derive_sentry_class(klass)
101
+ constantize(klass, '::Sentry'.freeze)
102
+ rescue NameError
103
+ constantize(klass, 'Sentry'.freeze)
104
+ end
105
+
106
+ private def constantize(klass, sentry_name)
107
+ module_prefix = (inline? ? self.class.command_class.name : self.class.name).sub(/[^:]+\Z/, ''.freeze)
108
+
109
+ klass_name = module_prefix + "_#{klass}".gsub!(/(_([a-z]))/){ $2.upcase } + sentry_name
110
+
111
+ klass_name.respond_to?(:constantize) ?
112
+ klass_name.constantize :
113
+ klass_name.split('::'.freeze).reduce(Object){ |obj, name| obj.const_get(name) }
114
+ end
115
+
116
+ private def inline?
117
+ self.class.command_class.const_defined?(:Sentry) && self.class.command_class::Sentry == self.class
118
+ end
119
+
120
+ private def define_delegations!
121
+ delegated_methods = self.class.command_class.arg_methods.instance_methods +
122
+ Array(self.class.command_class.plugins[:sentry].options[:delegate])
123
+
124
+ delegated_methods.each do |name|
125
+ self.class.delegations.send(:define_method, name) { command.public_send(name) }
126
+ end
127
+
128
+ self.class.instance_variable_set('@delegations_defined'.freeze, true)
129
+ end
130
+
131
+ private def delegations_defined?
132
+ self.class.instance_variable_get('@delegations_defined'.freeze)
133
+ end
134
+
135
+ def method_missing(name, *args)
136
+ unless delegations_defined?
137
+ define_delegations!
138
+ return send(name, *args) if respond_to?(name)
139
+ end
140
+
86
141
  if name.to_s.end_with?(??)
87
142
  sentinels[1..-1].each do |sentry|
88
143
  return sentry.public_send(name) if sentry.respond_to?(name)
@@ -32,6 +32,10 @@ module Excom
32
32
  @sentry ||= self.class.sentry_class.new(self)
33
33
  end
34
34
 
35
+ def sentry_hash
36
+ sentry.to_hash
37
+ end
38
+
35
39
  module ClassMethods
36
40
  attr_writer :_sentry_class
37
41
 
@@ -42,21 +46,38 @@ module Excom
42
46
 
43
47
  def sentry_class(klass = nil)
44
48
  return self._sentry_class = klass unless klass.nil?
49
+ return @sentry_class if defined? @sentry_class
45
50
 
46
- if _sentry_class.is_a?(String)
47
- return _sentry_class.constantize if _sentry_class.respond_to?(:constantize)
51
+ @sentry_class =
52
+ if _sentry_class.is_a?(String)
53
+ return _sentry_class.constantize if _sentry_class.respond_to?(:constantize)
48
54
 
49
- names = _sentry_class.split('::'.freeze)
50
- names.shift if names.first.empty?
51
- names.reduce(Object){ |obj, name| obj.const_get(name) }
52
- else
53
- _sentry_class
54
- end
55
+ names = _sentry_class.split('::'.freeze)
56
+ names.shift if names.first.empty?
57
+ names.reduce(Object){ |obj, name| obj.const_get(name) }
58
+ else
59
+ _sentry_class
60
+ end
61
+
62
+ @sentry_class.command_class = self
63
+ @sentry_class
55
64
  end
56
65
 
57
66
  def _sentry_class
58
67
  @_sentry_class ||= "#{name}Sentry"
59
68
  end
69
+
70
+ def sentry(delegate: [], &block)
71
+ (plugins[:sentry].options[:delegate] ||= []).concat(delegate).uniq!
72
+
73
+ if const_defined?(:Sentry)
74
+ const_get(:Sentry).class_eval(&block)
75
+ else
76
+ @_sentry_class = @sentry_class = Class.new(Sentry, &block)
77
+ @sentry_class.command_class = self
78
+ const_set(:Sentry, @_sentry_class)
79
+ end
80
+ end
60
81
  end
61
82
  end
62
83
  end
data/lib/excom/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Excom
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artem Kuzko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-01-14 00:00:00.000000000 Z
11
+ date: 2018-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler