dao 4.2.1 → 4.4.2
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.
- data/README +103 -63
- data/Rakefile +3 -3
- data/dao.gemspec +27 -16
- data/lib/dao.rb +17 -17
- data/lib/dao/active_record.rb +1 -0
- data/lib/dao/api.rb +2 -1
- data/lib/dao/api/{endpoints.rb → call.rb} +1 -0
- data/lib/dao/api/context.rb +2 -0
- data/lib/dao/api/dsl.rb +1 -0
- data/lib/dao/api/initializers.rb +1 -0
- data/lib/dao/api/modes.rb +1 -0
- data/lib/dao/api/routes.rb +1 -0
- data/lib/dao/blankslate.rb +1 -0
- data/lib/dao/conducer.rb +315 -274
- data/lib/dao/conducer/active_model.rb +98 -0
- data/lib/dao/conducer/attributes.rb +1 -0
- data/lib/dao/conducer/autocrud.rb +58 -0
- data/lib/dao/conducer/callback_support.rb +20 -0
- data/lib/dao/conducer/collection.rb +45 -0
- data/lib/dao/conducer/controller_support.rb +104 -0
- data/lib/dao/conducer/nav_support.rb +9 -0
- data/lib/dao/conducer/view_support.rb +16 -0
- data/lib/dao/data.rb +2 -1
- data/lib/dao/db.rb +2 -0
- data/lib/dao/endpoint.rb +1 -0
- data/lib/dao/engine.rb +1 -0
- data/lib/dao/errors.rb +109 -99
- data/lib/dao/exceptions.rb +1 -0
- data/lib/dao/extractor.rb +1 -0
- data/lib/dao/form.rb +175 -20
- data/lib/dao/instance_exec.rb +1 -0
- data/lib/dao/mode.rb +1 -0
- data/lib/dao/mongo_mapper.rb +1 -0
- data/lib/dao/name.rb +1 -0
- data/lib/dao/params.rb +2 -1
- data/lib/dao/path.rb +1 -0
- data/lib/dao/path_map.rb +24 -0
- data/lib/dao/rack.rb +1 -0
- data/lib/dao/rack/middleware.rb +1 -0
- data/lib/dao/rack/middleware/params_parser.rb +1 -0
- data/lib/dao/rails.rb +12 -32
- data/lib/dao/rails/lib/generators/dao/USAGE +2 -2
- data/lib/dao/rails/lib/generators/dao/dao_generator.rb +8 -27
- data/lib/dao/rails/lib/generators/dao/templates/api.rb +2 -1
- data/lib/dao/rails/lib/generators/dao/templates/api_controller.rb +22 -20
- data/lib/dao/rails/lib/generators/dao/templates/conducer.rb +49 -43
- data/lib/dao/rails/lib/generators/dao/templates/dao.css +26 -25
- data/lib/dao/rails/lib/generators/dao/templates/dao.js +3 -0
- data/lib/dao/rails/lib/generators/dao/templates/dao_helper.rb +58 -45
- data/lib/dao/result.rb +50 -1
- data/lib/dao/route.rb +1 -0
- data/lib/dao/slug.rb +12 -36
- data/lib/dao/status.rb +91 -7
- data/lib/dao/stdext.rb +1 -0
- data/lib/dao/support.rb +90 -80
- data/lib/dao/upload.rb +396 -0
- data/lib/dao/validations.rb +23 -5
- data/lib/dao/validations/callback.rb +5 -0
- data/lib/dao/validations/common.rb +100 -3
- data/lib/dao/validations/instance.rb +17 -0
- data/lib/dao/validations/validator.rb +192 -91
- data/test/active_model_conducer_lint_test.rb +1 -0
- data/test/api_test.rb +15 -0
- data/test/conducer_test.rb +608 -90
- data/test/data/han-solo.jpg +0 -0
- data/test/form_test.rb +1 -0
- data/test/helper.rb +1 -0
- data/test/leak.rb +1 -0
- data/test/support_test.rb +4 -1
- data/test/testing.rb +1 -0
- data/test/validations_test.rb +176 -30
- metadata +120 -131
- data/b.rb +0 -38
- data/lib/dao/conducer/crud.rb +0 -70
- data/lib/dao/current.rb +0 -66
- data/lib/dao/image_cache.rb +0 -193
- data/lib/dao/rails/lib/generators/dao/templates/conducer_controller.rb +0 -79
- data/test/db.yml +0 -9
data/lib/dao/result.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
module Dao
|
2
3
|
class Result < ::Map
|
3
4
|
def initialize(*args, &block)
|
@@ -12,8 +13,56 @@ module Dao
|
|
12
13
|
self.data = options[:data] || Data.new
|
13
14
|
end
|
14
15
|
|
16
|
+
=begin
|
17
|
+
%w(
|
18
|
+
path
|
19
|
+
route
|
20
|
+
mode
|
21
|
+
status
|
22
|
+
params
|
23
|
+
errors
|
24
|
+
data
|
25
|
+
).each do |attr|
|
26
|
+
|
27
|
+
module_eval <<-__, __FILE__, __LINE__
|
28
|
+
def #{ attr }(*value)
|
29
|
+
unless value.empty?
|
30
|
+
self["#{ attr }"] = value.first
|
31
|
+
end
|
32
|
+
self["#{ attr }"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def #{ attr }=(value)
|
36
|
+
self["#{ attr }"] = value
|
37
|
+
end
|
38
|
+
__
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
path
|
44
|
+
end
|
45
|
+
|
46
|
+
def attributes
|
47
|
+
params
|
48
|
+
end
|
49
|
+
=end
|
50
|
+
|
51
|
+
|
52
|
+
def form
|
53
|
+
@form ||= (
|
54
|
+
Form.new.tap do |f|
|
55
|
+
f.object = self
|
56
|
+
f.attributes = params
|
57
|
+
f.errors = errors
|
58
|
+
f.status = status
|
59
|
+
f.name = path
|
60
|
+
end
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
15
64
|
def inspect
|
16
|
-
|
65
|
+
Dao.json_for(self)
|
17
66
|
end
|
18
67
|
end
|
19
68
|
end
|
data/lib/dao/route.rb
CHANGED
data/lib/dao/slug.rb
CHANGED
@@ -1,40 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
begin
|
7
|
-
require 'unidecode'
|
8
|
-
rescue LoadError
|
9
|
-
end
|
10
|
-
|
11
|
-
class Slug < ::String
|
12
|
-
Slug::Version = '0.0.1'
|
13
|
-
|
14
|
-
def Slug.version
|
15
|
-
'0.0.1'
|
16
|
-
end
|
17
|
-
|
18
|
-
Join = '-'
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Dao
|
3
|
+
class Slug < ::String
|
4
|
+
Join = '-'
|
19
5
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
new(words.join(join).downcase)
|
29
|
-
end
|
30
|
-
|
31
|
-
unless defined?(Unidecoder)
|
32
|
-
def Slug.unidecode(string)
|
33
|
-
string
|
34
|
-
end
|
35
|
-
else
|
36
|
-
def Slug.unidecode(string)
|
37
|
-
Unidecoder.decode(string)
|
6
|
+
def Slug.for(*args)
|
7
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
8
|
+
join = options[:join]||options['join']||Join
|
9
|
+
string = args.flatten.compact.join(join)
|
10
|
+
words = string.to_s.scan(%r/\w+/)
|
11
|
+
words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
|
12
|
+
words.delete_if{|word| word.nil? or word.strip.empty?}
|
13
|
+
new(words.join(join).downcase)
|
38
14
|
end
|
39
15
|
end
|
40
16
|
end
|
data/lib/dao/status.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
module Dao
|
2
3
|
class Status < ::String
|
3
4
|
## http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
@@ -57,7 +58,84 @@ module Dao
|
|
57
58
|
504 => "Gateway Timeout",
|
58
59
|
505 => "HTTP Version Not Supported",
|
59
60
|
507 => "Insufficient Storage",
|
60
|
-
510 => "Not Extended"
|
61
|
+
510 => "Not Extended",
|
62
|
+
|
63
|
+
|
64
|
+
### ref: https://github.com/joho/7XX-rfc
|
65
|
+
|
66
|
+
#70X => "Inexcusable",
|
67
|
+
701 => "Meh",
|
68
|
+
702 => "Emacs",
|
69
|
+
|
70
|
+
#71X => "Novelty Implementations",
|
71
|
+
710 => "PHP",
|
72
|
+
711 => "Convenience Store",
|
73
|
+
719 => "I am not a teapot",
|
74
|
+
|
75
|
+
#72X => "Edge Cases",
|
76
|
+
720 => "Unpossible",
|
77
|
+
721 => "Known Unknowns",
|
78
|
+
722 => "Unknown Unknowns",
|
79
|
+
723 => "Tricky",
|
80
|
+
724 => "This line should be unreachable",
|
81
|
+
725 => "It works on my machine",
|
82
|
+
726 => "It's a feature, not a bug",
|
83
|
+
|
84
|
+
#73X => "Fucking",
|
85
|
+
731 => "Fucking Rubygems",
|
86
|
+
732 => "Fucking Unicode",
|
87
|
+
733 => "Fucking Deadlocks",
|
88
|
+
734 => "Fucking Deferreds",
|
89
|
+
735 => "Fucking IE",
|
90
|
+
736 => "Fucking Race Conditions",
|
91
|
+
737 => "FuckThreadsing",
|
92
|
+
738 => "Fucking Bundler",
|
93
|
+
739 => "Fucking Windows",
|
94
|
+
|
95
|
+
#74X => "Meme Driven",
|
96
|
+
741 => "Compiling",
|
97
|
+
742 => "A kitten dies",
|
98
|
+
743 => "I thought I knew regular expressions",
|
99
|
+
744 => "Y U NO write integration tests?",
|
100
|
+
745 => "I don't always test my code, but when I do I do it in production",
|
101
|
+
746 => "Missed Ballmer Peak",
|
102
|
+
747 => "Motherfucking Snakes on the Motherfucking Plane",
|
103
|
+
748 => "Confounded by Ponies",
|
104
|
+
749 => "Reserved for Chuck Norris",
|
105
|
+
|
106
|
+
#75X => "Syntax Errors",
|
107
|
+
750 => "Didn't bother to compile it",
|
108
|
+
753 => "Syntax Error",
|
109
|
+
|
110
|
+
#76X => "Substance-Affected Developer",
|
111
|
+
761 => "Hungover",
|
112
|
+
762 => "Stoned",
|
113
|
+
763 => "Under-Caffeinated",
|
114
|
+
764 => "Over-Caffeinated",
|
115
|
+
765 => "Railscamp",
|
116
|
+
766 => "Sober",
|
117
|
+
767 => "Drunk",
|
118
|
+
|
119
|
+
#77X => "Predictable Problems",
|
120
|
+
771 => "Cached for too long",
|
121
|
+
772 => "Not cached long enough",
|
122
|
+
773 => "Not cached at all",
|
123
|
+
774 => "Why was this cached?",
|
124
|
+
776 => "Error on the Exception",
|
125
|
+
777 => "Coincidence",
|
126
|
+
778 => "Off By One Error",
|
127
|
+
779 => "Off By Too Many To Count Error",
|
128
|
+
|
129
|
+
#78X => "Somebody Else's Problem",
|
130
|
+
781 => "Operations",
|
131
|
+
782 => "QA",
|
132
|
+
783 => "It was a customer request, honestly",
|
133
|
+
784 => "Management, obviously",
|
134
|
+
785 => "TPS Cover Sheet not attached",
|
135
|
+
|
136
|
+
#79X => "Internet crashed",
|
137
|
+
797 => "This is the last page of the Internet. Go back",
|
138
|
+
799 => "End of the world"
|
61
139
|
}
|
62
140
|
) unless defined?(Code2Message)
|
63
141
|
|
@@ -66,7 +144,8 @@ module Dao
|
|
66
144
|
200 => 'success',
|
67
145
|
300 => 'redirection',
|
68
146
|
400 => 'client_error',
|
69
|
-
500 => 'server_error'
|
147
|
+
500 => 'server_error',
|
148
|
+
700 => 'developer_error'
|
70
149
|
}) unless defined?(Groups)
|
71
150
|
|
72
151
|
# class methods
|
@@ -78,10 +157,14 @@ module Dao
|
|
78
157
|
|
79
158
|
def underscore(camel_cased_word)
|
80
159
|
camel_cased_word.to_s.gsub(/::/, '/').
|
160
|
+
gsub(/\s+/, '_').
|
81
161
|
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
82
162
|
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
83
|
-
|
163
|
+
gsub(/[^A-Za-z0-9_]/, '_').
|
84
164
|
tr("-", "_").
|
165
|
+
squeeze('_').
|
166
|
+
gsub(/^_+/, '').
|
167
|
+
gsub(/_+$/, '').
|
85
168
|
downcase
|
86
169
|
end
|
87
170
|
|
@@ -150,7 +233,7 @@ module Dao
|
|
150
233
|
|
151
234
|
Symbol2Code = (
|
152
235
|
Code2Message.inject(Hash.new) do |hash, (code, message)|
|
153
|
-
sym = Status.underscore(message.gsub(/\s+/, "")).to_sym
|
236
|
+
sym = Status.underscore(message.gsub(/\s+/, "_")).to_sym
|
154
237
|
hash.update(sym => code)
|
155
238
|
end
|
156
239
|
) unless defined?(Symbol2Code)
|
@@ -165,9 +248,10 @@ module Dao
|
|
165
248
|
|
166
249
|
# instance methods
|
167
250
|
#
|
168
|
-
|
169
|
-
|
170
|
-
|
251
|
+
attr_accessor :code
|
252
|
+
attr_accessor :message
|
253
|
+
attr_accessor :group
|
254
|
+
attr_accessor :source
|
171
255
|
|
172
256
|
def initialize(*args)
|
173
257
|
update(*args)
|
data/lib/dao/stdext.rb
CHANGED
data/lib/dao/support.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
module Dao
|
2
3
|
def map_for(*args, &block)
|
3
4
|
Map.for(*args, &block)
|
@@ -66,42 +67,29 @@ module Dao
|
|
66
67
|
Form.name_for(path, *keys)
|
67
68
|
end
|
68
69
|
|
69
|
-
def parse(*args, &block)
|
70
|
-
Params.process(*args, &block)
|
71
|
-
end
|
72
|
-
|
73
|
-
def normalize_parameters(params)
|
74
|
-
Params.normalize_parameters(params)
|
75
|
-
end
|
76
|
-
|
77
70
|
def current
|
78
|
-
|
79
|
-
Map.new(
|
80
|
-
:controller => nil
|
81
|
-
)
|
71
|
+
Current
|
82
72
|
end
|
83
73
|
|
84
74
|
def current_controller(*args)
|
85
|
-
|
86
|
-
|
75
|
+
Current.controller = args.first unless args.empty?
|
76
|
+
Current.controller
|
87
77
|
end
|
88
78
|
alias_method('controller', 'current_controller')
|
89
79
|
|
90
80
|
def current_controller=(controller)
|
91
|
-
|
81
|
+
Current.controller = controller
|
92
82
|
end
|
93
83
|
alias_method('controller=', 'current_controller=')
|
94
84
|
|
85
|
+
def mock_controller
|
86
|
+
Current.mock_controller
|
87
|
+
end
|
88
|
+
|
95
89
|
%w( request response session ).each do |attr|
|
96
90
|
module_eval <<-__, __FILE__, __LINE__
|
97
91
|
def current_#{ attr }
|
98
|
-
|
99
|
-
end
|
100
|
-
def current_#{ attr }=(value)
|
101
|
-
@current_#{ attr } = value
|
102
|
-
end
|
103
|
-
def #{ attr }
|
104
|
-
current_#{ attr }
|
92
|
+
current_controller.instance_eval{ #{ attr } }
|
105
93
|
end
|
106
94
|
__
|
107
95
|
end
|
@@ -109,7 +97,7 @@ module Dao
|
|
109
97
|
%w( current_user effective_user real_user ).each do |attr|
|
110
98
|
module_eval <<-__, __FILE__, __LINE__
|
111
99
|
def #{ attr }
|
112
|
-
|
100
|
+
current_controller.instance_eval{ #{ attr } }
|
113
101
|
end
|
114
102
|
__
|
115
103
|
end
|
@@ -122,58 +110,13 @@ module Dao
|
|
122
110
|
end
|
123
111
|
end
|
124
112
|
|
125
|
-
def key_for(*keys)
|
126
|
-
key = keys.flatten.join('.').strip
|
127
|
-
key.split(%r/\s*[,.:_-]\s*/).map{|key| key =~ %r/^\d+$/ ? Integer(key) : key}
|
128
|
-
end
|
129
|
-
|
130
|
-
def mock_controller
|
131
|
-
ensure_rails_application do
|
132
|
-
require 'action_dispatch/testing/test_request.rb'
|
133
|
-
require 'action_dispatch/testing/test_response.rb'
|
134
|
-
store = ActiveSupport::Cache::MemoryStore.new
|
135
|
-
controller =
|
136
|
-
begin
|
137
|
-
ApplicationController.new
|
138
|
-
rescue NameError
|
139
|
-
ActionController::Base.new
|
140
|
-
end
|
141
|
-
controller.perform_caching = true
|
142
|
-
controller.cache_store = store
|
143
|
-
request = ActionDispatch::TestRequest.new
|
144
|
-
response = ActionDispatch::TestResponse.new
|
145
|
-
controller.request = request
|
146
|
-
controller.response = response
|
147
|
-
controller.send(:initialize_template_class, response)
|
148
|
-
controller.send(:assign_shortcuts, request, response)
|
149
|
-
controller.send(:default_url_options).merge!(DefaultUrlOptions) if defined?(DefaultUrlOptions)
|
150
|
-
controller
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def ensure_rails_application(&block)
|
155
|
-
if Rails.application.nil?
|
156
|
-
mock = Class.new(Rails::Application)
|
157
|
-
Rails.application = mock.instance
|
158
|
-
begin
|
159
|
-
block.call()
|
160
|
-
ensure
|
161
|
-
Rails.application = nil
|
162
|
-
end
|
163
|
-
else
|
164
|
-
block.call()
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
113
|
def normalize_parameters(params)
|
169
114
|
dao = (params.delete('dao') || {}).merge(params.delete(:dao) || {})
|
170
115
|
|
171
|
-
unless dao.
|
116
|
+
unless dao.empty?
|
172
117
|
dao.each do |key, paths_and_values|
|
173
|
-
params[key] = nil
|
174
118
|
next if paths_and_values.blank?
|
175
|
-
|
176
|
-
map = Map.new
|
119
|
+
map = Map.for(params[key])
|
177
120
|
|
178
121
|
paths_and_values.each do |path, value|
|
179
122
|
keys = keys_for(path)
|
@@ -182,16 +125,25 @@ module Dao
|
|
182
125
|
|
183
126
|
params[key] = map
|
184
127
|
end
|
128
|
+
|
129
|
+
params['dao'] = dao
|
185
130
|
end
|
186
131
|
|
187
|
-
#params[:dao] = {:normalized => true}
|
188
132
|
params
|
189
133
|
end
|
190
134
|
|
191
|
-
def keys_for(keys)
|
192
|
-
keys.
|
135
|
+
def keys_for(*keys)
|
136
|
+
keys = keys.join('.').scan(/[^\,\.\s]+/iomx)
|
137
|
+
|
138
|
+
keys.map do |key|
|
139
|
+
digity, stringy, digits = %r/^(~)?(\d+)$/iomx.match(key).to_a
|
140
|
+
|
141
|
+
digity ? stringy ? String(digits) : Integer(digits) : key
|
142
|
+
end
|
193
143
|
end
|
194
144
|
|
145
|
+
alias_method(:key_for, :keys_for)
|
146
|
+
|
195
147
|
def render_json(object, options = {})
|
196
148
|
options = options.to_options!
|
197
149
|
controller = options[:controller] || Dao.current_controller
|
@@ -210,18 +162,76 @@ module Dao
|
|
210
162
|
end
|
211
163
|
end
|
212
164
|
|
213
|
-
def json_for(object)
|
165
|
+
def json_for(object, options = {})
|
214
166
|
object = object.as_json if object.respond_to?(:as_json)
|
215
167
|
|
168
|
+
options = options.empty? ? Map.for(options) : options
|
169
|
+
options[:pretty] = json_pretty? unless options.has_key?(:pretty)
|
170
|
+
|
216
171
|
begin
|
217
|
-
|
218
|
-
::JSON.generate(object)
|
219
|
-
else
|
220
|
-
::JSON.pretty_generate(object, :max_nesting => 0)
|
221
|
-
end
|
172
|
+
MultiJson.dump(object, options)
|
222
173
|
rescue Object => e
|
223
|
-
Rails.logger.error(e)
|
224
174
|
YAML.load( object.to_yaml ).to_json
|
225
175
|
end
|
226
176
|
end
|
177
|
+
|
178
|
+
def json_pretty?
|
179
|
+
@json_pretty ||= (defined?(Rails) ? !Rails.env.production? : true)
|
180
|
+
end
|
181
|
+
|
182
|
+
def call(object, method, *args, &block)
|
183
|
+
args = Dao.args_for_arity(args, object.method(method).arity)
|
184
|
+
object.send(method, *args, &block)
|
185
|
+
end
|
186
|
+
|
187
|
+
def args_for_arity(args, arity)
|
188
|
+
arity = Integer(arity.respond_to?(:arity) ? arity.arity : arity)
|
189
|
+
arity < 0 ? args.dup : args.slice(0, arity)
|
190
|
+
end
|
191
|
+
|
192
|
+
def tree_walk(node, *path, &block)
|
193
|
+
iterator = Array === node ? :each_with_index : :each
|
194
|
+
|
195
|
+
node.send(iterator) do |key, val|
|
196
|
+
key, val = val, key if Array === node
|
197
|
+
path.push(key)
|
198
|
+
begin
|
199
|
+
caught =
|
200
|
+
catch(:tree_walk) do
|
201
|
+
block.call(path, val)
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
next if caught==:next_sibling
|
205
|
+
|
206
|
+
case val
|
207
|
+
when Hash, Array
|
208
|
+
tree_walk(val, *path, &block)
|
209
|
+
end
|
210
|
+
ensure
|
211
|
+
path.pop
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
{
|
218
|
+
'ffi-uuid' => proc{|*args| FFI::UUID.generate_time.to_s},
|
219
|
+
'uuidtools' => proc{|*args| UUIDTools::UUID.timestamp_create.to_s},
|
220
|
+
'uuid' => proc{|*args| UUID.generate.to_s},
|
221
|
+
}.each do |lib, implementation|
|
222
|
+
begin
|
223
|
+
require(lib)
|
224
|
+
define_method(:uuid, &implementation)
|
225
|
+
break
|
226
|
+
rescue LoadError
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
end
|
230
|
+
abort 'no suitable uuid generation library detected' unless method_defined?(:uuid)
|
231
|
+
|
232
|
+
def ensure_interface!(object, *interface)
|
233
|
+
interface.flatten.compact.each do |method|
|
234
|
+
raise(NotImplementedError, "#{ object.class.name }##{ method }") unless object.respond_to?(method)
|
235
|
+
end
|
236
|
+
end
|
227
237
|
end
|