rexport 0.5.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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