active_export 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,19 +1,44 @@
1
1
  # ActiveExport
2
2
 
3
- Export to csv from ActiveRecord or others
3
+ ActiveExport generate from ActiveRecord or others to CSV String or CSV file.
4
4
 
5
- You do not need to write the dirty code to output the csv in your controller, model or others.
5
+ You can write the logic of generating CSV to a YAML file.
6
6
 
7
- In your controller:
7
+ Another Support:
8
+
9
+ * csv label adapt i18n.
10
+ * when the value of csv data is null or blank or true or false, change another label<br>
11
+ ex) nil to '', blank to 'empty', true to 'Yes', false to 'No'<br>
12
+
13
+ Example:
8
14
 
9
15
  ````ruby
10
- ActiveExport::Csv.export(Book.all, source_name, namespace)
16
+ ActiveExport::Csv.export(Book.scoped, source_name, namespace)
17
+ ````
18
+
19
+ YAML file
20
+ <pre>
21
+ namespace:
22
+ label_prefix: 'book'
23
+ methods:
24
+ - name
25
+ - author.name
26
+ - price: '(price * 1.095).ceil.to_i'
27
+ - created_at.strftime('%Y-%m-%d')
28
+ </pre>
29
+
30
+ Write the same way without the ActiveExport:
11
31
 
12
- # it means
32
+ ````ruby
13
33
  CSV.generate do |csv|
14
34
  csv << ['Title', 'Author', 'Price(in Tax)', 'Published Date']
15
35
  Book.all.each do |book|
16
- csv << [book.name, (book.author.try(:name) || ''), book.price, book.created_at.strftime('%Y%m%d')]
36
+ csv_data = []
37
+ csv_data << book.name.blank? ? '' : book.name
38
+ csv_data << book.author ? book.author.name : ''
39
+ csv_data << (book.price * 1.095).ceil.to_i
40
+ csv_data << book.created_at.blank? ? '' : book.created_at.strftime('%Y-%m-%d')
41
+ csv << csv_data
17
42
  end
18
43
  end
19
44
  end
@@ -37,106 +62,68 @@ Or install it yourself as:
37
62
 
38
63
  Add initalizers `active_export.rb`
39
64
 
40
- Create `active_export.yml` And write csv export method
41
-
42
- [YAML file format][yaml-file-format]
65
+ touch config/initializers/active_export.rb
43
66
 
44
- Use `ActiveExport::Csv.export(data, source_name, namespace)` in your controller or others.
45
-
46
- ## Example
47
-
48
- ActiveRecord:
67
+ Write configuration code to `active_export.rb`
49
68
 
50
69
  ````ruby
51
- class Book < ActiveRecord::Base
52
- belongs_to :author
53
- end
54
-
55
- class Author < ActiveRecord::Base
70
+ ActiveExportconfigure do |config|
71
+ config.sources = { default: Rails.root.join('config', 'active_export.yml') }
72
+ # config.default_csv_optoins = { col_sep: ',', row_sep: "\n", force_quotes: true }
73
+ # config.default_find_in_batches_options = {} # default
74
+ # config.default_value_label_scope = [:default_value_labels] # default
75
+ # config.always_reload = false # default
76
+ # config.no_source_raise_error = false # default
56
77
  end
57
78
  ````
58
79
 
59
- Book records:
60
-
61
- | id | name | author_id | price | created_at |
62
- |:--:|:----:|:---------:|:-----:|:----------:|
63
- | 1 | Ruby | 1 | 50 | 2012/08/01 00:00:00 UTC |
64
- | 2 | Java | 2 | 30 | 2012/08/02 00:00:00 UTC |
65
-
66
- Author records:
80
+ Create `active_export.yml` And write csv export method
67
81
 
68
- | id | name |
69
- |:--:|:-----:|
70
- | 1 | Bob |
71
- | 2 | Alice |
82
+ touch config/active_export.yml
72
83
 
73
- `en.yml`:
84
+ Write Csv generate logic to `active_export.yml`
74
85
 
75
86
  <pre>
76
- activerecord:
77
- attributes:
78
- book:
79
- name: 'Title'
80
- price: 'Price(in Tax)'
81
- created_at: 'Published Date'
82
- author:
83
- name: 'Author'
87
+ namespace_1:
88
+ label_prefix: 'book'
89
+ methods:
90
+ - method
91
+ - method
92
+ ...
93
+ namespace_2:
94
+ label_prefix: ...
95
+ ...
84
96
  </pre>
85
97
 
86
- `config/initializers/active_export.rb`:
98
+ Call Export method
87
99
 
88
- ````ruby
89
- ActiveExportconfigure do |config|
90
- config.sources = { default: Rails.root.join('config', 'active_export.yml') }
91
- ## option fields
92
- # config.default_csv_optoins = { col_sep: ',', row_sep: "\n", force_quotes: true }
93
- # config.always_reload = false # default
94
- # config.no_source_raise_error = false # default
95
- end
96
- ````
100
+ ActiveExport::Csv.export(Book.scoped, :default, :namespace_1)
101
+ ActiveExport::Csv.export_file(Book.scoped, :default, :namespace_1, filename)
97
102
 
98
- `config/active_export.yml`:
103
+ ## ActiveExport::Csv
99
104
 
100
- ````
101
- book:
102
- label_prefix: 'book'
103
- methods:
104
- - name
105
- - author.name
106
- - price
107
- - created_at: creaetd_at.strftime('%Y/%m/%d')
108
- ````
105
+ Support 2 methods:
109
106
 
110
- In your controller or others:
107
+ * `export(data, source_name, namespace, options = {})` ... Generate Csv string
108
+ * `export_file(data, source_name, namespace, filename, options = {})` ... Generate Csv file
111
109
 
112
- ````ruby
113
- ActiveExport::Csv.export(Book.all, :default, :book)
114
- # => CSV string
115
- # "Title","Author","Price(in Tax)","Published Date"
116
- # "Ruby","Bob","50","2012/08/01"
117
- # "Java","Alice","20","2012/08/02"
118
- ````
110
+ options:
111
+
112
+ * `:eval_methods` ... override export method from YAML file.
113
+ * `:label_keys` ... override csv header label from YAML file.
114
+ * `:label_prefix` ... override csv header label prefix from YAML file.
115
+ * `:csv_options` ... Csv generate options.
116
+ * `:header` ... false to not export Csv header labels.
119
117
 
120
118
  ## YAML file format
121
119
 
122
- ```
123
- [namespace]:
124
- label_prefix: [label_prefix]
120
+ <pre>
121
+ namespace:
122
+ label_prefix: label_prefix
125
123
  methods:
126
- - [method_name]
127
- - [label_name]: [method_name]
128
- - ...
129
- ```
130
-
131
- ### Method_name examples
132
-
133
- ```
134
- book:
135
- - "author.name" # call [instance].author.name
136
- - "price > 0" # call [instance].price > 0 # => true or false
137
- - "price.to_f / 2.0" # call [instance].price.to_f / 2.0
138
- - "sprintf("%#b", price)" # call sprintf("%#b", [instance].price)
139
- ```
124
+ - method_name
125
+ - label_name: method_name
126
+ </pre>
140
127
 
141
128
  ### I18n field priority
142
129
 
@@ -165,12 +152,23 @@ label_prefix ... "book"
165
152
  source_name ... "default"
166
153
  namespace ... "book_1"
167
154
 
168
- 1. "active_export.default.book_1.book_name"
169
- 2. "activerecord.attributes.book.name"
170
- 3. "activemode.attributes.book.name"
171
- 4. "book_name".humanize # => Book name
155
+ 1. `active_export.default.book_1.book_name`
156
+ 2. `activerecord.attributes.book.name`
157
+ 3. `activemode.attributes.book.name`
158
+ 4. `book_name`.humanize # => Book name
172
159
  </pre>
173
160
 
161
+
162
+ ### methods examples
163
+
164
+ ```
165
+ book:
166
+ - "author.name" # call [instance].author.name
167
+ - "price > 0" # call [instance].price > 0 # => true or false
168
+ - "price.to_f / 2.0" # call [instance].price.to_f / 2.0
169
+ - "sprintf("%#b", price)" # call sprintf("%#b", [instance].price)
170
+ ```
171
+
174
172
  ## Contributing
175
173
 
176
174
  1. Fork it
@@ -5,15 +5,16 @@ require 'active_support'
5
5
 
6
6
  module ActiveExport
7
7
  class Base
8
- attr_accessor :source_name, :namespace, :label_prefix, :source, :label_keys, :eval_methods
8
+ attr_accessor :source_name, :namespace, :label_prefix, :source, :label_keys, :eval_methods, :options
9
9
  attr_reader :config
10
10
  def initialize(source_name, namespace, options = {})
11
11
  @source_name = source_name.to_sym
12
12
  @namespace = namespace.to_sym
13
13
  @config = ::ActiveExport.configuration
14
- @label_keys = options.has_key?(:label_keys) ? options[:label_keys] : nil
15
- @eval_methods = options.has_key?(:eval_methods) ? options[:eval_methods] : nil
16
- @label_prefix = options.has_key?(:label_prefix) ? options[:label_prefix] : nil
14
+ @label_keys = options.has_key?(:label_keys) ? options.delete(:label_keys) : nil
15
+ @eval_methods = options.has_key?(:eval_methods) ? options.delete(:eval_methods) : nil
16
+ @label_prefix = options.has_key?(:label_prefix) ? options.delete(:label_prefix) : nil
17
+ @options = options
17
18
  end
18
19
 
19
20
  class << self
@@ -90,7 +91,7 @@ module ActiveExport
90
91
  def source
91
92
  @source ||= ::ActiveExport[self.source_name][self.namespace]
92
93
  rescue => e
93
- raise e if config.no_source_raise_error = true
94
+ raise e if self.config.no_source_raise_error
94
95
  return {}
95
96
  end
96
97
 
@@ -2,10 +2,17 @@
2
2
 
3
3
  module ActiveExport
4
4
  class Configuration
5
+ # YAML file list
5
6
  attr_accessor :sources
7
+ # Default options export CSV
6
8
  attr_accessor :default_csv_options
9
+ # Default options using `find_in_batches`
10
+ attr_accessor :default_find_in_batches_options
11
+ # Set true, no cached YAML file data
7
12
  attr_accessor :always_reload
13
+ # Export data default labels when value is nil or blank or true or false
8
14
  attr_accessor :default_value_label_scope
15
+ # Set true, if selected source file does not exists, ActiveExport raise error
9
16
  attr_accessor :no_source_raise_error
10
17
 
11
18
  def initialize
@@ -14,6 +21,7 @@ module ActiveExport
14
21
  @always_reload = false
15
22
  @default_value_label_scope = [:default_value_labels]
16
23
  @no_source_raise_error = false
24
+ @default_find_in_batches_options = {}
17
25
  end
18
26
  end
19
27
  end
@@ -4,6 +4,7 @@ require 'csv'
4
4
 
5
5
  module ActiveExport
6
6
  class Csv < ::ActiveExport::Base
7
+ # Generate Csv String
7
8
  # @param [Array, ActiveRecord::Relation] data
8
9
  # @param [Symbol, String] source
9
10
  # @param [Symobl, String] namespace
@@ -12,26 +13,54 @@ module ActiveExport
12
13
  # @option options [Array] :label_keys
13
14
  # @option options [String] :label_prefix
14
15
  # @option options [Hash] :csv_options (see http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV.html)
16
+ # @return [String] Csv String
15
17
  # @exmaple
16
18
  # AcriveExport::Csv.export(data, :source, :namespace)
17
19
  def self.export(data, source, namespace, options = {})
18
- new(source, namespace, options).export(data, options)
20
+ new(source, namespace, options).export(data)
19
21
  end
20
22
 
21
- def export(data, options = {})
22
- return '' if data.length <= 0 && eval_methods.nil?
23
- csv_options = self.config.default_csv_options.merge( options[:csv_options] || {} )
23
+ # Generate Csv File
24
+ # @param [Array, ActiveRecord::Relation] data
25
+ # @param [Symbol, String] source YAML File alias name (see ActiveExport::Configuration#sources)
26
+ # @param [Symbol, String] namespace YAML File namespace
27
+ # @param [String, Filepath] filename Csv File name
28
+ # @param [Hash] options ({}) (see .export)
29
+ def self.export_file(data, source, namespace, filename, options = {})
30
+ new(source, namespace, options).export_file(data, filename)
31
+ end
24
32
 
25
- each_method_name = data.respond_to?(:find_each) ? 'find_each' : 'each'
26
- # 1.9.2
33
+ def export(data)
27
34
  CSV.generate(csv_options) do |csv|
28
- csv << generate_header
29
- data.send(each_method_name) do |f|
30
- csv << generate_value(f)
35
+ csv << generate_header if header?
36
+ export_data(data, csv)
37
+ end
38
+ end
39
+
40
+ def export_file(data, filename)
41
+ File.atomic_write(filename) do |file|
42
+ CSV.open(file, "wb", csv_options) do |csv|
43
+ csv << generate_header if header?
44
+ export_data(data, csv)
31
45
  end
32
46
  end
47
+ filename
33
48
  end
34
49
 
50
+ # Export data to exporter
51
+ # @param [Array, ActiveRecord::Relation] data Export target data
52
+ # @param [CSV, Array, String] exporter CSV class or Array class, or String class (should support '<<' accessor)
53
+ def export_data(data, exporter)
54
+ if data.respond_to?(:find_in_batches) && data.respond_to?(:orders) && data.orders.blank? && data.respond_to?(:taken) && data.taken.blank?
55
+ data.find_in_batches(find_in_batches_options) do |group|
56
+ group.each{|f| exporter << generate_value(f) }
57
+ end
58
+ else
59
+ data.each{|f| exporter << generate_value(f) }
60
+ end
61
+ end
62
+
63
+ protected
35
64
  def generate_header
36
65
  self.label_keys.inject([]) {|result, key|
37
66
  result << translate(key_name(key))
@@ -44,5 +73,17 @@ module ActiveExport
44
73
  result << convert(v)
45
74
  }
46
75
  end
76
+
77
+ def find_in_batches_options
78
+ self.config.default_find_in_batches_options.merge( self.options[:find_in_batches_options] || {} )
79
+ end
80
+
81
+ def csv_options
82
+ self.config.default_csv_options.merge( self.options[:csv_options] || {} )
83
+ end
84
+
85
+ def header?
86
+ { header: true }.merge(self.options)[:header]
87
+ end
47
88
  end
48
89
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveExport
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -4,19 +4,11 @@ require 'spec_helper'
4
4
 
5
5
  describe ActiveExport::Base do
6
6
  before {
7
- @default_locale = I18n.locale
8
- I18n.locale = :en
9
-
10
7
  ActiveExport.configure do |config|
11
8
  config.sources = { default: fixture_file('csv_1.yml') }
12
9
  end
13
10
  }
14
11
 
15
- after {
16
- I18n.backend.reload!
17
- I18n.locale = @default_locale
18
- }
19
-
20
12
  let(:active_export) { ActiveExport::Base.new('default', 'book_2') }
21
13
  it { active_export.source_name = :default }
22
14
  it { active_export.namespace = :book_2 }
@@ -59,6 +51,10 @@ describe ActiveExport::Base do
59
51
 
60
52
  describe "#source" do
61
53
  before {
54
+ ActiveExport.configure do |config|
55
+ config.sources = { }
56
+ config.no_source_raise_error = true
57
+ end
62
58
  active_export.source_name = :not_found
63
59
  }
64
60
  it { expect { active_export.source }.to raise_error RuntimeError }
@@ -85,9 +81,7 @@ describe ActiveExport::Base do
85
81
  context "active_export" do
86
82
  before do
87
83
  I18n.backend.store_translations :en, active_export: {
88
- default: {
89
- book: { author_name: 'author_name' }
90
- }
84
+ default: { book: { author_name: 'author_name' } }
91
85
  }
92
86
  end
93
87
  it { should == 'author_name' }
@@ -3,16 +3,6 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe ActiveExport::Csv do
6
- before {
7
- @default_locale = I18n.locale
8
- I18n.locale = :en
9
- }
10
-
11
- after {
12
- I18n.backend.reload!
13
- I18n.locale = @default_locale
14
- }
15
-
16
6
  describe ".export" do
17
7
  let(:author_1) { Author.create!(name: 'author_1') }
18
8
  let(:author_2) { Author.create!(name: 'author_2') }
@@ -37,19 +27,85 @@ describe ActiveExport::Csv do
37
27
  }
38
28
  end
39
29
 
40
- context "add some options" do
30
+ context "add csv_options" do
41
31
  let(:csv_options) {
42
32
  { force_quotes: false, col_sep: ':'}
43
33
  }
44
- subject { ActiveExport::Csv.export(Book.order('id DESC').all, :default, :book_1, csv_options: csv_options) }
34
+ subject { ActiveExport::Csv.export(Book.scoped, :default, :book_1, csv_options: csv_options) }
45
35
  it {
46
36
  should == <<-EOS
47
37
  Book name:Author name:Book price
48
- book_2:author_2:38
49
38
  book_1:author_1:58
39
+ book_2:author_2:38
40
+ EOS
41
+ }
42
+ end
43
+
44
+ context "header false" do
45
+ subject { ActiveExport::Csv.export(Book.scoped, :default, :book_1, header: false) }
46
+ it {
47
+ should == <<-EOS
48
+ "book_1","author_1","58"
49
+ "book_2","author_2","38"
50
50
  EOS
51
51
  }
52
52
  end
53
+
54
+ end
55
+
56
+ describe ".export_file" do
57
+ let(:filename) { Rails.root.join('tmp', 'test.csv') }
58
+ before {
59
+ ActiveExport.configure do |config|
60
+ config.sources = { default: fixture_file('csv_1.yml') }
61
+ config.default_csv_options = { col_sep: ',', row_sep: "\n", force_quotes: true }
62
+ end
63
+
64
+ FileUtils.rm(filename) if FileTest.exist?(filename)
65
+ ActiveExport::Csv.export_file(Book.scoped, :default, :book_1, Rails.root.join('tmp', 'test.csv'))
66
+ }
67
+ after {
68
+ FileUtils.rm(filename) if FileTest.exist?(filename)
69
+ }
70
+
71
+ it { File.read(filename).split("\n").first.should eql %Q("Book name","Author name","Book price") }
72
+ end
73
+
74
+ describe "#export_data" do
75
+ let(:csv_exporter) { ActiveExport::Csv.new(:default, :book_1) }
76
+ let!(:book_1) { Book.create!(name: 'book_1', author: nil, price: 58) }
77
+ let!(:book_2) { Book.create!(name: 'book_2', author: nil, price: 58) }
78
+ let!(:book_3) { Book.create!(name: 'book_2', author: nil, price: 58) }
79
+
80
+ it "should not call find_in_batches when has order" do
81
+ obj = Book.order('id ASC')
82
+ obj.should_not_receive(:find_in_batches)
83
+ obj.should_receive(:each)
84
+ csv_exporter.send(:export_data, obj, [])
85
+ end
86
+
87
+
88
+ it "should not call find_in_batches when has limit" do
89
+ obj = Book.limit(1)
90
+ obj.should_not_receive(:find_in_batches)
91
+ obj.should_receive(:each)
92
+ csv_exporter.send(:export_data, obj, [])
93
+ end
94
+
95
+ it "should not call find_in_batches when specified obj is not ActiveRecord::Relation" do
96
+ obj = [Book.first]
97
+ obj.should_not_receive(:find_in_batches)
98
+ obj.should_receive(:each)
99
+ csv_exporter.send(:export_data, obj, [])
100
+ end
101
+
102
+ it "should call find_in_batches" do
103
+ obj = Book.scoped
104
+ obj.should_receive(:find_in_batches).with({batch_size: 1})
105
+ obj.should_not_receive(:each)
106
+ csv_exporter.stub(:find_in_batches_options).and_return({batch_size: 1})
107
+ csv_exporter.send(:export_data, obj, [])
108
+ end
53
109
  end
54
110
 
55
111
  describe ".generate_value" do
@@ -63,7 +119,7 @@ book_1:author_1:58
63
119
  default_value_labels: { nil: 'not found', blank: 'Blank', true: 'Yes!', false: 'No!' }
64
120
  }
65
121
  }
66
- subject { csv_exporter.generate_value(book_1) }
122
+ subject { csv_exporter.send(:generate_value, book_1) }
67
123
  it { should eql ['book_1', 'author_1', '1000', 'not found', 'Yes!', 'No!'] }
68
124
  end
69
125
 
@@ -71,11 +127,8 @@ book_1:author_1:58
71
127
  let(:label_keys) { %w(name author.name price) }
72
128
  let(:csv_exporter) { ActiveExport::Csv.new(:default, :book_1, label_keys: label_keys, label_prefix: 'book') }
73
129
 
74
- subject { csv_exporter.generate_header }
75
-
76
- context "no language file" do
77
- it { should eql ['Book name', 'Author name', 'Book price'] }
78
- end
130
+ subject { csv_exporter.send(:generate_header) }
131
+ it { should eql ['Book name', 'Author name', 'Book price'] }
79
132
 
80
133
  context "translate" do
81
134
  before do
data/spec/spec_helper.rb CHANGED
@@ -21,9 +21,13 @@ RSpec.configure do |config|
21
21
 
22
22
  config.before(:each) do
23
23
  DatabaseCleaner.start
24
+ @default_locale = I18n.locale
25
+ I18n.locale = :en
24
26
  end
25
27
 
26
28
  config.after(:each) do
29
+ I18n.backend.reload!
30
+ I18n.locale = @default_locale
27
31
  DatabaseCleaner.clean
28
32
  end
29
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_export
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-14 00:00:00.000000000 Z
12
+ date: 2012-08-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport