active_export 0.1.0 → 0.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.
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