activerecord-bixformer 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README-ja.md +10 -10
- data/lib/activerecord-bixformer/assignable_attributes_normalizer.rb +125 -0
- data/lib/activerecord-bixformer/attribute/booletania.rb +4 -6
- data/lib/activerecord-bixformer/attribute/enumerize.rb +4 -2
- data/lib/activerecord-bixformer/autoload.rb +27 -0
- data/lib/activerecord-bixformer/compiler.rb +45 -0
- data/lib/activerecord-bixformer/format/csv.rb +20 -0
- data/lib/activerecord-bixformer/from/csv.rb +15 -0
- data/lib/activerecord-bixformer/import_value_validatable.rb +21 -0
- data/lib/activerecord-bixformer/model/base.rb +67 -81
- data/lib/activerecord-bixformer/model/csv/base.rb +31 -23
- data/lib/activerecord-bixformer/model/csv/indexed.rb +31 -24
- data/lib/activerecord-bixformer/plan.rb +56 -0
- data/lib/activerecord-bixformer/plan_accessor.rb +131 -0
- data/lib/activerecord-bixformer/to/csv.rb +56 -0
- data/lib/activerecord-bixformer/version.rb +1 -1
- data/lib/activerecord-bixformer.rb +2 -43
- metadata +11 -9
- data/lib/activerecord-bixformer/generator/active_record.rb +0 -170
- data/lib/activerecord-bixformer/generator/base.rb +0 -66
- data/lib/activerecord-bixformer/generator/csv_row.rb +0 -40
- data/lib/activerecord-bixformer/modeler/base.rb +0 -160
- data/lib/activerecord-bixformer/modeler/csv.rb +0 -11
- data/lib/activerecord-bixformer/runner/base.rb +0 -47
- data/lib/activerecord-bixformer/runner/csv.rb +0 -123
@@ -1,40 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Bixformer
|
3
|
-
module Generator
|
4
|
-
class CsvRow < ::ActiveRecord::Bixformer::Generator::Base
|
5
|
-
private
|
6
|
-
|
7
|
-
def association_generator
|
8
|
-
:new_as_association_for_export
|
9
|
-
end
|
10
|
-
|
11
|
-
def generate_model_value(model)
|
12
|
-
generate_attributes_value(model).merge(generate_association_value(model))
|
13
|
-
end
|
14
|
-
|
15
|
-
def generate_attributes_value(model)
|
16
|
-
model.generate_export_value_map.map do |attribute_name, attribute_value|
|
17
|
-
[model.csv_title(attribute_name), attribute_value]
|
18
|
-
end.to_h
|
19
|
-
end
|
20
|
-
|
21
|
-
def generate_association_value(parent_model)
|
22
|
-
parent_model.association_map.values.inject({}) do |association_value, model_or_models|
|
23
|
-
models = model_or_models.is_a?(::Array) ? model_or_models : [model_or_models]
|
24
|
-
|
25
|
-
# 全関連レコードの生成結果を単一ハッシュにマージ
|
26
|
-
association_value.merge(
|
27
|
-
models.inject({}) do |current_association_value, model|
|
28
|
-
# 関連レコードの全生成結果を単一ハッシュにマージ
|
29
|
-
current_association_value.merge(
|
30
|
-
# キーをCSVカラム名に置き換えたハッシュを作成
|
31
|
-
generate_model_value(model)
|
32
|
-
)
|
33
|
-
end
|
34
|
-
)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,160 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Bixformer
|
3
|
-
module Modeler
|
4
|
-
class Base
|
5
|
-
attr_reader :format
|
6
|
-
|
7
|
-
def initialize(format)
|
8
|
-
@format = format
|
9
|
-
@module_load_namespaces_of = {}
|
10
|
-
@module_constant_of = {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def model_name
|
14
|
-
end
|
15
|
-
|
16
|
-
def entry_definition
|
17
|
-
{}
|
18
|
-
end
|
19
|
-
|
20
|
-
def optional_attributes
|
21
|
-
[]
|
22
|
-
end
|
23
|
-
|
24
|
-
def required_attributes
|
25
|
-
[]
|
26
|
-
end
|
27
|
-
|
28
|
-
def unique_indexes
|
29
|
-
[]
|
30
|
-
end
|
31
|
-
|
32
|
-
def required_condition
|
33
|
-
{}
|
34
|
-
end
|
35
|
-
|
36
|
-
def default_values
|
37
|
-
{}
|
38
|
-
end
|
39
|
-
|
40
|
-
def translation_config
|
41
|
-
{
|
42
|
-
scope: :bixformer,
|
43
|
-
extend_scopes: []
|
44
|
-
}
|
45
|
-
end
|
46
|
-
|
47
|
-
def module_load_namespaces(module_type)
|
48
|
-
[
|
49
|
-
"::ActiveRecord::Bixformer::#{module_type.to_s.camelize}::#{format.to_s.camelize}",
|
50
|
-
"::ActiveRecord::Bixformer::#{module_type.to_s.camelize}",
|
51
|
-
]
|
52
|
-
end
|
53
|
-
|
54
|
-
def config_value_for(model, config_name, default_value = nil)
|
55
|
-
model_names_without_root = (model.parents.map(&:name) + [model.name]).drop(1)
|
56
|
-
|
57
|
-
# 指定された設定の全設定値を取得
|
58
|
-
entire_config_value = __send__(config_name)
|
59
|
-
|
60
|
-
if entire_config_value.is_a?(::Hash)
|
61
|
-
# Hashなら、with_indifferent_accessしておく
|
62
|
-
entire_config_value = entire_config_value.with_indifferent_access
|
63
|
-
elsif entire_config_value.is_a?(::Array) && entire_config_value.last.is_a?(::Hash)
|
64
|
-
# Arrayで最後の要素がHashなら、with_indifferent_accessしておく
|
65
|
-
config_value = entire_config_value.pop
|
66
|
-
|
67
|
-
entire_config_value.push config_value.with_indifferent_access
|
68
|
-
end
|
69
|
-
|
70
|
-
# その中から、指定のmodelに対応する設定部分を抽出
|
71
|
-
config_value = if config_name == :entry_definition
|
72
|
-
find_entry_definition(entire_config_value, model_names_without_root)
|
73
|
-
else
|
74
|
-
find_nested_config_value(entire_config_value, model_names_without_root)
|
75
|
-
end
|
76
|
-
|
77
|
-
if config_value.is_a?(::Array)
|
78
|
-
# Arrayで最後の要素がHashの場合、それは子要素の設定値なので、結果に含めない
|
79
|
-
config_value.pop if config_value.last.is_a?(::Hash)
|
80
|
-
|
81
|
-
# Arrayなら、要素は文字列化しておく
|
82
|
-
config_value = config_value.map { |v| v.to_s }
|
83
|
-
end
|
84
|
-
|
85
|
-
config_value || default_value
|
86
|
-
end
|
87
|
-
|
88
|
-
def new_module_instance(module_type, name_or_instance, *initializers)
|
89
|
-
name_or_instance = :base unless name_or_instance
|
90
|
-
|
91
|
-
name_or_instance = name_or_instance.to_s if name_or_instance.is_a?(::Symbol)
|
92
|
-
|
93
|
-
return name_or_instance unless name_or_instance.is_a?(::String)
|
94
|
-
|
95
|
-
if initializers.size > 0
|
96
|
-
find_module_constant(module_type, name_or_instance).new(*initializers)
|
97
|
-
else
|
98
|
-
find_module_constant(module_type, name_or_instance).new
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def find_module_constant(module_type, name)
|
103
|
-
name = :base unless name
|
104
|
-
|
105
|
-
module_constant = @module_constant_of["#{module_type}/#{name}"]
|
106
|
-
|
107
|
-
return module_constant if module_constant
|
108
|
-
|
109
|
-
namespaces = @module_load_namespaces_of[module_type] ||= module_load_namespaces(module_type)
|
110
|
-
|
111
|
-
namespaces.each do |namespace|
|
112
|
-
constant = "#{namespace}::#{name.to_s.camelize}".safe_constantize
|
113
|
-
|
114
|
-
return @module_constant_of["#{module_type}/#{name}"] = constant if constant
|
115
|
-
end
|
116
|
-
|
117
|
-
raise ::ArgumentError.new "Not found module named #{name.to_s.camelize} in module_load_namespaces('#{module_type}')"
|
118
|
-
end
|
119
|
-
|
120
|
-
def parse_to_type_and_options(value)
|
121
|
-
value = value.dup if value.is_a?(::Array) || value.is_a?(::Hash)
|
122
|
-
type = value.is_a?(::Array) ? value.shift : value
|
123
|
-
|
124
|
-
arguments = if value.is_a?(::Array) && value.size == 1 && value.first.is_a?(::Hash)
|
125
|
-
value.first
|
126
|
-
elsif value.is_a?(::Array)
|
127
|
-
value
|
128
|
-
else
|
129
|
-
nil
|
130
|
-
end
|
131
|
-
|
132
|
-
[type, arguments]
|
133
|
-
end
|
134
|
-
|
135
|
-
private
|
136
|
-
|
137
|
-
def find_nested_config_value(config, keys)
|
138
|
-
return config ? config.dup : nil if keys.empty?
|
139
|
-
|
140
|
-
key = keys.shift
|
141
|
-
|
142
|
-
# config が Array なら、子要素は最後の要素にハッシュで定義してあるはず
|
143
|
-
config_map = config.is_a?(::Array) ? config.last : config
|
144
|
-
|
145
|
-
return nil unless config_map.is_a?(::Hash)
|
146
|
-
|
147
|
-
find_nested_config_value(config_map[key], keys)
|
148
|
-
end
|
149
|
-
|
150
|
-
def find_entry_definition(config, keys)
|
151
|
-
return config ? config.dup : nil if keys.empty?
|
152
|
-
|
153
|
-
key = keys.shift
|
154
|
-
|
155
|
-
find_entry_definition(config[:associations][key], keys)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'active_record'
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
module Bixformer
|
5
|
-
module Runner
|
6
|
-
class Base
|
7
|
-
attr_reader :errors
|
8
|
-
|
9
|
-
def initialize(format)
|
10
|
-
@format = format
|
11
|
-
@modelers = []
|
12
|
-
@errors = []
|
13
|
-
end
|
14
|
-
|
15
|
-
def add_modeler(*modelers)
|
16
|
-
modelers.each do |modeler|
|
17
|
-
unless modeler.format.to_s == @format.to_s
|
18
|
-
raise ArgumentError.new "modeler format unmatches to #{@format} as runner format : #{modeler.format}"
|
19
|
-
end
|
20
|
-
|
21
|
-
@modelers.push modeler
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def available_modelers
|
28
|
-
if @modelers.empty?
|
29
|
-
raise ArgumentError.new "Not exist any available modelers. You have to regist modeler by add_modeler."
|
30
|
-
end
|
31
|
-
|
32
|
-
@modelers
|
33
|
-
end
|
34
|
-
|
35
|
-
def active_modeler(force_detect = nil)
|
36
|
-
return @active_modeler if ! force_detect && @active_modeler
|
37
|
-
|
38
|
-
@active_modeler = detect_modeler
|
39
|
-
end
|
40
|
-
|
41
|
-
def detect_modeler
|
42
|
-
available_modelers.first
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,123 +0,0 @@
|
|
1
|
-
require 'csv'
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
module Bixformer
|
5
|
-
module Runner
|
6
|
-
class Csv < ::ActiveRecord::Bixformer::Runner::Base
|
7
|
-
def initialize
|
8
|
-
super(:csv)
|
9
|
-
end
|
10
|
-
|
11
|
-
def import(csv_data, options = {})
|
12
|
-
modeler = active_modeler(true)
|
13
|
-
model_constant = modeler.model_name.to_s.camelize.constantize
|
14
|
-
|
15
|
-
@errors = []
|
16
|
-
options[:headers] = true
|
17
|
-
|
18
|
-
model_attributes_list = parse_csv_rows(::CSV.parse(csv_data, options))
|
19
|
-
|
20
|
-
model_constant.transaction do
|
21
|
-
model_attributes_list.each.with_index(1) do |model_attributes, index|
|
22
|
-
next unless model_attributes&.present?
|
23
|
-
|
24
|
-
identified_value = model_attributes[model_constant.primary_key]
|
25
|
-
|
26
|
-
activerecord = if identified_value
|
27
|
-
find_activerecord(model_constant, identified_value)
|
28
|
-
else
|
29
|
-
build_activerecord(model_constant, model_attributes)
|
30
|
-
end
|
31
|
-
|
32
|
-
unless save_activerecord(activerecord, model_attributes)
|
33
|
-
@errors += make_error_messages(activerecord, index)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
raise ::ActiveRecord::Rollback unless @errors.empty?
|
38
|
-
end
|
39
|
-
|
40
|
-
@errors.empty? ? true : false
|
41
|
-
end
|
42
|
-
|
43
|
-
def export(active_records_or_relation, options = {})
|
44
|
-
modeler = active_modeler(true)
|
45
|
-
generator = modeler.new_module_instance(:generator, :csv_row, modeler, active_records_or_relation.first)
|
46
|
-
csv_titles = make_csv_titles(generator)
|
47
|
-
|
48
|
-
::CSV.generate(options) do |csv|
|
49
|
-
csv << csv_titles
|
50
|
-
|
51
|
-
active_records_or_relation.each do |activerecord|
|
52
|
-
generator = modeler.new_module_instance(:generator, :csv_row, modeler, activerecord)
|
53
|
-
|
54
|
-
csv << make_csv_row(generator, csv_titles)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def make_csv_titles(generator)
|
62
|
-
generator.compile.available_csv_titles
|
63
|
-
end
|
64
|
-
|
65
|
-
def make_csv_row(generator, csv_titles)
|
66
|
-
model_attributes = generator.generate
|
67
|
-
|
68
|
-
csv_titles.map { |title| model_attributes[title] }
|
69
|
-
end
|
70
|
-
|
71
|
-
def parse_csv_rows(csv_rows)
|
72
|
-
modeler = active_modeler
|
73
|
-
|
74
|
-
csv_rows.map do |csv_row|
|
75
|
-
generator = modeler.new_module_instance(:generator, :active_record, modeler, csv_row)
|
76
|
-
|
77
|
-
parse_csv_row(generator)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def parse_csv_row(generator)
|
82
|
-
generator.generate
|
83
|
-
end
|
84
|
-
|
85
|
-
def find_activerecord(model_class, id)
|
86
|
-
model_class.find(id)
|
87
|
-
end
|
88
|
-
|
89
|
-
def build_activerecord(model_class, attributes)
|
90
|
-
model_class.new(attributes)
|
91
|
-
end
|
92
|
-
|
93
|
-
def save_activerecord(activerecord, assigned_attributes)
|
94
|
-
# CSVで削除が可能だが、削除フラグは _destroy というattributesに入っており、こんなcolumnは存在しないので、このままだとエラーになる
|
95
|
-
# そのため、ここで要素から削除しておく
|
96
|
-
is_destroy = assigned_attributes.delete(:_destroy)
|
97
|
-
|
98
|
-
if activerecord.persisted?
|
99
|
-
if is_destroy
|
100
|
-
activerecord.destroy
|
101
|
-
else
|
102
|
-
activerecord.update(assigned_attributes)
|
103
|
-
end
|
104
|
-
else
|
105
|
-
activerecord.save
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def make_error_messages(activerecord, csv_row_index)
|
110
|
-
activerecord.errors.full_messages.map do |msg|
|
111
|
-
I18n.t(
|
112
|
-
:"bixformer.csv.errors.format", {
|
113
|
-
default: "Entry(%{csv_row_index}): %{message}",
|
114
|
-
csv_row_index: csv_row_index,
|
115
|
-
message: msg
|
116
|
-
}
|
117
|
-
)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|