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