rexport 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +17 -0
  4. data/Rakefile +38 -0
  5. data/app/helpers/exports_helper.rb +25 -0
  6. data/app/views/export_filters/_export_filter.html.erb +7 -0
  7. data/app/views/export_items/_export_item.html.erb +8 -0
  8. data/app/views/export_items/edit.html.erb +15 -0
  9. data/app/views/exports/_edit.html.erb +7 -0
  10. data/app/views/exports/_filters.html.erb +9 -0
  11. data/app/views/exports/_form.html.erb +25 -0
  12. data/app/views/exports/_local_form.html.erb +1 -0
  13. data/app/views/exports/_rexport_model.html.erb +17 -0
  14. data/app/views/exports/_show.html.erb +53 -0
  15. data/app/views/exports/edit.html.erb +1 -0
  16. data/app/views/exports/index.html.erb +33 -0
  17. data/app/views/exports/new.html.erb +7 -0
  18. data/app/views/exports/show.html.erb +1 -0
  19. data/config/routes.rb +11 -0
  20. data/db/migrate/20091105182959_create_export_tables.rb +34 -0
  21. data/lib/rexport.rb +17 -0
  22. data/lib/rexport/data_fields.rb +152 -0
  23. data/lib/rexport/export_filter_methods.rb +34 -0
  24. data/lib/rexport/export_filters_controller_methods.rb +13 -0
  25. data/lib/rexport/export_item_methods.rb +45 -0
  26. data/lib/rexport/export_item_sortings_controller_methods.rb +13 -0
  27. data/lib/rexport/export_items_controller_methods.rb +32 -0
  28. data/lib/rexport/export_methods.rb +272 -0
  29. data/lib/rexport/exports_controller_methods.rb +86 -0
  30. data/lib/rexport/tree_node.rb +45 -0
  31. data/lib/rexport/version.rb +3 -0
  32. data/test/factories.rb +86 -0
  33. data/test/jenkins.bash +3 -0
  34. data/test/log/test.log +61761 -0
  35. data/test/test_helper.rb +168 -0
  36. data/test/unit/data_field_test.rb +25 -0
  37. data/test/unit/data_fields_test.rb +117 -0
  38. data/test/unit/export_methods_test.rb +97 -0
  39. data/test/unit/rexport_model_test.rb +35 -0
  40. data/test/unit/tree_node_test.rb +43 -0
  41. metadata +165 -0
@@ -0,0 +1,34 @@
1
+ module Rexport #:nodoc:
2
+ module ExportFilterMethods
3
+ def self.included(base)
4
+ base.class_eval do
5
+ include InstanceMethods
6
+
7
+ belongs_to :export
8
+ validates_presence_of :filter_field
9
+ end
10
+ end
11
+
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
27
+ end
28
+
29
+ def attributes_for_copy
30
+ attributes.slice('filter_field', 'value')
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ module Rexport
2
+ module ExportFiltersControllerMethods
3
+ # DELETE /export_filters/1
4
+ def destroy
5
+ @export_filter = ExportFilter.find(params[:id])
6
+ @export_filter.destroy
7
+
8
+ respond_to do |format|
9
+ format.html { redirect_to(@export_filter.export) }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,45 @@
1
+ module Rexport #:nodoc:
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
14
+ end
15
+
16
+ module ClassMethods
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
22
+ end
23
+ end
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
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ module Rexport
2
+ module ExportItemSortingsControllerMethods
3
+ def update
4
+ ExportItem.resort(params[:sorted_items])
5
+
6
+ respond_to do |format|
7
+ format.js do
8
+ render :nothing => true
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module Rexport
2
+ module ExportItemsControllerMethods
3
+ # GET /export_item/1/edit
4
+ def edit
5
+ @export_item = ExportItem.find(params[:id])
6
+ end
7
+
8
+ # PUT /export_item/1
9
+ def update
10
+ @export_item = ExportItem.find(params[:id])
11
+
12
+ respond_to do |format|
13
+ if @export_item.update_attributes(params[:export_item])
14
+ flash[:notice] = 'ExportItem was successfully updated.'
15
+ format.html { redirect_to(export_path(@export_item.export)) }
16
+ else
17
+ format.html { render :action => "edit" }
18
+ end
19
+ end
20
+ end
21
+
22
+ # DELETE /export_item/1
23
+ def destroy
24
+ @export_item = ExportItem.find(params[:id])
25
+ @export_item.destroy
26
+
27
+ respond_to do |format|
28
+ format.html { redirect_to(export_path(@export_item.export)) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,272 @@
1
+ require 'csv'
2
+
3
+ module Rexport #:nodoc:
4
+
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
27
+
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
33
+
34
+ def name
35
+ klass.name
36
+ end
37
+ end
38
+
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_name
48
+ after_save :save_export_items
49
+ scope :alphabetical, -> { order :name }
50
+ scope :categorical, -> { order :model_name }
51
+ scope :by_model, ->(model_name) { where(model_name: model_name) }
52
+ end
53
+ end
54
+
55
+ module ClassMethods
56
+ def models
57
+ %w(override_this_method)
58
+ end
59
+
60
+ def enabled
61
+ find(:all).select {|export| export.enabled? }
62
+ end
63
+ end
64
+
65
+ module InstanceMethods
66
+ def full_name
67
+ "#{model_name.pluralize} - #{name}"
68
+ end
69
+
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
77
+ end
78
+ end
79
+
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
88
+ end
89
+ end
90
+
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
95
+
96
+ # Returns the export model class
97
+ def export_model
98
+ model_name.constantize
99
+ end
100
+
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
105
+
106
+ # Returns the records for the export
107
+ def records
108
+ @records ||= get_records
109
+ end
110
+
111
+ # Returns a limited number of records for the export
112
+ def sample_records
113
+ get_records(Rexport::SAMPLE_SIZE)
114
+ end
115
+
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
121
+
122
+ def has_rexport_field?(rexport_field)
123
+ rexport_fields.include?(rexport_field)
124
+ end
125
+
126
+ def rexport_fields=(rexport_fields)
127
+ @rexport_fields = if rexport_fields.respond_to?(:keys)
128
+ @set_position = false
129
+ rexport_fields.keys
130
+ else
131
+ @set_position = true
132
+ rexport_fields
133
+ end
134
+ end
135
+
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
149
+
150
+ def filter_value(filter_field)
151
+ filter = export_filters.detect {|f| f.filter_field == filter_field}
152
+ filter ? filter.value : nil
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
161
+
162
+ def modifiable?
163
+ # override to disable edit and destroy links for specific exports
164
+ true
165
+ end
166
+
167
+ def enabled?
168
+ export_model.count > 0
169
+ end
170
+
171
+ #########
172
+ protected
173
+ #########
174
+
175
+ def get_records(limit = nil)
176
+ get_export_values(export_model.where(build_conditions).includes(build_include).limit(limit))
177
+ end
178
+
179
+ def seed_records(objects)
180
+ @records = get_export_values(objects)
181
+ end
182
+
183
+ def get_export_values(objects)
184
+ objects.map { |object| object.export(rexport_methods) }
185
+ end
186
+
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
195
+ 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))
201
+ end
202
+
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
210
+
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
218
+
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
224
+
225
+ def rexport_methods
226
+ @rexport_methods ||= export_model.get_rexport_methods(ordered_rexport_fields)
227
+ end
228
+
229
+ def rexport_fields
230
+ @rexport_fields ||= export_items.map {|i| i.rexport_field}
231
+ end
232
+
233
+ def ordered_rexport_fields
234
+ export_items.ordered.map {|i| i.rexport_field}
235
+ end
236
+
237
+ def filter_fields
238
+ export_filters.map {|f| f.filter_field}
239
+ end
240
+
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
249
+
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
256
+
257
+ export_items.reset
258
+ @rexport_fields = nil
259
+ return true
260
+ end
261
+
262
+ def attributes_for_copy
263
+ attributes.slice('model_name', 'description').merge(name: find_unique_name(self.name))
264
+ end
265
+
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
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,86 @@
1
+ module Rexport
2
+ module ExportsControllerMethods
3
+ # GET /exports
4
+ def index
5
+ @exports = Export.categorical.alphabetical
6
+
7
+ respond_to do |format|
8
+ format.html # index.html.erb
9
+ end
10
+ end
11
+
12
+ # GET /exports/1
13
+ def show
14
+ @export = Export.find(params[:id])
15
+
16
+ respond_to do |format|
17
+ format.html # show.html.erb
18
+ format.csv { send_data(@export.to_csv, :type => export_content_type, :filename => filename) }
19
+ end
20
+ end
21
+
22
+ # GET /exports/new
23
+ def new
24
+ @export = Export.new(params[:export])
25
+
26
+ respond_to do |format|
27
+ format.html # new.html.erb
28
+ end
29
+ end
30
+
31
+ # GET /exports/1/edit
32
+ def edit
33
+ @export = Export.find(params[:id])
34
+ end
35
+
36
+ # POST /exports
37
+ def create
38
+ @export = params[:original_export_id] ? Export.find(params[:original_export_id]).copy : Export.new(params[:export])
39
+
40
+ respond_to do |format|
41
+ if @export.save
42
+ flash[:notice] = 'Export was successfully created.'
43
+ format.html { redirect_to(@export) }
44
+ else
45
+ format.html { render :action => "new" }
46
+ end
47
+ end
48
+ end
49
+
50
+ # PUT /exports/1
51
+ def update
52
+ @export = Export.find(params[:id])
53
+
54
+ respond_to do |format|
55
+ if @export.update_attributes(params[:export])
56
+ flash[:notice] = 'Export was successfully updated.'
57
+ format.html { redirect_to(@export) }
58
+ else
59
+ format.html { render :action => "edit" }
60
+ end
61
+ end
62
+ end
63
+
64
+ # DELETE /exports/1
65
+ def destroy
66
+ @export = Export.find(params[:id])
67
+ @export.destroy
68
+
69
+ respond_to do |format|
70
+ format.html { redirect_to(exports_url) }
71
+ end
72
+ end
73
+
74
+ #######
75
+ private
76
+ #######
77
+
78
+ def export_content_type
79
+ request.user_agent =~ /windows/i ? 'application/vnd.ms-excel' : 'text/csv'
80
+ end
81
+
82
+ def filename
83
+ "#{@export.model_name}_#{@export.name.gsub(/ /, '_')}_#{Time.now.strftime('%Y%m%d')}.csv"
84
+ end
85
+ end
86
+ end