flextures 2.1.0 → 3.0.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.
@@ -1,16 +1,27 @@
1
1
  # encoding: utf-8
2
2
 
3
- # 基本設定を記述する
3
+ # base configurations
4
+ # if you want to change setting, create 'config/flextures.config.rb', and overwrite setting
5
+ #
6
+ # example:
7
+ # module Flextures
8
+ # # load and dump directroy setting change "spec/fixtures/" to "test/fixtures/"
9
+ # Config.fixture_load_directory = "test/fixtures/"
10
+ # Config.fixture_dump_directory = "test/fixtures/"
11
+ # end
12
+ #
4
13
  module Flextures
5
14
  module Config
6
15
  @@read_onlys=[]
7
16
  @@configs={
8
- fixture_load_directory: "spec/fixtures/",
9
- fixture_dump_directory: "spec/fixtures/",
10
- init_all_tables: false, # 実行後に全テーブルの初期化を行うか?falseにするとそのぶん高速化できる
11
- ignore_tables: ["schema_migrations"],
17
+ ignore_tables: ["schema_migrations"], # 'ignore_tables' table data is not deleted by flextures delete_all method
18
+ fixture_load_directory: "spec/fixtures/", # base load directory
19
+ fixture_dump_directory: "spec/fixtures/", # dump load directory
20
+ init_all_tables: false, # if this option is 'true', when start unit test, all table data is delete
21
+ options: {}, # options(example { unfilter: true })
22
+ table_load_order: [], # set load options
12
23
  }
13
- # ハッシュをsetter、getterに変換
24
+ # hash key change to getter and setter
14
25
  class<< self
15
26
  @@configs.each do |setting_key, setting_value|
16
27
  define_method(setting_key){ @@configs[setting_key] }
@@ -15,49 +15,47 @@ module Flextures
15
15
  Flextures::init_load
16
16
  table_names = Flextures::ARGS.parse
17
17
  puts "dumping..."
18
- if ["yml","yaml"].member? ENV["FORMAT"]
18
+ case ENV["FORMAT"].to_s.to_sym
19
+ when :yml,:yaml
19
20
  table_names.map { |fmt| Flextures::Dumper::yml(fmt) }
21
+ when :csv
22
+ table_names.map { |fmt| Flextures::Dumper::csv(fmt) }
20
23
  else
21
24
  table_names.map { |fmt| Flextures::Dumper::csv(fmt) }
22
25
  end
23
26
  end
24
27
 
25
- def self.csvdump
26
- Flextures::init_load
27
- puts "dumping..."
28
- table_names = Flextures::ARGS.parse
29
- table_names.map { |fmt| Flextures::Dumper::csv(fmt) }
30
- end
31
-
32
- def self.ymldump
33
- Flextures::init_load
34
- puts "dumping..."
35
- table_names = Flextures::ARGS.parse
36
- table_names.map { |fmt| Flextures::Dumper::yml(fmt) }
37
- end
38
-
39
28
  def self.load
40
29
  Flextures::init_load
41
30
  table_names = Flextures::ARGS.parse
42
31
  Flextures::init_tables unless ENV["T"] or ENV["TABLE"] or ENV["M"] or ENV["MODEL"] or ENV["F"] or ENV["FIXTUES"]
32
+ file_format = ENV["FORMAT"]
43
33
  puts "loading..."
44
- table_names.map { |fmt| Flextures::Loader::load(fmt) }
34
+ case file_format.to_s.to_sym
35
+ when :csv
36
+ table_names.map { |fmt| Flextures::Loader::csv(fmt) }
37
+ when :yml
38
+ table_names.map { |fmt| Flextures::Loader::yml(fmt) }
39
+ else
40
+ table_names.map { |fmt| Flextures::Loader::load(fmt) }
41
+ end
45
42
  end
46
43
 
47
- def self.csvload
44
+ # load and dump data
45
+ def self.generate
48
46
  Flextures::init_load
49
47
  table_names = Flextures::ARGS.parse
50
48
  Flextures::init_tables unless ENV["T"] or ENV["TABLE"] or ENV["M"] or ENV["MODEL"] or ENV["F"] or ENV["FIXTUES"]
51
- puts "loading..."
52
- table_names.map { |fmt| Flextures::Loader::csv(fmt) }
53
- end
54
-
55
- def self.ymlload
56
- Flextures::init_load
57
- table_names = Flextures::ARGS::parse
58
- Flextures::init_tables unless ENV["T"] or ENV["TABLE"] or ENV["M"] or ENV["MODEL"] or ENV["F"] or ENV["FIXTUES"]
59
- puts "loading..."
60
- table_names.map { |fmt| Flextures::Loader::yml(fmt) }
49
+ file_format = ENV["FORMAT"]
50
+ puts "generating..."
51
+ case file_format.to_s.to_sym
52
+ when :yml
53
+ table_names.map { |fmt| Flextures::Loader::yml(fmt); Flextures::Dumper::yml(fmt) }
54
+ when :csv
55
+ table_names.map { |fmt| Flextures::Loader::csv(fmt); Flextures::Dumper::csv(fmt) }
56
+ else
57
+ table_names.map { |fmt| Flextures::Loader::csv(fmt); Flextures::Dumper::csv(fmt) }
58
+ end
61
59
  end
62
60
  end
63
61
  end
@@ -1,14 +1,16 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Flextures
4
- # データを吐き出す処理をまとめる
6
+ # defined data dump methods
5
7
  module Dumper
6
8
  PARENT = Flextures
7
9
  # procに機能追加、関数合成のためのメソッドを追加する
8
10
  class Proc < ::Proc
9
11
  def *(other)
10
12
  if self.lambda? and other.lambda?
11
- lambda {|*x| other.call(self.call(*x)) }
13
+ lambda { |*x| other.call(self.call(*x)) }
12
14
  elsif not self.lambda? and not other.lambda?
13
15
  Proc.new {|*x| other.call(self.call(*x)) }
14
16
  else
@@ -21,8 +23,9 @@ module Flextures
21
23
  Proc.new(&b)
22
24
  end
23
25
 
26
+ # create data translater
24
27
  def self.translate_creater( val, rules )
25
- rule_map ={
28
+ rule_map = {
26
29
  nullstr: proc { |d|
27
30
  return "null" if d.nil?
28
31
  d
@@ -77,6 +80,7 @@ module Flextures
77
80
  procs.call(val)
78
81
  end
79
82
 
83
+ # data translaters
80
84
  TRANSLATER = {
81
85
  binary:->( d, format ){
82
86
  procs = (format == :yml)?
@@ -144,84 +148,107 @@ module Flextures
144
148
  [proc { |d| d.to_s }]
145
149
  self.translate_creater d, procs
146
150
  },
151
+ # use null only value
152
+ null:->( d, format ){
153
+ format==:yml ? "null" : ""
154
+ },
147
155
  }
148
156
 
149
- # 適切な型に変換
157
+ # translate data
158
+ # @params [Object] value
159
+ # @params [Symbol] type datatype
160
+ # @params [Symbol] format data type (:yml or :csv)
161
+ # @return translated value
150
162
  def self.trans( v, type, format )
151
163
  trans = TRANSLATER[type]
152
164
  return trans.call( v, format ) if trans
153
165
  v
154
166
  end
155
167
 
156
- # csv fixtures を dump
168
+ # dump attributes data
169
+ # @params klass dump table Model
170
+ # @params [Hash] options dump options
171
+ # @return [Array] columns format information
172
+ def self.dump_attributes klass, options
173
+ columns = klass.columns.map { |column| { name: column.name, type: column.type } }
174
+ # option[:minus] colum is delete columns
175
+ columns.reject! { |column| options[:minus].include?(column[:name]) } if options[:minus]
176
+ # option[:plus] colum is new columns
177
+ # values is all nil
178
+ plus = options[:plus].to_a.map { |colname| { name: colname, type: :null } }
179
+ columns + plus
180
+ end
181
+
182
+ # filter is translate value safe YAML or CSV string
183
+ # @params [Class] klass ActiveRecord class
184
+ # @params [String] table_name table name
185
+ # @params [Hash] options options
186
+ # @params [Symbol] type format type (:yml or :csv)
187
+ # @return [Proc] filter function
188
+ def self.create_filter attr_type, format, type
189
+ filter = DumpFilter[format[:table].to_s.to_sym] || {}
190
+ ->(row) {
191
+ attr_type.map do |h|
192
+ v = filter[h[:name].to_sym] ? filter[h[:name].to_sym].call(row[h[:name]]) : trans(row[h[:name]], h[:type], type)
193
+ [h[:name],v]
194
+ end
195
+ }
196
+ end
197
+
198
+ # data dump to csv format
199
+ # @params [Hash] format file format data
200
+ # @params [Hash] options dump format options
157
201
  def self.csv format
202
+ klass = PARENT.create_model(format[:table])
203
+ attr_type = self.dump_attributes klass, format
204
+ filter = self.create_filter attr_type, format, :csv
205
+ self.dump_csv klass, attr_type, filter, format
206
+ end
207
+
208
+ # dump csv format data
209
+ def self.dump_csv klass, attr_type, values_filter, format
210
+ # TODO: 拡張子は指定してもしなくても良いようにする
158
211
  file_name = format[:file] || format[:table]
159
- dir_name = format[:dir] || DUMP_DIR
160
- # 指定されたディレクトリを作成
161
- recursive_mkdir(dir_name)
212
+ dir_name = File.join( Flextures::Config.fixture_dump_directory, format[:dir].to_s )
213
+ FileUtils.mkdir_p(dir_name)
162
214
  outfile = File.join(dir_name, "#{file_name}.csv")
163
- table_name = format[:table]
164
- klass = PARENT.create_model(table_name)
165
- attributes = klass.columns.map { |column| column.name }
166
- filter = DumpFilter[table_name]
167
215
  CSV.open(outfile,'w') do |csv|
168
- attr_type = klass.columns.map { |column| { name: column.name, type: column.type } }
169
- csv<< attributes
216
+ # dump column names
217
+ csv<< attr_type.map { |h| h[:name].to_s }
218
+ # dump column datas
170
219
  klass.all.each do |row|
171
- csv<< attr_type.map do |h|
172
- if (filter && filter[h[:name].to_sym])
173
- filter[h[:name].to_sym].call(row[h[:name]])
174
- else
175
- trans(row[h[:name]], h[:type], :csv)
176
- end
177
- end
220
+ csv<< values_filter.call(row).map(&:last)
178
221
  end
179
222
  end
180
223
  outfile
181
224
  end
182
225
 
183
- # yaml fixtures dump
226
+ # data dump to yaml format
227
+ # @params [Hash] format file format data
228
+ # @params [Hash] options dump format options
184
229
  def self.yml format
230
+ klass = PARENT::create_model(format[:table])
231
+ attr_type = self.dump_attributes klass, format
232
+ filter = self.create_filter attr_type, format, :yml
233
+ self.dump_yml klass, filter, format
234
+ end
235
+
236
+ # dump yml format data
237
+ def self.dump_yml klass, values_filter, format
238
+ # TODO: 拡張子は指定してもしなくても良いようにする
239
+ table_name = format[:table]
185
240
  file_name = format[:file] || format[:table]
186
- dir_name = format[:dir] || DUMP_DIR
187
- # 指定されたディレクトリを作成
188
- recursive_mkdir(dir_name)
241
+ dir_name = File.join( Flextures::Config.fixture_dump_directory, format[:dir].to_s )
242
+ FileUtils.mkdir_p(dir_name)
189
243
  outfile = File.join(dir_name, "#{file_name}.yml")
190
- table_name = format[:table]
191
- klass = PARENT::create_model(table_name)
192
- attributes = klass.columns.map { |colum| colum.name }
193
- columns = klass.columns
194
- # テーブルからカラム情報を取り出し
195
- column_hash = {}
196
- columns.each { |col| column_hash[col.name] = col }
197
- # 自動補完が必要なはずのカラム
198
- lack_columns = columns.select { |c| !c.null and !c.default }.map{ |o| o.name.to_sym }
199
- not_nullable_columns = columns.select { |c| !c.null }.map &:name
200
-
201
- filter = DumpFilter[table_name]
202
244
  File.open(outfile,"w") do |f|
203
- klass.all.each_with_index do |row,idx|
204
- f<< "#{table_name}_#{idx}:\n" +
205
- klass.columns.map { |column|
206
- colname, coltype = column.name, column.type
207
- if (filter && filter[colname.to_sym])
208
- v = filter[colname.to_sym].call(row[colname.to_sym])
209
- else
210
- v = trans(row[colname], coltype, :yml)
211
- end
212
- " #{colname}: #{v}\n"
213
- }.join
245
+ klass.all.each.with_index do |row,idx|
246
+ values = values_filter.call(row).map { |k,v| " #{k}: #{v}\n" }.join
247
+ f<< "#{table_name}_#{idx}:\n" + values
214
248
  end
215
249
  end
216
250
  outfile
217
251
  end
218
-
219
- def self.recursive_mkdir(path)
220
- return if FileTest.exist?(path)
221
- dir = File.dirname(path)
222
- recursive_mkdir(dir)
223
- Dir.mkdir(path)
224
- end
225
252
  end
226
253
  end
227
254
 
@@ -3,25 +3,29 @@
3
3
  require 'ostruct'
4
4
 
5
5
  module Flextures
6
- # Plug-in 内部拡張
6
+ # OpenStruct hack in flextures Plug-in
7
7
  class OpenStruct < ::OpenStruct
8
- # hashに変化させる
8
+ # Struct Data translate to Hash
9
9
  def to_hash
10
- h={}
11
10
  (self.methods - ::OpenStruct.new.methods)
12
11
  .select{ |name| name.to_s.match(/\w+=/) }
13
12
  .map{ |name| name.to_s.gsub(/=/,'').to_sym }
14
- .each{ |k| h[k]=self.send(k) }
15
- h
13
+ .inject({}){ |k,h| h[k]=self.send(k); h }
16
14
  end
17
15
  end
18
16
 
19
17
  module Extensions
20
18
  module Array
19
+ # use Object#extend
20
+ # @params [Array] keys hash keys
21
+ # @return [Hash] tanslated Hash data
22
+ # example:
23
+ # hash = array.extend(Extensions::Array).to_hash(keys)
21
24
  def to_hash keys
22
- h = {}
23
- [keys,self].transpose.each{ |k,v| h[k]=v }
24
- h
25
+ values = self
26
+ values = values[0..keys.size-1] if keys.size < values.size
27
+ values = values+[nil]*(keys.size-values.size) if keys.size > values.size
28
+ [keys,values].transpose.inject({}){ |h,pair| k,v=pair; h[k]=v; h }
25
29
  end
26
30
  end
27
31
  end
@@ -1,16 +1,15 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Flextures
4
- # ロードするデータを必要に応じて加工する
4
+ # Flextures FactoryFilter is program to translate ActiveRecord data
5
5
  class LoadFilter
6
- # 設置ファイルから取得した Factoryの一覧を取得
6
+ # FactoryFilter data
7
7
  FACTORIES={}
8
8
 
9
- # Factory を定義
10
- # @params table_name
11
- # @params options
12
- # @params block
13
- # @return Flextures::Factory
9
+ # set FactoryFilter
10
+ # @params [String] table_name
11
+ # @params [Array] options arguments ActiveRecord Model
12
+ # @params [Proc] block FactoryFilter
14
13
  def self.define table_name, *options, &block
15
14
  h={ block: block }
16
15
  options.each do |o|
@@ -22,18 +21,21 @@ module Flextures
22
21
  FACTORIES[table_name.to_sym]=h
23
22
  end
24
23
 
25
- # Factoryを取得
24
+ # get FactoryFilter
25
+ # @params [String|Symbol] table_name
26
+ # @return [Proc] filter block
26
27
  def self.get table_name
27
28
  f = FACTORIES[table_name.to_sym]
28
29
  f && f[:block]
29
30
  end
30
31
  def self.[](table_name); self.get(table_name); end
31
32
  end
33
+
32
34
  class DumpFilter
33
- # 設置ファイルから取得した Factoryの一覧を取得
35
+ # FactoryDumpFilter data
34
36
  FACTORIES={}
35
37
 
36
- # Factory を定義
38
+ # set FactoryFilter
37
39
  # @params table_name
38
40
  # @params options
39
41
  # @params block
@@ -42,7 +44,7 @@ module Flextures
42
44
  FACTORIES[table_name.to_sym]=hash
43
45
  end
44
46
 
45
- # Factoryを取得
47
+ # get FactoryFilter
46
48
  def self.get table_name
47
49
  FACTORIES[table_name.to_sym]
48
50
  end
@@ -9,11 +9,11 @@ require 'flextures/flextures'
9
9
  require 'flextures/flextures_factory'
10
10
 
11
11
  module Flextures
12
- # Dumperと違ってデータの吐き出し処理をまとめたクラス
12
+ # data loader
13
13
  module Loader
14
14
  PARENT = Flextures
15
15
 
16
- @@table_cache = {}
16
+ @@option_cache = {}
17
17
 
18
18
  # column set default value
19
19
  COMPLETER = {
@@ -38,7 +38,7 @@ module Flextures
38
38
  },
39
39
  boolean:->(d){
40
40
  return d if d.nil?
41
- (0==d || ""==d || !d) ? false : true
41
+ !(0==d || ""==d || !d)
42
42
  },
43
43
  date:->(d){
44
44
  return d if d.nil?
@@ -51,27 +51,23 @@ module Flextures
51
51
  DateTime.parse(d.to_s)
52
52
  },
53
53
  decimal:->(d){
54
- return d if d.nil?
54
+ return d if d.nil?
55
55
  d.to_i
56
56
  },
57
57
  float:->(d){
58
- return d if d.nil?
58
+ return d if d.nil?
59
59
  d.to_f
60
60
  },
61
61
  integer:->(d){
62
- return d if d.nil?
62
+ return d if d.nil?
63
63
  d.to_i
64
64
  },
65
65
  string:->(d){
66
- return d if d.nil?
67
- return d if d.is_a?(Hash)
68
- return d if d.is_a?(Array)
66
+ return d if d.nil? or d.is_a?(Hash) or d.is_a?(Array)
69
67
  d.to_s
70
68
  },
71
69
  text:->(d){
72
- return d if d.nil?
73
- return d if d.is_a?(Hash)
74
- return d if d.is_a?(Array)
70
+ return d if d.nil? or d.is_a?(Hash) or d.is_a?(Array)
75
71
  d.to_s
76
72
  },
77
73
  time:->(d){
@@ -86,163 +82,243 @@ module Flextures
86
82
  },
87
83
  }
88
84
 
89
- # どのファイルが存在するかチェック
90
- # @param [Hash] format ロードしたいファイルの情報
91
- # @return 存在するファイルの種類(csv,yml)、どちも存在しないならnil
92
- def self.file_exist format, type = [:csv,:yml]
93
- table_name = format[:table].to_s
94
- file_name = format[:file] || format[:table]
95
- dir_name = format[:dir] || LOAD_DIR
96
-
97
- ext=->{
98
- if type.member?(:csv) and File.exist? "#{dir_name}#{file_name}.csv"
99
- :csv
100
- elsif type.member?(:yml) and File.exist? "#{dir_name}#{file_name}.yml"
101
- :yml
102
- else
103
- nil
104
- end
105
- }.call
106
-
107
- [table_name, "#{dir_name}#{file_name}",ext]
108
- end
109
-
110
- # flextures関数の引数をパースして
111
- # 単純な読み込み向け形式に変換します
85
+ # load fixture datas
112
86
  #
113
- # @params [Hash] 読み込むテーブルとファイル名のペア
114
- # @return [Array] 読み込テーブルごとに切り分けられた設定のハッシュを格納
115
- def self.parse_flextures_options *fixtures
116
- options = {}
117
- options = fixtures.shift if fixtures.size > 1 and fixtures.first.is_a?(Hash)
87
+ # example:
88
+ # flextures :all # load all table data
89
+ # flextures :users, :items # load table data, received arguments
90
+ # flextures :users => :users2 # :table_name => :file_name
91
+ #
92
+ # @params [Hash] fixtures load table data
93
+ def self.flextures *fixtures
94
+ load_list = parse_flextures_options(*fixtures)
95
+ load_list.sort(&self.loading_order).each{ |params| Loader::load params }
96
+ end
118
97
 
119
- # :allですべてのfixtureを反映
120
- fixtures = Flextures::deletable_tables if fixtures.size== 1 and :all == fixtures.first
98
+ # @return [Proc] order rule block (user Array#sort methd)
99
+ def self.loading_order
100
+ ->(a,b){
101
+ a = Flextures::Config.table_load_order.index(a) || -1
102
+ b = Flextures::Config.table_load_order.index(b) || -1
103
+ b <=> a
104
+ }
105
+ end
121
106
 
122
- last_hash = {}
123
- last_hash = fixtures.pop if fixtures.last.is_a? Hash
107
+ # called by Rspec or Should
108
+ # set options
109
+ # @params [Hash] options exmple : { cashe: true, dir: "models/users" }
110
+ def self.set_options options
111
+ @@option_cache ||= {}
112
+ @@option_cache.merge!(options)
113
+ end
124
114
 
125
- load_hash = fixtures.inject({}){ |h,name| h[name.to_sym] = name; h }
126
- load_hash.merge!(last_hash)
127
- load_hash = load_hash.map { |k,v| { table: k, file: v, loader: :fun } }
128
- [load_hash, options]
115
+ # called by Rspec or Should after filter
116
+ # reflesh options
117
+ def self.delete_options
118
+ @@option_cache = {}
129
119
  end
130
120
 
131
- # fixturesをまとめてロード、主にテストtest/unit, rspec で使用する
132
- #
133
- # 全テーブルが対象
134
- # flextures :all
135
- # テーブル名で一覧する
136
- # flextures :users, :items
137
- # ハッシュで指定
138
- # flextures :users => :users2
139
- #
140
- # @params [Hash] 読み込むテーブルとファイル名のペア
141
- def self.flextures *fixtures
142
- load_hash, options = parse_flextures_options(*fixtures)
143
- load_hash.each{ |params| Loader::load params }
121
+ # return current option status
122
+ # @return [Hash] current option status
123
+ def self.flextures_options
124
+ @@option_cache
144
125
  end
145
126
 
146
- # csv 優先で存在している fixtures をロード
147
- def self.load format, options = {}
148
- table_name, file_name, method = file_exist format
127
+ # load fixture data
128
+ # fixture file prefer YAML to CSV
129
+ # @params [Hash] format file load format(table name, file name, options...)
130
+ def self.load format
131
+ file_name, method = file_exist format
149
132
  if method
150
- send(method, format, options)
133
+ send(method, format)
151
134
  else
152
- # ファイルが存在しない時
153
- print "Warning: #{file_name} is not exist!\n"
135
+ puts "Warning: #{file_name} is not exist!" unless format[:silent]
154
136
  end
155
137
  end
156
138
 
157
- # CSVのデータをロードする
158
- def self.csv format, options={}
159
- table_name, file_name, ext = file_exist format, [:csv]
139
+ # load CSV data
140
+ # @params [Hash] format file load format(table name, file name, options...)
141
+ def self.csv format
142
+ type = :csv
143
+ file_name, ext = file_exist format, [type]
144
+ return unless self.file_loadable? format, file_name
145
+ klass, filter = self.create_model_filter format, file_name, type
146
+ self.load_csv format, klass, filter, file_name
147
+ end
160
148
 
161
- # キャッシュ利用可能ならそれをそのまま使う
162
- return if options[:cache] and @@table_cache[table_name.to_sym] and @@table_cache[table_name.to_sym] == file_name
163
- @@table_cache[table_name.to_sym] = file_name
149
+ # load YAML data
150
+ # @params [Hash] format file load format( table: name, file: name, options...)
151
+ def self.yml format
152
+ type = :yml
153
+ file_name, ext = file_exist format, [type]
164
154
 
165
- print "try loading #{file_name}.csv\n" unless [:fun].include? format[:loader]
166
- return nil unless File.exist? "#{file_name}.csv"
155
+ return unless self.file_loadable? format, file_name
167
156
 
168
- klass = PARENT::create_model table_name
169
- attributes = klass.columns.map &:name
170
- filter = create_filter klass, LoadFilter[table_name], file_name, :csv
171
- # rails3_acts_as_paranoid がdelete_allで物理削除しないことの対策
172
- klass.send( klass.respond_to?(:delete_all!) ? :delete_all! : :delete_all )
157
+ klass, filter = self.create_model_filter format, file_name, type
158
+ self.load_yml format, klass, filter, file_name
159
+ end
173
160
 
174
- CSV.open( "#{file_name}.csv" ) do |csv|
175
- keys = csv.shift # keyの設定
176
- warning "CSV", attributes, keys
161
+ def self.load_csv format, klass, filter, file_name
162
+ attributes = klass.columns.map &:name
163
+ CSV.open( file_name ) do |csv|
164
+ keys = csv.shift # active record column names
165
+ warning "CSV", attributes, keys unless format[:silent]
177
166
  csv.each do |values|
178
167
  h = values.extend(Extensions::Array).to_hash(keys)
179
- o = filter.call h
180
- o.save( validate: false )
168
+ filter.call h
181
169
  end
182
170
  end
183
- "#{file_name}.csv"
171
+ file_name
184
172
  end
185
173
 
186
- # YAML形式でデータをロードする
187
- def self.yml format, options={}
188
- table_name, file_name, ext = file_exist format, [:yml]
174
+ def self.load_yml format, klass, filter, file_name
175
+ yaml = YAML.load File.open(file_name)
176
+ return false unless yaml # if file is empty
177
+ attributes = klass.columns.map &:name
178
+ yaml.each do |k,h|
179
+ warning "YAML", attributes, h.keys unless format[:silent]
180
+ filter.call h
181
+ end
182
+ file_name
183
+ end
184
+
185
+ # if parameter include controller, action value
186
+ # load directroy is change
187
+ # spec/fixtures/:controller_name/:action_name/
188
+ # @return [String] directory path
189
+ def self.parse_controller_option options
190
+ controller_dir = ["controllers"]
191
+ controller_dir<< options[:controller] if options[:controller]
192
+ controller_dir<< options[:action] if options[:controller] and options[:action]
193
+ File.join(*controller_dir)
194
+ end
195
+
196
+ # if parameter include controller, action value
197
+ # load directroy is change
198
+ # spec/fixtures/:model_name/:method_name/
199
+ # @return [String] directory path
200
+ def self.parse_model_options options
201
+ model_dir = ["models"]
202
+ model_dir<< options[:model] if options[:model]
203
+ model_dir<< options[:method] if options[:model] and options[:method]
204
+ File.join(*model_dir)
205
+ end
206
+
207
+ # parse flextures function arguments
208
+ # @params [Hash] fixtures function arguments
209
+ # @return [Array] formatted load options
210
+ def self.parse_flextures_options *fixtures
211
+ options = {}
212
+ options = fixtures.shift if fixtures.size > 1 and fixtures.first.is_a?(Hash)
189
213
 
190
- # キャッシュ利用可能ならそれをそのまま使う
191
- return if options[:cache] and @@table_cache[table_name.to_sym] and @@table_cache[table_name.to_sym] == file_name
192
- @@table_cache[table_name.to_sym] = file_name
214
+ options[:dir] = self.parse_controller_option( options ) if options[:controller]
215
+ options[:dir] = self.parse_model_options( options ) if options[:model]
193
216
 
194
- print "try loading #{file_name}.yml\n" unless [:fun].include? format[:loader]
195
- return nil unless File.exist? "#{file_name}.yml"
217
+ # :all value load all loadable fixtures
218
+ fixtures = Flextures::deletable_tables if fixtures.size==1 and :all == fixtures.first
219
+ last_hash = fixtures.last.is_a?(Hash) ? fixtures.pop : {}
220
+ load_hash = fixtures.inject({}){ |h,name| h[name.to_sym] = name.to_s; h } # if name is string is buged
221
+ load_hash.merge!(last_hash)
222
+ load_hash.map { |k,v| { table: k, file: v, loader: :fun }.merge(@@option_cache).merge(options) }
223
+ end
196
224
 
197
- attributes, filter = ->{
198
- klass = PARENT::create_model table_name
199
- # rails3_acts_as_paranoid がdelete_allで物理削除しないことの対策
200
- klass.send( klass.respond_to?(:delete_all!) ? :delete_all! : :delete_all )
201
- attributes = klass.columns.map &:name
202
- filter = create_filter klass, LoadFilter[table_name], file_name, :yml
203
- [attributes, filter]
204
- }.call
225
+ # example:
226
+ # self.create_stair_list("foo/bar/baz")
227
+ # return ["foo/bar/baz","foo/bar","foo",""]
228
+ def self.stair_list dir, stair=true
229
+ return [dir.to_s] unless stair
230
+ l = []
231
+ dir.to_s.split("/").inject([]){ |a,d| a<< d; l.unshift(a.join("/")); a }
232
+ l<< ""
233
+ l
234
+ end
205
235
 
206
- yaml = YAML.load(File.open("#{file_name}.yml"))
207
- return false unless yaml # ファイルの中身が空の場合
208
- yaml.each do |k,h|
209
- warning "YAML", attributes, h.keys
210
- o = filter.call h
211
- o.save( validate: false )
236
+ # parse format option and return load file info
237
+ # @param [Hash] format load file format informations
238
+ # @return [Array] [file_name, filt_type(:csv or :yml)]
239
+ def self.file_exist format, type = [:csv,:yml]
240
+ table_name = format[:table].to_s
241
+ file_name = (format[:file] || format[:table]).to_s
242
+ base_dir_name = Flextures::Config.fixture_load_directory
243
+ self.stair_list(format[:dir], format[:stair]).each do |dir|
244
+ file_path = File.join( base_dir_name, dir, file_name )
245
+ return ["#{file_path}.csv", :csv] if type.member?(:csv) and File.exist? "#{file_path}.csv"
246
+ return ["#{file_path}.yml", :yml] if type.member?(:yml) and File.exist? "#{file_path}.yml"
212
247
  end
213
- "#{file_name}.yml"
248
+
249
+ [ File.join(base_dir_name, "#{file_name}.csv"), nil ]
250
+ end
251
+
252
+ # file load check
253
+ # @return [Bool] lodable is 'true'
254
+ def self.file_loadable? format, file_name
255
+ return unless File.exist? file_name
256
+ puts "try loading #{file_name}" if !format[:silent] and ![:fun].include?(format[:loader])
257
+ true
214
258
  end
215
259
 
216
- # 欠けたカラムを検知してメッセージを出しておく
260
+ # print warinig message that lack or not exist colum names
217
261
  def self.warning format, attributes, keys
218
- (attributes-keys).each { |name| print "Warning: #{format} colum is missing! [#{name}]\n" }
219
- (keys-attributes).each { |name| print "Warning: #{format} colum is left over! [#{name}]\n" }
262
+ (attributes-keys).each { |name| puts "Warning: #{format} colum is missing! [#{name}]" }
263
+ (keys-attributes).each { |name| puts "Warning: #{format} colum is left over! [#{name}]" }
220
264
  end
221
265
 
222
- # フィクスチャから取り出した値を、加工して欲しいデータにするフィルタを作成して返す
223
- def self.create_filter klass, factory, filename, ext
266
+ # create filter and table info
267
+ def self.create_model_filter format, file_name, type
268
+ table_name = format[:table].to_s
269
+ klass = PARENT::create_model table_name
270
+ # if you use 'rails3_acts_as_paranoid' gem, that is not delete data 'delete_all' method
271
+ klass.send (klass.respond_to?(:delete_all!) ? :delete_all! : :delete_all)
272
+
273
+ filter = ->(h){
274
+ filter = create_filter klass, LoadFilter[table_name.to_sym], file_name, type, format
275
+ o = klass.new
276
+ o = filter.call o, h
277
+ o.save( validate: false )
278
+ o
279
+ }
280
+ [klass, filter]
281
+ end
282
+
283
+ # return flextures data translate filter
284
+ # translate filter is some functions
285
+ # 1. column value is fill, if colum is not nullable
286
+ # 2. factory filter
287
+ # @params [ActiveRecord::Base] klass ActiveRecord model data
288
+ # @params [Proc] factory FactoryFilter
289
+ # @params [String] filename
290
+ # @params [Symbol] ext file type (:csv or :yml)
291
+ # @params [Hash] options other options
292
+ # @return [Proc] translate filter
293
+ def self.create_filter klass, factory, filename, ext, options
224
294
  columns = klass.columns
225
- # テーブルからカラム情報を取り出し
226
- column_hash = {}
227
- columns.each { |col| column_hash[col.name] = col }
228
- # 自動補完が必要なはずのカラム
229
- lack_columns = columns.select { |c| !c.null and !c.default }.map{ |o| o.name.to_sym }
230
- not_nullable_columns = columns.select { |c| !c.null }.map &:name
231
- # ハッシュを受け取って、必要な値に加工してからハッシュで返すラムダを返す
232
- return->(h){
233
- # テーブルに存在しないキーが定義されているときは削除
295
+ # data translat array to hash
296
+ column_hash = columns.inject({}) { |h,col| h[col.name] = col; h }
297
+ lack_columns = columns.reject { |c| c.null and c.default }.map{ |o| o.name.to_sym }
298
+ # default value shound not be null columns
299
+ not_nullable_columns = columns.reject(&:null).map &:name
300
+ strict_filter=->(o,h){
301
+ # if value is not 'nil', value translate suitable form
302
+ h.each{ |k,v| v.nil? || o[k] = (TRANSLATER[column_hash[k].type] && TRANSLATER[column_hash[k].type].call(v)) }
303
+ # call FactoryFilter
304
+ factory.call(*[o, filename, ext][0,factory.arity]) if factory and !options[:unfilter]
305
+ o
306
+ }
307
+ # receives hased data and translate ActiveRecord Model data
308
+ # loose filter correct error values
309
+ # strict filter don't correct errora values and raise error
310
+ loose_filter=->(o,h){
311
+ h.reject! { |k,v| options[:minus].include?(k) } if options[:minus]
312
+ # if column name is not include database table columns, those names delete
234
313
  h.select! { |k,v| column_hash[k] }
235
- o = klass.new
236
- # 値がnilでないなら型をDBで適切なものに変更
237
- h.each{ |k,v| nil==v || o[k] = (TRANSLATER[column_hash[k].type] ? TRANSLATER[column_hash[k].type].call(v) : nil) }
238
- # FactoryFilterを動作させる
239
- factory.call(*[o, :load, filename, ext][0,factory.arity]) if factory
240
- # 値がnilの列にデフォルト値を補間
241
- not_nullable_columns.each{ |k| o[k]==nil && o[k] = (COMPLETER[column_hash[k].type] ? COMPLETER[column_hash[k].type].call : nil) }
242
- # 列ごと抜けているデータを保管
243
- lack_columns.each { |k| nil==o[k] && o[k] = COMPLETER[column_hash[k].type].call }
314
+ strict_filter.call(o,h)
315
+ # set default value if value is 'nil'
316
+ not_nullable_columns.each{ |k| o[k].nil? && o[k] = (column_hash[k] && COMPLETER[column_hash[k].type] && COMPLETER[column_hash[k].type].call) }
317
+ # fill span values if column is not exist
318
+ lack_columns.each { |k| o[k].nil? && o[k] = (column_hash[k] && COMPLETER[column_hash[k].type] && COMPLETER[column_hash[k].type].call) }
244
319
  o
245
320
  }
321
+ (options[:strict]==true) ? strict_filter : loose_filter
246
322
  end
247
323
  end
248
324
  end