csv_rails 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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