csv_rails 0.6.1 → 0.7.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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +64 -3
  3. data/lib/csv_rails.rb +1 -0
  4. data/lib/csv_rails/active_model.rb +6 -1
  5. data/lib/csv_rails/import.rb +78 -0
  6. data/lib/csv_rails/version.rb +1 -1
  7. data/test/csv_rails/active_record_test.rb +2 -1
  8. data/test/csv_rails/array_test.rb +2 -2
  9. data/test/csv_rails/import_test.rb +134 -0
  10. data/test/dummy/app/controllers/groups_controller.rb +17 -0
  11. data/test/dummy/app/controllers/users_controller.rb +1 -1
  12. data/test/dummy/app/helpers/groups_helper.rb +2 -0
  13. data/test/dummy/app/models/group.rb +1 -0
  14. data/test/dummy/app/models/user.rb +2 -0
  15. data/test/dummy/config/database.yml +7 -19
  16. data/test/dummy/config/environments/test.rb +6 -0
  17. data/test/dummy/config/initializers/secret_token.rb +6 -1
  18. data/test/dummy/config/locales/ja.yml +4 -1
  19. data/test/dummy/config/mongoid.yml +66 -5
  20. data/test/dummy/config/routes.rb +1 -0
  21. data/test/dummy/coverage/assets/0.7.1/application.css +1110 -0
  22. data/test/dummy/coverage/assets/0.7.1/application.js +626 -0
  23. data/test/dummy/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  24. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  25. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  26. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  27. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  28. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  29. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  30. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  31. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  32. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  33. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  34. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  35. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  36. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  37. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  38. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  39. data/test/dummy/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  40. data/test/dummy/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  41. data/test/dummy/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  42. data/test/dummy/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  43. data/test/dummy/coverage/assets/0.7.1/favicon_green.png +0 -0
  44. data/test/dummy/coverage/assets/0.7.1/favicon_red.png +0 -0
  45. data/test/dummy/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  46. data/test/dummy/coverage/assets/0.7.1/loading.gif +0 -0
  47. data/test/dummy/coverage/assets/0.7.1/magnify.png +0 -0
  48. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  49. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  50. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  51. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  52. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  53. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  54. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  55. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  56. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  57. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  58. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  59. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  60. data/test/dummy/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  61. data/test/dummy/coverage/index.html +72 -0
  62. data/test/dummy/db/development.sqlite3 +0 -0
  63. data/test/dummy/db/test.sqlite3 +0 -0
  64. data/test/dummy/log/development.log +108 -1682
  65. data/test/dummy/log/test.log +46674 -58091
  66. data/test/dummy/test/fixtures/groups.csv +5 -0
  67. data/test/dummy/test/fixtures/groups_includes_wrong_user_name.csv +3 -0
  68. data/test/dummy/test/functional/groups_controller_test.rb +35 -0
  69. data/test/dummy/test/functional/users_controller_test.rb +3 -3
  70. data/test/dummy/test/unit/helpers/groups_helper_test.rb +4 -0
  71. data/test/test_helper.rb +1 -1
  72. metadata +206 -126
  73. data/test/dummy/doc/README_FOR_APP +0 -2
  74. data/test/dummy/log/production.log +0 -0
  75. data/test/dummy/log/server.log +0 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: abca01f38995c646411bf71c5194c44761f581ab
4
+ data.tar.gz: 0802df4b50d7487420c7f642154834a087675b98
5
+ SHA512:
6
+ metadata.gz: 16d5d26847e86645c93d3a5f16885a91409c4a6e14a5b6360908510a0e607771f94cdfde8632d16eb297cebd1e78a9364ab78279c26b1d273d2019a77783350a
7
+ data.tar.gz: 6de4e32ec224e341b2f867ffe45d9c798e69654bf78e741925f7515f2f0ac401c9dc75ed7b23f78267f09a35d939cc1fdcce8a30a12bab9822032cd9da2d9ef9
@@ -33,6 +33,8 @@ to your Gemfile and then run
33
33
 
34
34
  == Usage
35
35
 
36
+ === Download
37
+
36
38
  If you want formatted attribute, CsvRails call "#{attribute}_as_csv". For example, you wish formatted created_at then you write like this.
37
39
 
38
40
  class User < ActiveRecord::Base
@@ -77,11 +79,13 @@ If you do not use :header option, header is using :fields and I18n transfer.
77
79
  groups:
78
80
  first:
79
81
  <<: *groupmodel
80
- # edge rails
82
+ # rails 3.2.3 - 3.2.5
81
83
  user/groups:
82
84
  first:
83
85
  <<: *groupmodel
84
-
86
+ # rails 3.2.6 or higher
87
+ groups/first:
88
+ <<: *groupmodel
85
89
 
86
90
  # app/controllers/user_controller.rb
87
91
  def index
@@ -110,4 +114,61 @@ You also use i18n_scope option
110
114
 
111
115
  User.where("id < 1").all.to_csv(:i18n_scope => :csv) #=> "なまえ\n"
112
116
 
113
- Copyright (c) 2012 yalab, released under the MIT license
117
+ === Upload
118
+
119
+ CSVRails is also have upload concern.
120
+
121
+ You should include CSVRails::Import into your model.
122
+
123
+ example
124
+
125
+ # app/model/user.rb
126
+ class User < ActiveRecord::Base
127
+ attr_accessor :file
128
+ include CsvRails::Import
129
+ ....
130
+
131
+ And render file_field into form
132
+
133
+ example
134
+
135
+ # app/views/users/index.html.erb
136
+
137
+ <%= form_for(@user, multipart: true) do |f| %>
138
+ File: <%= f.file_field :file %>
139
+ <%= f.submit 'Upload' %>
140
+ <% end %>
141
+
142
+ Next implement action
143
+
144
+ # app/controllers/users_controller.rb
145
+
146
+ def create
147
+ if params[:format] == 'csv'
148
+ users = User.csv_import(params[:file])
149
+ if users.find{|u| u.errors.any? }
150
+ render :index
151
+ else
152
+ redirect_to users_path, notice: 'Upload success'
153
+ end
154
+ end
155
+ end
156
+
157
+ csv_import is be able to use block.
158
+
159
+ User.csv_import(params[:file]) do |user, params, row_number|
160
+ next false if row_number == 2 # 'next false' in the block, the line is skipped.
161
+ end
162
+
163
+ csv_import use transaction, if invalid line is existed then all rows is not imported.
164
+
165
+ First line used for fields, but you can manually choose it using options
166
+
167
+ User.csv_import(params[:file], fields: [:age, :name])
168
+
169
+ It line first line has 'id', csv_import call where(id: id).first_or_initialize, but you can use other field name
170
+
171
+ User.csv_import(params[:file], find_key: :name)
172
+ #=> It call User.where(name: name).first_or_initialize internal.
173
+
174
+ Copyright (c) 2012-2013 yalab, released under the MIT license
@@ -1,5 +1,6 @@
1
1
  require 'csv_rails/array'
2
2
  require 'csv_rails/active_model'
3
+ require 'csv_rails/import'
3
4
 
4
5
  Array.send(:include, CsvRails::Array)
5
6
 
@@ -5,7 +5,12 @@ module CsvRails
5
5
  def to_csv(opts={})
6
6
  fields = opts[:fields] || csv_fields
7
7
  header = csv_header(fields, opts.delete(:i18n_scope))
8
- all.to_a.to_csv(opts.update(:fields => fields, :header => header))
8
+ all = if self.respond_to?(:to_a)
9
+ to_a
10
+ else
11
+ send(:all).to_a
12
+ end
13
+ all.to_csv(opts.update(:fields => fields, :header => header))
9
14
  end
10
15
 
11
16
  def csv_header(fields, scope=nil)
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ module CsvRails::Import
3
+ extend ActiveSupport::Concern
4
+ module ClassMethods
5
+ def inverse_human_attriute_name(attribute, options = {})
6
+ defaults = options[:defaults] || []
7
+ parts = attribute.to_s.split(".")
8
+ attribute = parts.pop
9
+ namespace = parts.join("/") unless parts.empty?
10
+
11
+ if defaults.blank?
12
+ if namespace
13
+ lookup_ancestors.each do |klass|
14
+ defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}"
15
+ end
16
+ defaults << :"#{self.i18n_scope}.attributes.#{namespace}"
17
+ else
18
+ lookup_ancestors.each do |klass|
19
+ defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}"
20
+ end
21
+ end
22
+ defaults << :"attributes"
23
+ end
24
+ entry = nil
25
+ defaults.each do |k|
26
+ map = I18n.translate(k)
27
+ if map.is_a?(Hash)
28
+ entry = map.invert[attribute]
29
+ break
30
+ end
31
+ end
32
+ entry || attribute.underscore.gsub(/ /, '_')
33
+ end
34
+
35
+ def csv_import(body, opts={})
36
+ fields = opts.delete(:fields)
37
+ find_key = opts.has_key?(:find_key) ? opts.delete(:find_key) : :id
38
+ records = []
39
+ all_green = true
40
+ translated = false
41
+ self.transaction do
42
+ CSV.parse(body, opts).each.with_index do |row, i|
43
+ unless fields
44
+ fields = row
45
+ next
46
+ end
47
+ unless translated
48
+ fields.map!{|f| inverse_human_attriute_name(f) }
49
+ translated = true
50
+ end
51
+ attributes = ActiveSupport::HashWithIndifferentAccess.new(Hash[fields.zip(row)])
52
+ record = if find_key
53
+ self.where(:"#{find_key}" => attributes[find_key.to_s]).first_or_initialize
54
+ else
55
+ self.new
56
+ end
57
+ record.attributes = attributes.select{|k, v| self.attribute_method?(k) }
58
+ if block_given?
59
+ val = yield record, attributes, i
60
+ next if val == false
61
+ end
62
+ if all_green && record.valid?
63
+ record.save!
64
+ else
65
+ all_green = false
66
+ end
67
+ records << record
68
+ end
69
+ raise ActiveRecord::Rollback unless all_green
70
+ end
71
+ records
72
+ end
73
+
74
+ def tsv_import(body, opts={}, &block)
75
+ csv_import(body, opts.merge(col_sep: "\t"), &block)
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,3 @@
1
1
  module CsvRails
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -2,6 +2,7 @@
2
2
  require 'test_helper'
3
3
  class CsvRails::ActiveRecordTest < ActiveSupport::TestCase
4
4
  setup do
5
+ I18n.locale = :en
5
6
  @user = User.create(:name => 'yalab', :age => '29', :secret => 'password')
6
7
  @group = Group.create(:name => 'ruby')
7
8
  @user.groups << @group
@@ -95,6 +96,6 @@ class CsvRails::ActiveRecordTest < ActiveSupport::TestCase
95
96
  end
96
97
 
97
98
  test ".to_csv with row_sep option" do
98
- assert_match /\r\n/, User.all.to_csv(:row_sep => "\r\n")
99
+ assert_match /\r\n/, User.all.to_a.to_csv(:row_sep => "\r\n")
99
100
  end
100
101
  end
@@ -53,12 +53,12 @@ class CsvRails::ArrayTest < ActiveSupport::TestCase
53
53
  test ".to_csv only it includes Mongoid instance" do
54
54
  post = Post.create(:title => 'this is csv_rails', :body => "line\nline\nline\n")
55
55
 
56
- assert_equal " type,\"\",タイトル,本文\n#{post._type},#{post._id},#{post.title},\"#{post.body}\"\n", [post].to_csv
56
+ assert_equal "\"\",タイトル,本文\n#{post._id},#{post.title},\"#{post.body}\"\n", [post].to_csv
57
57
  end
58
58
 
59
59
  test ".to_tsv only it includes Mongoid instance" do
60
60
  post = Post.create(:title => 'this is csv_rails', :body => "line\nline\nline\n")
61
61
 
62
- assert_equal " type\t\"\"\tタイトル\t本文\n#{post._type}\t#{post._id}\t#{post.title}\t\"#{post.body}\"\n", [post].to_tsv
62
+ assert_equal "\"\"\tタイトル\t本文\n#{post._id}\t#{post.title}\t\"#{post.body}\"\n", [post].to_tsv
63
63
  end
64
64
  end
@@ -0,0 +1,134 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class CsvRails::ImportTest < ActiveSupport::TestCase
5
+ setup do
6
+ I18n.locale = :en
7
+ @users = [{id: 1, name: 'yoshida', age: 30, secret: 'password'},
8
+ {id: 2, name: 'yalab', age: 3, secret: 'password'}]
9
+ csv = <<-EOS.gsub(/^\s+/, '')
10
+ id,name,age,secret
11
+ #{@users[0].values.join(',')}
12
+ #{@users[1].values.join(',')}
13
+ EOS
14
+ @csv = StringIO.new(csv)
15
+ end
16
+
17
+ test "import IO" do
18
+ User.csv_import(@csv)
19
+ @users.each do |params|
20
+ user = User.find(params[:id])
21
+ params.each do |k, v|
22
+ assert_equal v, user[k]
23
+ end
24
+ end
25
+ end
26
+
27
+ test "import with block" do
28
+ name = 'test'
29
+ User.csv_import(@csv) do |user, params|
30
+ if params[:id] == '1'
31
+ user.name = name
32
+ end
33
+ end
34
+ user = User.find(1)
35
+ assert_equal name, user.name
36
+ end
37
+
38
+ test "import with fields option" do
39
+ user = {name: 'atsushi', age: 14}
40
+ csv = <<-EOS.gsub(/^\s*/, '')
41
+ #{user.values.join(',')}
42
+ EOS
43
+ users = User.csv_import(StringIO.new(csv), fields: user.keys)
44
+ user.each do |k, v|
45
+ assert_equal v, users.first[k]
46
+ end
47
+ end
48
+
49
+ test "import skip blank line" do
50
+ csv = <<-EOS.gsub(/^\s*/, '')
51
+ name,age
52
+
53
+ yoshida,30
54
+ EOS
55
+ User.csv_import(csv)
56
+ assert_equal 1, User.count
57
+ end
58
+
59
+ test "import skip if block returns false" do
60
+ User.csv_import(@csv) do |user|
61
+ next false if user.id == 2
62
+ end
63
+ assert_nil User.find_by_id(2)
64
+ end
65
+
66
+ test "import is transaction" do
67
+ User.csv_import(@csv) do |user, attributes, index|
68
+ raise ActiveRecord::Rollback if index == 2
69
+ end
70
+ assert_nil User.find_by_id(1)
71
+ end
72
+
73
+ test "includes invalid attributes" do
74
+ csv =<<-EOS.gsub(/^\s*/, '')
75
+ name,age
76
+ ,30
77
+ yoshida,12
78
+ EOS
79
+ users = User.csv_import(csv)
80
+ assert_equal 0, User.count
81
+ assert_equal ["Name can't be blank"], users[0].errors.full_messages
82
+ end
83
+
84
+ test "import can accept not a column" do
85
+ csv =<<-EOS.gsub(/^\s*/, '')
86
+ name,age,group_name
87
+ yoshida,12,human
88
+ EOS
89
+ assert_difference("User.count") do
90
+ User.csv_import(csv) do |user, attributes|
91
+ assert_equal 'human', attributes[:group_name]
92
+ end
93
+ end
94
+ end
95
+
96
+ test "it also use tsv_import" do
97
+ secret = 'hogehoge'
98
+ User.tsv_import(@csv.read.gsub(/,/, "\t")) do |user, params, ix|
99
+ user.secret = secret if user.name == 'yalab'
100
+ end
101
+ @users.each do |params|
102
+ user = User.find_by_id(params[:id])
103
+ params.each do |k, v|
104
+ if k == :secret && user.name == 'yalab'
105
+ v = secret
106
+ end
107
+ assert_equal v, user[k]
108
+ end
109
+ end
110
+ end
111
+
112
+ test "inverse_human_attriute_name" do
113
+ I18n.locale = :ja
114
+ assert_equal :name, User.inverse_human_attriute_name("名前")
115
+ end
116
+
117
+ test "csv download upload" do
118
+ I18n.locale = :ja
119
+ user = User.create(name: 'foobar', age: 20)
120
+ User.csv_import(User.to_csv.gsub('foobar', 'hogehoge'))
121
+ assert_equal 'hogehoge', user.reload.name
122
+ end
123
+
124
+ test "import can choose key" do
125
+ csv =<<-EOS.gsub(/^\s*/, '')
126
+ name,age,group_name
127
+ yoshida,12,human
128
+ yoshida,15,human
129
+ EOS
130
+ User.csv_import(csv, find_key: :name)
131
+ assert_equal 1, User.count
132
+ assert_equal 15, User.find(1).age
133
+ end
134
+ end
@@ -0,0 +1,17 @@
1
+ class GroupsController < ApplicationController
2
+ def create
3
+ user_prefix = /^user_/
4
+ @groups = Group.csv_import(params[:group][:file], find_key: :name) do |group, _params, i|
5
+ user_params = _params.select{|k, v| k =~ user_prefix }.to_a.inject({}){|hash, (k, v)|
6
+ hash[k.gsub(user_prefix, '')] = v
7
+ hash
8
+ }
9
+ user_params
10
+ user = User.where(name: user_params['name']).first_or_initialize
11
+ user.attributes = user_params
12
+ group.users << user
13
+ end
14
+
15
+ render text: nil
16
+ end
17
+ end
@@ -9,7 +9,7 @@ class UsersController < ApplicationController
9
9
  end
10
10
 
11
11
  def sjis
12
- @users = User.includes(:groups).all
12
+ @users = User.includes(:groups).to_a
13
13
  respond_to do |format|
14
14
  format.html
15
15
  format.csv { render csv: @users, fields: [:id, :name, :age, :"groups.first.name"], encoding: 'SJIS' }
@@ -0,0 +1,2 @@
1
+ module GroupsHelper
2
+ end
@@ -1,6 +1,7 @@
1
1
  class Group < ActiveRecord::Base
2
2
  has_many :memberships
3
3
  has_many :users, through: :memberships
4
+ include CsvRails::Import
4
5
 
5
6
  def created_at_as_csv
6
7
  created_at.strftime("%F")
@@ -1,6 +1,8 @@
1
1
  class User < ActiveRecord::Base
2
2
  has_many :memberships
3
3
  has_many :groups, through: :memberships
4
+ validates :name, presence: :true
5
+ include CsvRails::Import
4
6
 
5
7
  def updated_at_as_csv
6
8
  self.updated_at.strftime("%F %H:%M")
@@ -1,25 +1,13 @@
1
- # SQLite version 3.x
2
- # gem install sqlite3
3
- #
4
- # Ensure the SQLite 3 gem is defined in your Gemfile
5
- # gem 'sqlite3'
6
- development:
1
+ default: &default
7
2
  adapter: sqlite3
8
- database: db/development.sqlite3
9
3
  pool: 5
10
4
  timeout: 5000
11
5
 
12
- # Warning: The database defined as "test" will be erased and
13
- # re-generated from your development database when you run "rake".
14
- # Do not set this db to the same as development or production.
6
+ development:
7
+ <<: *default
8
+ database: db/development.sqlite3
9
+
15
10
  test:
16
- adapter: sqlite3
17
- database: db/test.sqlite3
18
- pool: 5
19
- timeout: 5000
11
+ <<: *default
12
+ database: ":memory:"
20
13
 
21
- production:
22
- adapter: sqlite3
23
- database: db/production.sqlite3
24
- pool: 5
25
- timeout: 5000