rexport 0.5.4 → 1.0.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 +5 -5
- data/Rakefile +6 -34
- data/app/views/export_filters/_export_filter.html.erb +2 -2
- data/app/views/export_items/_export_item.html.erb +2 -2
- data/app/views/exports/_edit.html.erb +2 -2
- data/app/views/exports/_filters.html.erb +1 -1
- data/app/views/exports/_form.html.erb +6 -6
- data/app/views/exports/_rexport_model.html.erb +1 -1
- data/app/views/exports/_show.html.erb +6 -6
- data/app/views/exports/edit.html.erb +1 -1
- data/app/views/exports/index.html.erb +5 -5
- data/app/views/exports/new.html.erb +2 -2
- data/app/views/exports/show.html.erb +1 -1
- data/config/routes.rb +6 -6
- data/db/migrate/20091105182959_create_export_tables.rb +3 -3
- data/lib/rexport.rb +3 -1
- data/lib/rexport/data_field.rb +17 -0
- data/lib/rexport/data_fields.rb +25 -56
- data/lib/rexport/export_filter_methods.rb +46 -24
- data/lib/rexport/export_item_methods.rb +29 -34
- data/lib/rexport/export_methods.rb +161 -208
- data/lib/rexport/exports_controller_methods.rb +22 -28
- data/lib/rexport/rexport_model.rb +33 -0
- data/lib/rexport/tree_node.rb +13 -16
- data/lib/rexport/version.rb +1 -1
- data/test/factories.rb +58 -53
- data/test/test_helper.rb +29 -33
- data/test/unit/data_field_test.rb +11 -11
- data/test/unit/data_fields_test.rb +137 -95
- data/test/unit/export_filter_methods_test.rb +37 -0
- data/test/unit/export_item_methods_test.rb +21 -0
- data/test/unit/export_methods_test.rb +185 -59
- data/test/unit/rexport_model_test.rb +12 -12
- data/test/unit/tree_node_test.rb +20 -20
- metadata +14 -26
- data/test/jenkins.bash +0 -3
- data/test/log/test.log +0 -3891
@@ -1,45 +1,40 @@
|
|
1
1
|
module Rexport #:nodoc:
|
2
2
|
module ExportItemMethods
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
acts_as_list scope: :export
|
7
|
+
|
8
|
+
belongs_to :export
|
9
|
+
|
10
|
+
before_validation :replace_blank_name_with_rexport_field
|
11
|
+
validates_presence_of :name, :rexport_field
|
12
|
+
|
13
|
+
scope :ordered, -> { order :position }
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
module ClassMethods
|
17
17
|
def resort(export_item_ids)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
transaction do
|
19
|
+
export_item_ids.each_with_index do |id, index|
|
20
|
+
find(id.gsub(/[^0-9]/, '')).update_attribute(:position, index + 1)
|
21
|
+
end
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
rexport_field.split('.').values_at(-2..-1).map {|v| v.titleize}.join(' - ')
|
39
|
-
else
|
40
|
-
rexport_field.titleize
|
41
|
-
end
|
42
|
-
end
|
25
|
+
|
26
|
+
def attributes_for_copy
|
27
|
+
attributes.slice('position', 'name', 'rexport_field')
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def replace_blank_name_with_rexport_field
|
33
|
+
self.name = generate_name_from_rexport_field if name.blank?
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_name_from_rexport_field
|
37
|
+
rexport_field.split('.').last(2).map(&:titleize).join(' - ')
|
43
38
|
end
|
44
39
|
end
|
45
40
|
end
|
@@ -1,272 +1,225 @@
|
|
1
1
|
require 'csv'
|
2
2
|
|
3
3
|
module Rexport #:nodoc:
|
4
|
+
module ExportMethods
|
5
|
+
extend ActiveSupport::Concern
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_accessor :klass, :path
|
9
|
-
|
10
|
-
def initialize(klass, path = nil)
|
11
|
-
self.klass = klass
|
12
|
-
self.path = path.to_s unless path.blank?
|
13
|
-
end
|
14
|
-
|
15
|
-
def rexport_fields_array
|
16
|
-
klass.rexport_fields_array
|
17
|
-
end
|
18
|
-
|
19
|
-
def field_path(field_name)
|
20
|
-
[path, field_name].compact * '.'
|
21
|
-
end
|
22
|
-
|
23
|
-
def collection_from_association(association)
|
24
|
-
return klass.send("find_#{association}_for_rexport") if klass.respond_to?("find_#{association}_for_rexport")
|
25
|
-
klass.reflect_on_association(association.to_sym).klass.all
|
26
|
-
end
|
7
|
+
included do
|
8
|
+
has_many :export_items, dependent: :destroy
|
9
|
+
has_many :export_filters, dependent: :destroy
|
27
10
|
|
28
|
-
|
29
|
-
return field.method unless field.method.include?('.')
|
30
|
-
association = field.method.split('.').first
|
31
|
-
klass.reflect_on_association(association.to_sym).foreign_key
|
32
|
-
end
|
11
|
+
validates_presence_of :name, :model_class_name
|
33
12
|
|
34
|
-
|
35
|
-
klass.name
|
36
|
-
end
|
37
|
-
end
|
13
|
+
after_save :save_export_items
|
38
14
|
|
39
|
-
|
40
|
-
|
41
|
-
base.extend ClassMethods
|
42
|
-
base.class_eval do
|
43
|
-
include InstanceMethods
|
44
|
-
|
45
|
-
has_many :export_items, :dependent => :destroy
|
46
|
-
has_many :export_filters, :dependent => :destroy
|
47
|
-
validates_presence_of :name, :model_class_name
|
48
|
-
after_save :save_export_items
|
49
|
-
scope :alphabetical, -> { order :name }
|
50
|
-
scope :categorical, -> { order :model_class_name }
|
51
|
-
scope :by_model, ->(model_class_name) { where(model_class_name: model_class_name) }
|
52
|
-
end
|
15
|
+
scope :alphabetical, -> { order :name }
|
16
|
+
scope :categorical, -> { order :model_class_name }
|
53
17
|
end
|
54
18
|
|
55
19
|
module ClassMethods
|
56
20
|
def models
|
57
21
|
%w(override_this_method)
|
58
22
|
end
|
59
|
-
|
60
|
-
def enabled
|
61
|
-
find(:all).select {|export| export.enabled? }
|
62
|
-
end
|
63
23
|
end
|
64
24
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
25
|
+
def full_name
|
26
|
+
"#{model_class_name.pluralize} - #{name}"
|
27
|
+
end
|
69
28
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
29
|
+
# Returns a string with the export data
|
30
|
+
def to_s
|
31
|
+
String.new.tap do |result|
|
32
|
+
result << header * '|' << "\n"
|
33
|
+
records.each do |record|
|
34
|
+
result << record * '|' << "\n"
|
77
35
|
end
|
78
36
|
end
|
37
|
+
end
|
79
38
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
39
|
+
# Returns a csv string with the export data
|
40
|
+
def to_csv(objects = nil)
|
41
|
+
seed_records(objects) unless objects.nil?
|
42
|
+
CSV.generate do |csv|
|
43
|
+
csv << header
|
44
|
+
records.each do |record|
|
45
|
+
csv << record
|
88
46
|
end
|
89
47
|
end
|
48
|
+
end
|
90
49
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
50
|
+
# Returns an array with the header names from the associated export_items
|
51
|
+
def header
|
52
|
+
export_items.ordered.map(&:name)
|
53
|
+
end
|
95
54
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
55
|
+
# Returns the export model class
|
56
|
+
def export_model
|
57
|
+
model_class_name.constantize
|
58
|
+
end
|
100
59
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
60
|
+
# Returns an array of RexportModels including export_model and associated rexport capable models
|
61
|
+
def rexport_models
|
62
|
+
@rexport_models ||= get_rexport_models(export_model)
|
63
|
+
end
|
105
64
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
65
|
+
# Returns the records for the export
|
66
|
+
def records
|
67
|
+
@records ||= get_records
|
68
|
+
end
|
110
69
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
70
|
+
# Returns a limited number of records for the export
|
71
|
+
def sample_records
|
72
|
+
get_records(Rexport::SAMPLE_SIZE)
|
73
|
+
end
|
115
74
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
75
|
+
# Returns a class based on a path array
|
76
|
+
def get_klass_from_path(path, klass = export_model)
|
77
|
+
return klass unless (association_name = path.shift)
|
78
|
+
get_klass_from_path(path, klass.reflect_on_association(association_name.to_sym).klass)
|
79
|
+
end
|
121
80
|
|
122
|
-
|
123
|
-
|
124
|
-
|
81
|
+
def has_rexport_field?(rexport_field)
|
82
|
+
rexport_fields.include?(rexport_field)
|
83
|
+
end
|
125
84
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
end
|
85
|
+
def rexport_fields=(rexport_fields)
|
86
|
+
@rexport_fields = if rexport_fields.respond_to?(:keys)
|
87
|
+
@set_position = false
|
88
|
+
rexport_fields.keys.map(&:to_s)
|
89
|
+
else
|
90
|
+
@set_position = true
|
91
|
+
rexport_fields.map(&:to_s)
|
134
92
|
end
|
93
|
+
end
|
135
94
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
end
|
95
|
+
def export_filter_attributes=(attributes)
|
96
|
+
attributes.each do |field, value|
|
97
|
+
if value.blank?
|
98
|
+
filter = export_filters.find_by(filter_field: field)
|
99
|
+
filter.destroy if filter
|
100
|
+
elsif new_record?
|
101
|
+
export_filters.build(filter_field: field, value: value)
|
102
|
+
else
|
103
|
+
filter = export_filters.find_or_create_by(filter_field: field)
|
104
|
+
filter.update_attribute(:value, value)
|
147
105
|
end
|
148
106
|
end
|
107
|
+
end
|
149
108
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
end
|
154
|
-
|
155
|
-
def copy
|
156
|
-
self.class.create(attributes_for_copy) do |new_export|
|
157
|
-
export_items.ordered.each { |item| new_export.export_items.build(item.attributes_for_copy) }
|
158
|
-
export_filters.each { |filter| new_export.export_filters.build(filter.attributes_for_copy) }
|
159
|
-
end
|
160
|
-
end
|
109
|
+
def filter_value(filter_field)
|
110
|
+
export_filters.detect { |f| f.filter_field == filter_field }&.value
|
111
|
+
end
|
161
112
|
|
162
|
-
|
163
|
-
|
164
|
-
|
113
|
+
def copy
|
114
|
+
self.class.create(attributes_for_copy) do |new_export|
|
115
|
+
export_items.ordered.each { |item| new_export.export_items.build(item.attributes_for_copy) }
|
116
|
+
export_filters.each { |filter| new_export.export_filters.build(filter.attributes_for_copy) }
|
165
117
|
end
|
118
|
+
end
|
166
119
|
|
167
|
-
|
168
|
-
|
169
|
-
|
120
|
+
def modifiable?
|
121
|
+
# override to disable edit and destroy links for specific exports
|
122
|
+
true
|
123
|
+
end
|
170
124
|
|
171
|
-
|
172
|
-
protected
|
173
|
-
#########
|
125
|
+
private
|
174
126
|
|
175
|
-
|
176
|
-
|
177
|
-
|
127
|
+
def get_records(limit = nil)
|
128
|
+
get_export_values(export_model.where(build_conditions).includes(build_include).limit(limit))
|
129
|
+
end
|
178
130
|
|
179
|
-
|
180
|
-
|
181
|
-
|
131
|
+
def seed_records(objects)
|
132
|
+
@records = get_export_values(objects)
|
133
|
+
end
|
182
134
|
|
183
|
-
|
184
|
-
|
185
|
-
|
135
|
+
def get_export_values(objects)
|
136
|
+
objects.map { |object| object.export(rexport_methods) }
|
137
|
+
end
|
186
138
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
end
|
139
|
+
def get_rexport_models(rexport_model, result = [], path = nil)
|
140
|
+
return unless rexport_model.respond_to?(:rexport_fields)
|
141
|
+
result << RexportModel.new(rexport_model, path)
|
142
|
+
get_associations(rexport_model).each do |associated_model|
|
143
|
+
# prevent infinite loop by checking if this class is already in the result set
|
144
|
+
unless result.detect { |model| model.klass == associated_model.klass }
|
145
|
+
get_rexport_models(associated_model.klass, result, [path, associated_model.name].compact * '.')
|
195
146
|
end
|
196
|
-
return result
|
197
147
|
end
|
148
|
+
return result
|
149
|
+
end
|
198
150
|
|
199
|
-
|
200
|
-
|
201
|
-
|
151
|
+
def get_associations(rexport_model)
|
152
|
+
%i(belongs_to has_one).map do |type|
|
153
|
+
rexport_model.reflect_on_all_associations(type)
|
154
|
+
end.flatten.reject(&:polymorphic?)
|
155
|
+
end
|
202
156
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
end
|
208
|
-
root.to_include
|
157
|
+
def build_include
|
158
|
+
root = Rexport::TreeNode.new('root')
|
159
|
+
(rexport_methods + filter_fields).select {|m| m.include?('.')}.each do |method|
|
160
|
+
root.add_child(method.split('.').values_at(0..-2))
|
209
161
|
end
|
162
|
+
root.to_include
|
163
|
+
end
|
210
164
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
end
|
165
|
+
def build_conditions
|
166
|
+
Hash.new.tap do |conditions|
|
167
|
+
export_filters.each do |filter|
|
168
|
+
conditions[get_database_field(filter.filter_field)] = filter.value
|
216
169
|
end
|
217
170
|
end
|
171
|
+
end
|
218
172
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
173
|
+
def get_database_field(field)
|
174
|
+
path = field.split('.')
|
175
|
+
field = path.pop
|
176
|
+
"#{get_klass_from_path(path).table_name}.#{field}"
|
177
|
+
end
|
224
178
|
|
225
|
-
|
226
|
-
|
227
|
-
|
179
|
+
def rexport_methods
|
180
|
+
@rexport_methods ||= export_model.get_rexport_methods(ordered_rexport_fields)
|
181
|
+
end
|
228
182
|
|
229
|
-
|
230
|
-
|
231
|
-
|
183
|
+
def rexport_fields
|
184
|
+
@rexport_fields ||= export_items.map(&:rexport_field)
|
185
|
+
end
|
232
186
|
|
233
|
-
|
234
|
-
|
235
|
-
|
187
|
+
def ordered_rexport_fields
|
188
|
+
export_items.ordered.map(&:rexport_field)
|
189
|
+
end
|
236
190
|
|
237
|
-
|
238
|
-
|
239
|
-
|
191
|
+
def filter_fields
|
192
|
+
export_filters.map(&:filter_field)
|
193
|
+
end
|
240
194
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
end
|
195
|
+
def save_export_items
|
196
|
+
export_items.each do |export_item|
|
197
|
+
unless rexport_fields.include?(export_item.rexport_field)
|
198
|
+
export_item.destroy
|
246
199
|
end
|
200
|
+
end
|
247
201
|
|
248
|
-
|
202
|
+
position = 0
|
203
|
+
rexport_fields.each do |rexport_field|
|
204
|
+
position += 1
|
205
|
+
export_item = export_items.detect { |i| i.rexport_field == rexport_field } || export_items.create(rexport_field: rexport_field)
|
206
|
+
export_item.update_attribute(:position, position) if set_position
|
207
|
+
end
|
249
208
|
|
250
|
-
|
251
|
-
|
252
|
-
position += 1
|
253
|
-
export_item = export_items.detect {|i| i.rexport_field == rexport_field } || export_items.create(:rexport_field => rexport_field)
|
254
|
-
export_item.update_attribute(:position, position) if @set_position && export_item.position != position
|
255
|
-
end
|
209
|
+
return true
|
210
|
+
end
|
256
211
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
end
|
261
|
-
|
262
|
-
def attributes_for_copy
|
263
|
-
attributes.slice('model_class_name', 'description').merge(name: find_unique_name(self.name))
|
264
|
-
end
|
212
|
+
def attributes_for_copy
|
213
|
+
attributes.slice('model_class_name', 'description').merge(name: find_unique_name(name))
|
214
|
+
end
|
265
215
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
216
|
+
def find_unique_name(original_name, suffix = 0)
|
217
|
+
new_name = suffix == 0 ? "#{original_name} Copy" : "#{original_name} Copy [#{suffix}]"
|
218
|
+
self.class.find_by(name: new_name) ? find_unique_name(original_name, suffix += 1) : new_name
|
219
|
+
end
|
220
|
+
|
221
|
+
def set_position
|
222
|
+
@set_position ||= false
|
270
223
|
end
|
271
224
|
end
|
272
225
|
end
|