datashift 0.10.1 → 0.10.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/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?
|