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
@@ -3,75 +3,74 @@ module Rexport
3
3
  def index
4
4
  @exports = Export.categorical.alphabetical
5
5
  end
6
-
6
+
7
7
  def show
8
- @export = Export.find(params[:id])
9
-
8
+ export
9
+
10
10
  respond_to do |format|
11
11
  format.html # show.html.erb
12
- format.csv { send_data(@export.to_csv, :type => export_content_type, :filename => filename) }
12
+ format.csv { send_data(export.to_csv, type: export_content_type, filename: filename) }
13
13
  end
14
14
  end
15
-
15
+
16
16
  def new
17
17
  @export = Export.new(export_params)
18
18
  end
19
-
19
+
20
20
  def edit
21
- @export = Export.find(params[:id])
21
+ export
22
22
  end
23
-
23
+
24
24
  def create
25
25
  @export = params[:original_export_id] ? Export.find(params[:original_export_id]).copy : Export.new(export_params)
26
-
26
+
27
27
  if @export.save
28
28
  redirect_to @export, notice: 'Export was successfully created.'
29
29
  else
30
30
  render :new
31
31
  end
32
32
  end
33
-
33
+
34
34
  def update
35
- @export = Export.find(params[:id])
36
-
37
- if @export.update_attributes(export_params)
38
- redirect_to @export, notice: 'Export was successfully updated.'
35
+ if export.update(export_params)
36
+ redirect_to export, notice: 'Export was successfully updated.'
39
37
  else
40
38
  render :edit
41
39
  end
42
40
  end
43
-
41
+
44
42
  def destroy
45
- @export = Export.find(params[:id])
46
- @export.destroy
47
-
43
+ export.destroy
44
+
48
45
  redirect_to exports_url
49
46
  end
50
-
47
+
51
48
  private
52
-
53
- def export_params
54
- params.require(:export).permit(:name, :model_class_name, :description).merge(rexport_fields: rexport_fields, export_filter_attributes: export_filter_attributes)
55
- end
56
-
57
- def rexport_fields
58
- permit_all params[:export][:rexport_fields]
49
+
50
+ def export
51
+ @export ||= Export.find(params[:id])
59
52
  end
60
-
61
- def export_filter_attributes
62
- permit_all params[:export][:export_filter_attributes]
53
+
54
+ def export_params
55
+ params.require(:export).permit(permitted_params)
63
56
  end
64
-
65
- def permit_all(params)
66
- params ? params.permit! : []
57
+
58
+ def permitted_params
59
+ [
60
+ :name,
61
+ :model_class_name,
62
+ :description,
63
+ rexport_fields: {},
64
+ export_filter_attributes: {}
65
+ ]
67
66
  end
68
-
67
+
69
68
  def export_content_type
70
69
  request.user_agent =~ /windows/i ? 'application/vnd.ms-excel' : 'text/csv'
71
70
  end
72
-
71
+
73
72
  def filename
74
- "#{@export.model_class_name}_#{@export.name.gsub(/ /, '_')}_#{Time.now.strftime('%Y%m%d')}.csv"
73
+ "#{export.model_class_name}_#{export.name.gsub(/ /, '_')}_#{Time.now.strftime('%Y%m%d')}.csv"
75
74
  end
76
75
  end
77
76
  end
@@ -0,0 +1,118 @@
1
+ module Rexport #:nodoc:
2
+ class RexportModel
3
+ attr_accessor :klass, :path
4
+
5
+ def initialize(klass, path: nil)
6
+ self.klass = klass
7
+ self.path = path.to_s unless path.blank?
8
+ initialize_rexport_fields
9
+ end
10
+
11
+ def rexport_fields
12
+ @rexport_fields ||= HashWithIndifferentAccess.new
13
+ end
14
+
15
+ def rexport_fields_array
16
+ rexport_fields.values.sort
17
+ end
18
+
19
+ def field_path(field_name)
20
+ [path, field_name].compact * '.'
21
+ end
22
+
23
+ def collection_from_association(association)
24
+ if klass.respond_to?("find_#{association}_for_rexport")
25
+ klass.public_send("find_#{association}_for_rexport")
26
+ else
27
+ klass.reflect_on_association(association.to_sym).klass.all
28
+ end
29
+ end
30
+
31
+ def filter_column(field)
32
+ return field.method unless field.method.include?('.')
33
+ association = field.method.split('.').first
34
+ klass.reflect_on_association(association.to_sym).foreign_key
35
+ end
36
+
37
+ def name
38
+ klass.name
39
+ end
40
+
41
+ # Adds a data item to rexport_fields
42
+ def add_rexport_field(name, options = {})
43
+ rexport_fields[name.to_s] = DataField.new(name, options)
44
+ end
45
+
46
+ # Removes files from rexport_fields
47
+ # useful to remove content columns you don't want included in exports
48
+ def remove_rexport_fields(*fields)
49
+ fields.flatten.each { |field| rexport_fields.delete(field.to_s) }
50
+ end
51
+
52
+ # Adds associated methods to rexport_fields
53
+ # :associations - an association or arrary of associations
54
+ # :methods - a method or array of methods
55
+ # :filter - if true will send type: :association to add_report_field
56
+ def add_association_methods(options = {})
57
+ options.stringify_keys!
58
+ options.assert_valid_keys(%w(associations methods filter))
59
+
60
+ methods = options.reverse_merge('methods' => 'name')['methods']
61
+ methods = [methods] if methods.kind_of?(String)
62
+
63
+ associations = options['associations']
64
+ associations = [associations] if associations.kind_of?(String)
65
+
66
+ type = options['filter'] ? :association : nil
67
+
68
+ associations.each do |association|
69
+ methods.each do |method|
70
+ add_rexport_field("#{association}_#{method}", method: "#{association}.#{method}", type: type)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Returns an array of export methods corresponding with field_names
76
+ def get_rexport_methods(*field_names)
77
+ field_names.flatten.map do |f|
78
+ begin
79
+ components = f.to_s.split('.')
80
+ field_name = components.pop
81
+ components.push(get_rexport_model(components).get_rexport_method(field_name)) * '.'
82
+ rescue NoMethodError
83
+ 'undefined_rexport_field'
84
+ end
85
+ end
86
+ end
87
+
88
+ protected
89
+
90
+ # Returns a rexport_model for the associated class by following the chain of associations
91
+ def get_rexport_model(associations)
92
+ associations.empty? ? self : rexport_models[associations.dup]
93
+ end
94
+
95
+ # Memoize rexport_models to avoid initializing rexport_fields multiple times
96
+ def rexport_models
97
+ @rexport_models ||= Hash.new do |hash, key|
98
+ hash[key] = self.class.new(klass.get_klass_from_associations(key))
99
+ end
100
+ end
101
+
102
+ # Returns the export method for a given field_name
103
+ def get_rexport_method(field_name)
104
+ if rexport_fields[field_name]
105
+ rexport_fields[field_name].method
106
+ else
107
+ raise NoMethodError
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def initialize_rexport_fields
114
+ klass.content_columns.each { |field| add_rexport_field(field.name, type: field.type) }
115
+ klass.initialize_local_rexport_fields(self) if klass.respond_to?(:initialize_local_rexport_fields)
116
+ end
117
+ end
118
+ end
@@ -2,44 +2,41 @@ module Rexport #:nodoc:
2
2
  # A basic tree for building up ActiveRecord find :include parameters
3
3
  class TreeNode
4
4
  attr_accessor :name, :children
5
-
5
+
6
6
  # Initialize a tree node setting name and adding a child if one was passed
7
7
  def initialize(name, *names)
8
8
  self.name = name
9
9
  self.children = []
10
10
  add_child(names)
11
11
  end
12
-
12
+
13
13
  # Add one or more children to the tree
14
14
  def add_child(*names)
15
15
  names.flatten!
16
16
  return unless name = names.shift
17
- if node = children.find {|c| c.name == name}
18
- node.add_child(names)
19
- else
20
- children << TreeNode.new(name, names)
21
- end
17
+ node = children.find { |c| c.name == name }
18
+ node ? node.add_child(names) : (children << TreeNode.new(name, names))
22
19
  end
23
-
20
+
24
21
  # Return an array representation of the tree
25
22
  def to_a
26
- [name, children.map {|c| c.to_a}]
23
+ [name, children.map(&:to_a)]
27
24
  end
28
-
25
+
29
26
  # Return a :include comptatible statement from the tree
30
27
  def to_include
31
- children.map {|c| c.build_include}
28
+ children.map(&:build_include)
32
29
  end
33
-
30
+
34
31
  # Return the include parameters for a child
35
32
  def build_include
36
- leaf_node? ? name.to_sym : { name.to_sym => children.map {|c| c.build_include} }
33
+ leaf_node? ? name.to_sym : { name.to_sym => children.map(&:build_include) }
37
34
  end
38
-
35
+
39
36
  private
40
-
37
+
41
38
  def leaf_node?
42
39
  children.blank?
43
40
  end
44
41
  end
45
- end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module Rexport
2
- VERSION = '0.5.4'
2
+ VERSION = '1.2.0'
3
3
  end
data/test/factories.rb CHANGED
@@ -1,86 +1,91 @@
1
1
  module Rexport
2
2
  module Factories
3
-
4
- FactoryGirl.define do
5
-
3
+
4
+ FactoryBot.define do
5
+
6
6
  factory :status do
7
- name 'active'
7
+ name { 'active' }
8
8
  end
9
9
 
10
10
  factory :family do
11
- name 'The Sample Family'
11
+ name { 'The Sample Family' }
12
12
  end
13
-
13
+
14
14
  factory :student do
15
- family {|f| f.association(:family)}
16
- name 'Sammy Sample'
17
- date_of_birth Date.parse('2008-12-08')
15
+ family { |family| family.association(:family) }
16
+ name { 'Sammy Sample' }
17
+ date_of_birth { Date.parse('2008-12-08') }
18
18
  end
19
-
19
+
20
20
  factory :enrollment do
21
- status {|status| status.association(:status)}
22
- student {|s| s.association(:student)}
23
- grade 1
21
+ status { |status| status.association(:status) }
22
+ student { |student| student.association(:student) }
23
+ grade { 1 }
24
+ updated_at { Time.now }
24
25
  end
25
-
26
- factory :second_grade_enrollment, :class => 'Enrollment' do
27
- status {|status| status.association(:status)}
28
- grade 2
26
+
27
+ factory :second_grade_enrollment, class: 'Enrollment' do
28
+ status { |status| status.association(:status) }
29
+ grade { 2 }
29
30
  end
30
-
31
+
31
32
  factory :export do
32
- name 'Enrollment Export'
33
- model_class_name 'Enrollment'
33
+ name { 'Enrollment Export' }
34
+ model_class_name { 'Enrollment' }
34
35
  export_items do |items|
35
36
  %w(family_name_export_item grade_export_item status_name_export_item bogus_export_item).map do |item|
36
37
  items.association(item)
37
38
  end
38
39
  end
39
40
  end
40
-
41
- factory :filtered_export, :class => 'Export' do
42
- name 'Filtered Enrollment Export'
43
- model_class_name 'Enrollment'
41
+
42
+ factory :filtered_export, class: 'Export' do
43
+ name { 'Filtered Enrollment Export' }
44
+ model_class_name { 'Enrollment' }
45
+ export_items do |items|
46
+ %w(grade_export_item status_name_export_item).map do |item|
47
+ items.association(item)
48
+ end
49
+ end
44
50
  export_filters do |filters|
45
51
  %w(grade_filter status_filter).map do |filter|
46
52
  filters.association(filter)
47
53
  end
48
54
  end
49
55
  end
50
-
51
- factory :family_name_export_item, :class => 'ExportItem' do
52
- position 1
53
- name 'Family Name'
54
- rexport_field 'student.family.name'
56
+
57
+ factory :family_name_export_item, class: 'ExportItem' do
58
+ position { 1 }
59
+ name { 'Family Name' }
60
+ rexport_field { 'student.family.name' }
55
61
  end
56
-
57
- factory :grade_export_item, :class => 'ExportItem' do
58
- position 2
59
- name 'Grade'
60
- rexport_field 'grade'
62
+
63
+ factory :grade_export_item, class: 'ExportItem' do
64
+ position { 2 }
65
+ rexport_field { 'grade' }
61
66
  end
62
-
63
- factory :status_name_export_item, :class => 'ExportItem' do
64
- position 3
65
- name 'Status'
66
- rexport_field 'status_name'
67
+
68
+ factory :status_name_export_item, class: 'ExportItem' do
69
+ position { 3 }
70
+ name { 'Status' }
71
+ rexport_field { 'status_name' }
67
72
  end
68
-
69
- factory :bogus_export_item, :class => 'ExportItem' do
70
- position 4
71
- name 'Bogus Item'
72
- rexport_field 'bogus_field'
73
+
74
+ factory :bogus_export_item, class: 'ExportItem' do
75
+ position { 4 }
76
+ name { 'Bogus Item' }
77
+ rexport_field { 'bogus_field' }
73
78
  end
74
-
75
- factory :grade_filter, :class => 'ExportFilter' do
76
- filter_field 'grade'
77
- value '1'
79
+
80
+ factory :grade_filter, class: 'ExportFilter' do
81
+ filter_field { 'grade' }
82
+ value { '1' }
78
83
  end
79
-
80
- factory :status_filter, :class => 'ExportFilter' do
81
- filter_field 'status.name'
82
- value 'active'
84
+
85
+ factory :status_filter, class: 'ExportFilter' do
86
+ filter_field { 'status.name' }
87
+ value { 'active' }
83
88
  end
84
89
  end
85
90
  end
86
- end
91
+ end
data/test/test_helper.rb CHANGED
@@ -1,29 +1,26 @@
1
- require 'minitest/autorun'
2
- require 'rubygems'
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter '/test/'
4
+ end
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
7
+ require "rails"
3
8
  require 'active_record'
4
- require 'active_support/test_case'
5
- require 'logger'
6
- require 'factory_girl'
7
- require 'mocha/mini_test'
8
- require File.dirname(__FILE__) + '/factories'
9
- require File.dirname(__FILE__) + '/../lib/rexport/data_fields'
10
- require File.dirname(__FILE__) + '/../lib/rexport/export_methods'
11
- require File.dirname(__FILE__) + '/../lib/rexport/export_item_methods'
12
- require File.dirname(__FILE__) + '/../lib/rexport/export_filter_methods'
13
- require File.dirname(__FILE__) + '/../lib/rexport/tree_node'
9
+ require "rexport"
14
10
 
15
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
11
+ require 'minitest/autorun'
12
+ require 'factory_bot'
13
+ require 'mocha/minitest'
14
+ require File.dirname(__FILE__) + '/factories'
16
15
 
17
- RAILS_DEFAULT_LOGGER = Logger.new(File.dirname(__FILE__) + '/log/test.log')
18
- RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
19
- ActiveRecord::Base.logger = RAILS_DEFAULT_LOGGER
16
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
20
17
 
21
18
  class ActiveSupport::TestCase
22
- include FactoryGirl::Syntax::Methods
19
+ include FactoryBot::Syntax::Methods
23
20
  include Rexport::Factories
24
21
 
25
22
  def setup
26
- setup_db
23
+ suppress_output { setup_db }
27
24
  Enrollment.instance_variable_set('@rexport_fields', nil)
28
25
  Student.instance_variable_set('@rexport_fields', nil)
29
26
  end
@@ -32,17 +29,10 @@ class ActiveSupport::TestCase
32
29
  teardown_db
33
30
  end
34
31
 
35
- # Placeholder so test/unit ignores test cases without any tests.
36
- def default_test
37
- end
38
-
39
32
  private
40
33
 
41
34
  def setup_db
42
- old_stdout = $stdout
43
- $stdout = StringIO.new
44
-
45
- ActiveRecord::Schema.define(:version => 1) do
35
+ ActiveRecord::Schema.define(version: 1) do
46
36
  create_table :enrollments do |t|
47
37
  t.integer :student_id, :status_id, :grade
48
38
  t.boolean :active
@@ -85,8 +75,6 @@ class ActiveSupport::TestCase
85
75
  create_table :self_referential_checks do |t|
86
76
  end
87
77
  end
88
-
89
- $stdout = old_stdout
90
78
  end
91
79
 
92
80
  def teardown_db
@@ -94,6 +82,14 @@ class ActiveSupport::TestCase
94
82
  ActiveRecord::Base.connection.drop_table(table)
95
83
  end
96
84
  end
85
+
86
+ def suppress_output
87
+ original_stdout = $stdout.clone
88
+ $stdout.reopen File.new('/dev/null', 'w')
89
+ yield
90
+ ensure
91
+ $stdout.reopen original_stdout
92
+ end
97
93
  end
98
94
 
99
95
  class ActiveRecord::Base
@@ -107,7 +103,7 @@ class Enrollment < ActiveRecord::Base
107
103
  include Rexport::DataFields
108
104
  belongs_to :student
109
105
  belongs_to :status
110
- belongs_to :ilp_status, :class_name => 'Status', :foreign_key => 'ilp_status_id'
106
+ belongs_to :ilp_status, class_name: 'Status', foreign_key: 'ilp_status_id'
111
107
  belongs_to :self_referential_check
112
108
 
113
109
  def foo
@@ -116,10 +112,10 @@ class Enrollment < ActiveRecord::Base
116
112
 
117
113
  private
118
114
 
119
- def Enrollment.initialize_local_rexport_fields
120
- add_rexport_field(:foo_method, :method => :foo)
121
- add_rexport_field(:bad_method, :method => 'bad_method')
122
- add_association_methods(:associations => %w(status ilp_status))
115
+ def self.initialize_local_rexport_fields(rexport_model)
116
+ rexport_model.add_rexport_field(:foo_method, method: :foo)
117
+ rexport_model.add_rexport_field(:bad_method, method: 'bad_method')
118
+ rexport_model.add_association_methods(associations: %w(status ilp_status))
123
119
  end
124
120
  end
125
121
 
@@ -127,6 +123,10 @@ class Student < ActiveRecord::Base
127
123
  include Rexport::DataFields
128
124
  belongs_to :family
129
125
  has_many :enrollments
126
+
127
+ def self.find_family_for_rexport
128
+ Family.order(:name)
129
+ end
130
130
  end
131
131
 
132
132
  class Family < ActiveRecord::Base
@@ -139,8 +139,8 @@ class Family < ActiveRecord::Base
139
139
 
140
140
  private
141
141
 
142
- def Family.initialize_local_rexport_fields
143
- add_rexport_field(:foo_method, :method => :foo)
142
+ def self.initialize_local_rexport_fields(rexport_model)
143
+ rexport_model.add_rexport_field(:foo_method, method: :foo)
144
144
  end
145
145
  end
146
146
 
@@ -162,9 +162,6 @@ class ExportFilter < ActiveRecord::Base
162
162
  end
163
163
 
164
164
  class SelfReferentialCheck < ActiveRecord::Base
165
+ include Rexport::DataFields
165
166
  belongs_to :enrollment
166
-
167
- def SelfReferentialCheck.rexport_fields
168
- 'trick get_rexport_models into believing we are exportable'
169
- end
170
167
  end