datashift 0.10.1 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/datashift.gemspec +13 -6
- data/lib/datashift.rb +2 -20
- data/lib/datashift/exceptions.rb +2 -0
- data/lib/datashift/method_detail.rb +15 -29
- data/lib/datashift/method_dictionary.rb +36 -21
- data/lib/datashift/method_mapper.rb +56 -16
- data/lib/datashift/populator.rb +23 -0
- data/lib/datashift/querying.rb +86 -0
- data/lib/generators/csv_generator.rb +1 -4
- data/lib/generators/excel_generator.rb +28 -11
- data/lib/generators/generator_base.rb +12 -0
- data/lib/loaders/csv_loader.rb +9 -3
- data/lib/loaders/excel_loader.rb +14 -6
- data/lib/loaders/loader_base.rb +38 -125
- data/lib/loaders/paperclip/attachment_loader.rb +130 -62
- data/lib/loaders/paperclip/datashift_paperclip.rb +46 -12
- data/lib/loaders/paperclip/image_loading.rb +25 -41
- data/lib/thor/generate.thor +16 -6
- data/lib/thor/paperclip.thor +25 -5
- data/spec/Gemfile +3 -2
- data/spec/MissingAttachmentRecords/DEMO_001_ror_bag.jpeg +0 -0
- data/spec/{fixtures/images/DEMO_002_Powerstation.jpg → MissingAttachmentRecords/DEMO_002_Powerstation.jpeg} +0 -0
- data/spec/MissingAttachmentRecords/DEMO_002_Powerstation.jpg +0 -0
- data/spec/MissingAttachmentRecords/DEMO_003_ror_mug.jpeg +0 -0
- data/spec/MissingAttachmentRecords/DEMO_004_ror_ringer.jpeg +0 -0
- data/spec/excel_generator_spec.rb +28 -0
- data/spec/excel_loader_spec.rb +12 -17
- data/spec/fixtures/config/database.yml +1 -1
- data/spec/fixtures/db/datashift_test_models_db.sqlite +0 -0
- data/spec/fixtures/db/migrate/20121009161700_add_digitals.rb +24 -0
- data/spec/fixtures/images/DEMO_002_Powerstation.jpeg +0 -0
- data/spec/fixtures/models/digital.rb +14 -0
- data/spec/fixtures/models/owner.rb +5 -3
- data/spec/fixtures/test_model_defs.rb +4 -62
- data/spec/loader_spec.rb +42 -50
- data/spec/method_dictionary_spec.rb +3 -10
- data/spec/method_mapper_spec.rb +79 -20
- data/spec/paperclip_loader_spec.rb +95 -0
- data/spec/spec_helper.rb +44 -8
- metadata +236 -224
- data/lib/helpers/rake_utils.rb +0 -42
- data/spec/fixtures/models/test_model_defs.rb +0 -67
data/Rakefile
CHANGED
@@ -22,7 +22,12 @@ require 'rubygems'
|
|
22
22
|
|
23
23
|
require 'rake'
|
24
24
|
|
25
|
-
|
25
|
+
lib = File.expand_path('../lib/', __FILE__)
|
26
|
+
|
27
|
+
$:.unshift '.'
|
28
|
+
$:.unshift lib unless $:.include?(lib)
|
29
|
+
|
30
|
+
require 'datashift'
|
26
31
|
|
27
32
|
require 'jeweler'
|
28
33
|
Jeweler::Tasks.new do |gem|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.10.
|
1
|
+
0.10.2
|
data/datashift.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "datashift"
|
8
|
-
s.version = "0.10.
|
8
|
+
s.version = "0.10.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Thomas Statter"]
|
12
|
-
s.date = "2012-
|
12
|
+
s.date = "2012-10-21"
|
13
13
|
s.description = "Comprehensive tools to import/export between Excel/CSV and ActiveRecord databases, Rails apps, and any Ruby project."
|
14
14
|
s.email = "rubygems@autotelik.co.uk"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -46,6 +46,7 @@ Gem::Specification.new do |s|
|
|
46
46
|
"lib/datashift/method_mapper.rb",
|
47
47
|
"lib/datashift/model_mapper.rb",
|
48
48
|
"lib/datashift/populator.rb",
|
49
|
+
"lib/datashift/querying.rb",
|
49
50
|
"lib/exporters/csv_exporter.rb",
|
50
51
|
"lib/exporters/excel_exporter.rb",
|
51
52
|
"lib/exporters/exporter_base.rb",
|
@@ -54,7 +55,6 @@ Gem::Specification.new do |s|
|
|
54
55
|
"lib/generators/generator_base.rb",
|
55
56
|
"lib/guards.rb",
|
56
57
|
"lib/helpers/core_ext/to_b.rb",
|
57
|
-
"lib/helpers/rake_utils.rb",
|
58
58
|
"lib/java/poi-3.7/._poi-3.7-20101029.jar5645100390082102460.tmp",
|
59
59
|
"lib/java/poi-3.7/LICENSE",
|
60
60
|
"lib/java/poi-3.7/NOTICE",
|
@@ -82,6 +82,11 @@ Gem::Specification.new do |s|
|
|
82
82
|
"lib/thor/paperclip.thor",
|
83
83
|
"lib/thor/tools.thor",
|
84
84
|
"spec/Gemfile",
|
85
|
+
"spec/MissingAttachmentRecords/DEMO_001_ror_bag.jpeg",
|
86
|
+
"spec/MissingAttachmentRecords/DEMO_002_Powerstation.jpeg",
|
87
|
+
"spec/MissingAttachmentRecords/DEMO_002_Powerstation.jpg",
|
88
|
+
"spec/MissingAttachmentRecords/DEMO_003_ror_mug.jpeg",
|
89
|
+
"spec/MissingAttachmentRecords/DEMO_004_ror_ringer.jpeg",
|
85
90
|
"spec/csv_exporter_spec.rb",
|
86
91
|
"spec/csv_loader_spec.rb",
|
87
92
|
"spec/datashift_spec.rb",
|
@@ -100,19 +105,20 @@ Gem::Specification.new do |s|
|
|
100
105
|
"spec/fixtures/config/database.yml",
|
101
106
|
"spec/fixtures/db/datashift_test_models_db.sqlite",
|
102
107
|
"spec/fixtures/db/migrate/20110803201325_create_test_bed.rb",
|
108
|
+
"spec/fixtures/db/migrate/20121009161700_add_digitals.rb",
|
103
109
|
"spec/fixtures/images/DEMO_001_ror_bag.jpeg",
|
104
|
-
"spec/fixtures/images/DEMO_002_Powerstation.
|
110
|
+
"spec/fixtures/images/DEMO_002_Powerstation.jpeg",
|
105
111
|
"spec/fixtures/images/DEMO_003_ror_mug.jpeg",
|
106
112
|
"spec/fixtures/images/DEMO_004_ror_ringer.jpeg",
|
107
113
|
"spec/fixtures/load_datashift.thor",
|
108
114
|
"spec/fixtures/models/category.rb",
|
115
|
+
"spec/fixtures/models/digital.rb",
|
109
116
|
"spec/fixtures/models/empty.rb",
|
110
117
|
"spec/fixtures/models/loader_release.rb",
|
111
118
|
"spec/fixtures/models/long_and_complex_table_linked_to_version.rb",
|
112
119
|
"spec/fixtures/models/milestone.rb",
|
113
120
|
"spec/fixtures/models/owner.rb",
|
114
121
|
"spec/fixtures/models/project.rb",
|
115
|
-
"spec/fixtures/models/test_model_defs.rb",
|
116
122
|
"spec/fixtures/models/version.rb",
|
117
123
|
"spec/fixtures/simple_export_spec.xls",
|
118
124
|
"spec/fixtures/simple_template_spec.xls",
|
@@ -120,6 +126,7 @@ Gem::Specification.new do |s|
|
|
120
126
|
"spec/loader_spec.rb",
|
121
127
|
"spec/method_dictionary_spec.rb",
|
122
128
|
"spec/method_mapper_spec.rb",
|
129
|
+
"spec/paperclip_loader_spec.rb",
|
123
130
|
"spec/rails_sandbox/.gitignore",
|
124
131
|
"spec/rails_sandbox/Gemfile",
|
125
132
|
"spec/rails_sandbox/README.rdoc",
|
@@ -190,7 +197,7 @@ Gem::Specification.new do |s|
|
|
190
197
|
s.homepage = "http://github.com/autotelik/datashift"
|
191
198
|
s.licenses = ["MIT"]
|
192
199
|
s.require_paths = ["lib"]
|
193
|
-
s.rubygems_version = "1.8.
|
200
|
+
s.rubygems_version = "1.8.24"
|
194
201
|
s.summary = "Shift data betwen Excel/CSV and any Ruby app"
|
195
202
|
|
196
203
|
if s.respond_to? :specification_version then
|
data/lib/datashift.rb
CHANGED
@@ -33,27 +33,9 @@
|
|
33
33
|
# DataShift::load_commands
|
34
34
|
#
|
35
35
|
require 'rbconfig'
|
36
|
-
|
37
|
-
module DataShift
|
38
|
-
|
39
|
-
module Guards
|
40
|
-
|
41
|
-
def self.jruby?
|
42
|
-
return RUBY_PLATFORM == "java"
|
43
|
-
end
|
44
|
-
def self.mac?
|
45
|
-
RbConfig::CONFIG['target_os'] =~ /darwin/i
|
46
|
-
end
|
36
|
+
require 'guards'
|
47
37
|
|
48
|
-
|
49
|
-
RbConfig::CONFIG['target_os'] =~ /linux/i
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.windows?
|
53
|
-
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
38
|
+
module DataShift
|
57
39
|
|
58
40
|
if(Guards::jruby?)
|
59
41
|
require 'java'
|
data/lib/datashift/exceptions.rb
CHANGED
@@ -12,6 +12,7 @@
|
|
12
12
|
#
|
13
13
|
require 'to_b'
|
14
14
|
require 'logging'
|
15
|
+
require 'populator'
|
15
16
|
require 'set'
|
16
17
|
|
17
18
|
module DataShift
|
@@ -20,6 +21,9 @@ module DataShift
|
|
20
21
|
|
21
22
|
include DataShift::Logging
|
22
23
|
|
24
|
+
include DataShift::Populator
|
25
|
+
extend DataShift::Populator
|
26
|
+
|
23
27
|
def self.supported_types_enum
|
24
28
|
@type_enum ||= Set[:assignment, :belongs_to, :has_one, :has_many]
|
25
29
|
@type_enum
|
@@ -35,13 +39,17 @@ module DataShift
|
|
35
39
|
@@insistent_find_by_list ||= [:name, :title, :id]
|
36
40
|
|
37
41
|
# Name is the raw, client supplied name
|
38
|
-
|
42
|
+
attr_accessor :name
|
43
|
+
attr_accessor :column_index
|
44
|
+
|
45
|
+
# The rel col type from the DB
|
46
|
+
attr_reader :col_type, :current_value
|
39
47
|
|
40
48
|
attr_reader :operator, :operator_type
|
41
49
|
|
42
50
|
# TODO make it a list/primary keys
|
43
51
|
attr_accessor :find_by_operator, :find_by_value
|
44
|
-
|
52
|
+
|
45
53
|
# Store the raw (client supplied) name against the active record klass(model).
|
46
54
|
# Operator is the associated method call on klass,
|
47
55
|
# so client name maybe Price but true operator is price
|
@@ -68,6 +76,8 @@ module DataShift
|
|
68
76
|
else
|
69
77
|
@col_type = col_types[operator]
|
70
78
|
end
|
79
|
+
|
80
|
+
@column_index = -1
|
71
81
|
end
|
72
82
|
|
73
83
|
|
@@ -163,12 +173,6 @@ module DataShift
|
|
163
173
|
"#{@name} => #{operator}"
|
164
174
|
end
|
165
175
|
|
166
|
-
|
167
|
-
def self.insistent_method_list
|
168
|
-
@insistent_method_list ||= [:to_s, :to_i, :to_f, :to_b]
|
169
|
-
@insistent_method_list
|
170
|
-
end
|
171
|
-
|
172
176
|
private
|
173
177
|
|
174
178
|
# Attempt to find the associated object via id, name, title ....
|
@@ -188,7 +192,7 @@ module DataShift
|
|
188
192
|
end
|
189
193
|
rescue => e
|
190
194
|
puts "ERROR: #{e.inspect}"
|
191
|
-
if(x ==
|
195
|
+
if(x == Populator::insistent_method_list.last)
|
192
196
|
raise "I'm sorry I have failed to assign [#{value}] to #{@assignment}" unless value.nil?
|
193
197
|
end
|
194
198
|
end
|
@@ -211,7 +215,7 @@ module DataShift
|
|
211
215
|
end
|
212
216
|
rescue => e
|
213
217
|
puts "ERROR: #{e.inspect}"
|
214
|
-
if(x ==
|
218
|
+
if(x == Populator::insistent_method_list.last)
|
215
219
|
raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil?
|
216
220
|
end
|
217
221
|
end
|
@@ -220,25 +224,7 @@ module DataShift
|
|
220
224
|
end
|
221
225
|
|
222
226
|
def insistent_assignment( record, value )
|
223
|
-
|
224
|
-
op = operator + '='
|
225
|
-
|
226
|
-
begin
|
227
|
-
record.send(op, value)
|
228
|
-
rescue => e
|
229
|
-
MethodDetail::insistent_method_list.each do |f|
|
230
|
-
begin
|
231
|
-
record.send(op, value.send( f) )
|
232
|
-
break
|
233
|
-
rescue => e
|
234
|
-
#puts "DEBUG: insistent_assignment: #{e.inspect}"
|
235
|
-
if f == MethodDetail::insistent_method_list.last
|
236
|
-
puts "I'm sorry I have failed to assign [#{value}] to #{operator}"
|
237
|
-
raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil?
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
227
|
+
Populator::insistent_assignment( record, value, operator)
|
242
228
|
end
|
243
229
|
|
244
230
|
private
|
@@ -16,7 +16,6 @@ module DataShift
|
|
16
16
|
def initialize
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
19
|
# Has the dictionary been populated for klass
|
21
20
|
def self.for?(klass)
|
22
21
|
return !(has_many[klass] || belongs_to[klass] || has_one[klass] || assignments[klass]).nil?
|
@@ -122,22 +121,12 @@ module DataShift
|
|
122
121
|
method_details_mgrs[klass] = method_details_mgr
|
123
122
|
|
124
123
|
end
|
125
|
-
|
126
|
-
#
|
127
|
-
#
|
128
|
-
|
129
|
-
# If not nil, returned method can be used directly in for example klass.new.send( call, .... )
|
130
|
-
#
|
131
|
-
def self.find_method_detail( klass, external_name )
|
132
|
-
|
133
|
-
method_details_mgr = get_method_details_mgr( klass )
|
134
|
-
|
135
|
-
# md_mgr.all_available_operators.each { |l| puts "DEBUG: Mapped Method : #{l.inspect}" }
|
136
|
-
|
124
|
+
|
125
|
+
# TODO - check out regexp to do this work better plus Inflections ??
|
126
|
+
# Want to be able to handle any of ["Count On hand", 'count_on_hand', "Count OnHand", "COUNT ONHand" etc]
|
127
|
+
def self.substitutions(external_name)
|
137
128
|
name = external_name.to_s
|
138
|
-
|
139
|
-
# TODO - check out regexp to do this work better plus Inflections ??
|
140
|
-
# Want to be able to handle any of ["Count On hand", 'count_on_hand', "Count OnHand", "COUNT ONHand" etc]
|
129
|
+
|
141
130
|
[
|
142
131
|
name,
|
143
132
|
name.tableize,
|
@@ -146,20 +135,46 @@ module DataShift
|
|
146
135
|
name.gsub(/(\s+)/, '_').downcase,
|
147
136
|
name.gsub(' ', ''),
|
148
137
|
name.gsub(' ', '').downcase,
|
149
|
-
name.gsub(' ', '_').underscore
|
150
|
-
|
151
|
-
|
138
|
+
name.gsub(' ', '_').underscore
|
139
|
+
]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Find the proper format of name, appropriate call + column type for a given name.
|
143
|
+
# e.g Given users entry in spread sheet check for pluralization, missing underscores etc
|
144
|
+
#
|
145
|
+
# If not nil, returned method can be used directly in for example klass.new.send( call, .... )
|
146
|
+
#
|
147
|
+
def self.find_method_detail( klass, external_name )
|
148
|
+
|
149
|
+
method_details_mgr = get_method_details_mgr( klass )
|
150
|
+
|
151
|
+
# md_mgr.all_available_operators.each { |l| puts "DEBUG: Mapped Method : #{l.inspect}" }
|
152
|
+
substitutions(external_name).each do |n|
|
152
153
|
|
154
|
+
# Try each association type, returning first that contains matching operator with name n
|
153
155
|
MethodDetail::supported_types_enum.each do |t|
|
154
156
|
method_detail = method_details_mgr.find(n, t)
|
155
157
|
return method_detail.clone if(method_detail)
|
156
|
-
end
|
157
|
-
|
158
|
+
end
|
158
159
|
end
|
159
160
|
|
160
161
|
nil
|
161
162
|
end
|
163
|
+
|
164
|
+
# Assignments can contain things like delegated methods, this returns a matching
|
165
|
+
# method details only when a true database column
|
166
|
+
def self.find_method_detail_if_column( klass, external_name )
|
162
167
|
|
168
|
+
method_details_mgr = get_method_details_mgr( klass )
|
169
|
+
|
170
|
+
substitutions(external_name).each do |n|
|
171
|
+
method_detail = method_details_mgr.find(n, :assignment)
|
172
|
+
return method_detail if(method_detail && method_detail.col_type)
|
173
|
+
end
|
174
|
+
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
163
178
|
def self.clear
|
164
179
|
belongs_to.clear
|
165
180
|
has_many.clear
|
@@ -24,7 +24,6 @@ module DataShift
|
|
24
24
|
|
25
25
|
include DataShift::Logging
|
26
26
|
|
27
|
-
attr_accessor :header_row, :headers
|
28
27
|
attr_accessor :method_details, :missing_methods
|
29
28
|
|
30
29
|
|
@@ -45,68 +44,109 @@ module DataShift
|
|
45
44
|
|
46
45
|
def initialize
|
47
46
|
@method_details = []
|
48
|
-
@headers = []
|
49
47
|
end
|
50
48
|
|
51
49
|
# Build complete picture of the methods whose names listed in columns
|
52
50
|
# Handles method names as defined by a user, from spreadsheets or file headers where the names
|
53
51
|
# specified may not be exactly as required e.g handles capitalisation, white space, _ etc
|
54
|
-
#
|
52
|
+
#
|
53
|
+
# The header can also contain the fields to use in lookups, separated with MethodMapper::column_delim
|
54
|
+
#
|
55
|
+
# product:name or project:title or user:email
|
56
|
+
#
|
57
|
+
# Returns: Array of matching method_details, including nils for non matched items
|
58
|
+
#
|
59
|
+
# N.B Columns that could not be mapped are left in the array as NIL
|
60
|
+
#
|
61
|
+
# This is to support clients that need to map via the index on @method_details
|
62
|
+
#
|
63
|
+
# Other callers can simply call compact on the results if the index not important.
|
64
|
+
#
|
65
|
+
# The Methoddetails instance will contain a pointer to the column index from which it was mapped.
|
66
|
+
#
|
67
|
+
#
|
68
|
+
# Options:
|
69
|
+
#
|
70
|
+
# [:force_inclusion] : List of columns that do not map to any operator but should be includeed in processing.
|
71
|
+
#
|
72
|
+
# This provides the opportunity for loaders to provide specific methods to handle these fields
|
73
|
+
# when no direct operator is available on the modle or it's associations
|
74
|
+
#
|
75
|
+
# [:include_all] : Include all headers in processing - takes precedence of :force_inclusion
|
55
76
|
#
|
56
|
-
def
|
77
|
+
def map_inbound_headers_to_methods( klass, columns, options = {} )
|
78
|
+
|
79
|
+
# If klass not in MethodDictionary yet, add to dictionary all possible operators on klass
|
80
|
+
# which can be used to map headers and populate an object of type klass
|
81
|
+
unless(MethodDictionary::for?(klass))
|
82
|
+
DataShift::MethodDictionary.find_operators(klass)
|
83
|
+
|
84
|
+
DataShift::MethodDictionary.build_method_details(klass)
|
85
|
+
end
|
57
86
|
|
58
87
|
forced = [*options[:force_inclusion]].compact
|
59
88
|
forced.collect! { |f| f.downcase }
|
60
89
|
|
61
90
|
@method_details, @missing_methods = [], []
|
62
91
|
|
63
|
-
columns.
|
64
|
-
|
92
|
+
columns.each_with_index do |col_data, col_index|
|
93
|
+
|
94
|
+
raw_col_data = col_data.to_s
|
95
|
+
|
96
|
+
if(raw_col_data.nil? or raw_col_data.empty?)
|
65
97
|
logger.warn("Column list contains empty or null columns")
|
66
98
|
@method_details << nil
|
67
99
|
next
|
68
100
|
end
|
69
101
|
|
70
|
-
|
102
|
+
raw_col_name, lookup = raw_col_data.split(MethodMapper::column_delim)
|
71
103
|
|
72
|
-
md = MethodDictionary::find_method_detail( klass,
|
104
|
+
md = MethodDictionary::find_method_detail( klass, raw_col_name )
|
73
105
|
|
74
106
|
# TODO be nice if we could cheeck that the assoc on klass responds to the specified
|
75
107
|
# lookup key now (nice n early)
|
76
108
|
# active_record_helper = "find_by_#{lookup}"
|
77
|
-
if(md.nil? && forced.include?(
|
78
|
-
md = MethodDictionary::add(klass,
|
109
|
+
if(md.nil? && options[:include_all] || forced.include?(raw_col_name.downcase))
|
110
|
+
md = MethodDictionary::add(klass, raw_col_name)
|
79
111
|
end
|
80
112
|
|
81
113
|
if(md)
|
82
114
|
|
115
|
+
md.name = raw_col_name
|
116
|
+
md.column_index = col_index
|
117
|
+
|
83
118
|
if(lookup)
|
84
119
|
find_by, find_value = lookup.split(MethodMapper::column_delim)
|
85
120
|
md.find_by_value = find_value
|
86
121
|
md.find_by_operator = find_by # TODO and klass.x.respond_to?(active_record_helper))
|
87
122
|
#puts "DEBUG: Method Detail #{md.name};#{md.operator} : find_by_operator #{md.find_by_operator}"
|
88
123
|
end
|
89
|
-
|
90
|
-
@method_details << md
|
91
124
|
else
|
92
|
-
|
125
|
+
# TODO populate unmapped with a real MethodDetail that is 'null' and create is_nil
|
126
|
+
@missing_methods << raw_col_name
|
93
127
|
end
|
94
128
|
|
129
|
+
@method_details << md
|
130
|
+
|
95
131
|
end
|
96
|
-
|
132
|
+
|
97
133
|
@method_details
|
98
134
|
end
|
99
135
|
|
136
|
+
|
137
|
+
# TODO populate unmapped with a real MethodDetail that is 'null' and create is_nil
|
138
|
+
#
|
100
139
|
# The raw client supplied names
|
101
140
|
def method_names()
|
102
|
-
@method_details.collect( &:name )
|
141
|
+
@method_details.compact.collect( &:name )
|
103
142
|
end
|
104
143
|
|
105
144
|
# The true operator names discovered from model
|
106
145
|
def operator_names()
|
107
|
-
@method_details.collect( &:operator )
|
146
|
+
@method_details.compact.collect( &:operator )
|
108
147
|
end
|
109
148
|
|
149
|
+
|
110
150
|
# Returns true if discovered methods contain every operator in mandatory_list
|
111
151
|
def contains_mandatory?( mandatory_list )
|
112
152
|
[ [*mandatory_list] - operator_names].flatten.empty?
|