flextures 2.1.0 → 3.0.0

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