flextures 2.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/README.ja.rdoc +33 -22
- data/README.rdoc +29 -22
- data/VERSION +1 -1
- data/flextures.gemspec +7 -6
- data/lib/flextures/flextures.rake +28 -4
- data/lib/flextures/flextures.rb +33 -28
- data/lib/flextures/flextures_base_config.rb +17 -6
- data/lib/flextures/flextures_command.rb +25 -27
- data/lib/flextures/flextures_dumper.rb +82 -55
- data/lib/flextures/flextures_extension_modules.rb +12 -8
- data/lib/flextures/flextures_factory.rb +13 -11
- data/lib/flextures/flextures_loader.rb +208 -132
- data/lib/flextures/rspec_flextures_support.rb +13 -7
- data/lib/flextures/testunit_flextures_support.rb +30 -4
- data/lib/flextures/version.rb +1 -1
- data/test/unit/test_flextures.rb +1 -1
- data/test/unit/test_flextures_args.rb +70 -17
- data/test/unit/test_flextures_dumper.rb +48 -37
- data/test/unit/test_flextures_extention_modules.rb +37 -0
- data/test/unit/test_flextures_hooks.rb +66 -17
- data/test/unit/test_flextures_loader.rb +89 -14
- data/test/unit/test_simple.rb +1 -1
- metadata +12 -21
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
#
|
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]
|
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
|
-
|
169
|
-
csv<<
|
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<<
|
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
|
-
#
|
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]
|
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.
|
204
|
-
|
205
|
-
|
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
|
-
#
|
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
|
-
.
|
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
|
-
|
23
|
-
[keys
|
24
|
-
|
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
|
-
#
|
6
|
+
# FactoryFilter data
|
7
7
|
FACTORIES={}
|
8
8
|
|
9
|
-
#
|
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
|
-
#
|
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
|
-
#
|
35
|
+
# FactoryDumpFilter data
|
34
36
|
FACTORIES={}
|
35
37
|
|
36
|
-
#
|
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
|
-
#
|
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
|
-
#
|
12
|
+
# data loader
|
13
13
|
module Loader
|
14
14
|
PARENT = Flextures
|
15
15
|
|
16
|
-
@@
|
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)
|
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
|
54
|
+
return d if d.nil?
|
55
55
|
d.to_i
|
56
56
|
},
|
57
57
|
float:->(d){
|
58
|
-
return d
|
58
|
+
return d if d.nil?
|
59
59
|
d.to_f
|
60
60
|
},
|
61
61
|
integer:->(d){
|
62
|
-
return d
|
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
|
-
#
|
114
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
#
|
132
|
-
#
|
133
|
-
|
134
|
-
|
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
|
-
#
|
147
|
-
|
148
|
-
|
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
|
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
|
-
|
159
|
-
|
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
|
-
|
163
|
-
|
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
|
-
|
166
|
-
return nil unless File.exist? "#{file_name}.csv"
|
155
|
+
return unless self.file_loadable? format, file_name
|
167
156
|
|
168
|
-
klass =
|
169
|
-
|
170
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
180
|
-
o.save( validate: false )
|
168
|
+
filter.call h
|
181
169
|
end
|
182
170
|
end
|
183
|
-
|
171
|
+
file_name
|
184
172
|
end
|
185
173
|
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
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
|
-
|
195
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
-
|
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|
|
219
|
-
(keys-attributes).each { |name|
|
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.
|
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.
|
228
|
-
#
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
236
|
-
#
|
237
|
-
|
238
|
-
#
|
239
|
-
|
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
|