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.
@@ -3,47 +3,54 @@ module ActiveRecord
3
3
  module Model
4
4
  module Csv
5
5
  class Indexed < ::ActiveRecord::Bixformer::Model::Csv::Base
6
- class << self
7
- def new_as_association_for_import(parent, association_name, options)
8
- options = options.is_a?(Hash) ? options : {}
9
- limit_size = options[:size] || 1
6
+ def initialize(model_or_association_name, options)
7
+ super
8
+
9
+ @options = options.is_a?(::Hash) ? options : {}
10
10
 
11
- (1..limit_size).map do |index|
12
- model = self.new(association_name, options.merge(index: index))
11
+ @options[:size] ||= 1
12
+ end
13
13
 
14
- model.data_source = parent.data_source # parent.data_source is CSV::Row
14
+ def make_export_value(activerecord_or_activerecords)
15
+ activerecord_or_activerecords ||= []
15
16
 
16
- model
17
- end
17
+ # has_many でしか使わない想定なので activerecord_or_activerecords は Array のはず
18
+ (1..options[:size]).inject({}) do |values, index|
19
+ update_translator(index)
20
+
21
+ values.merge(super(activerecord_or_activerecords[index-1]))
18
22
  end
23
+ end
19
24
 
20
- def new_as_association_for_export(parent, association_name, options)
21
- options = options.is_a?(Hash) ? options : {}
22
- limit_size = options[:size] || 1
23
- associations = parent.data_source ? parent.data_source.__send__(association_name).to_a : []
25
+ def make_import_value(csv_row, parent_activerecord_id = nil)
26
+ # has_many でしか使わない想定なので Array を返却
27
+ (1..options[:size]).map do |index|
28
+ update_translator(index)
24
29
 
25
- (1..limit_size).map do |index|
26
- model = self.new(association_name, options.merge(index: index))
30
+ super
31
+ end
32
+ end
27
33
 
28
- model.data_source = associations[index - 1]
34
+ def csv_titles
35
+ (1..options[:size]).flat_map do |index|
36
+ update_translator(index)
29
37
 
30
- model
31
- end
38
+ super
32
39
  end
33
40
  end
34
41
 
35
- def setup_with_modeler(modeler)
36
- super
42
+ private
37
43
 
38
- @translator.model_arguments = { index: @options[:index] }
44
+ def update_translator(index)
45
+ @translator.model_arguments = { index: index }
39
46
 
40
- @translator.attribute_arguments_map = @attribute_map.keys.map do |attribute_name|
41
- [attribute_name, { index: @options[:index] }]
47
+ @translator.attribute_arguments_map = @attributes.map do |attr|
48
+ [attr.name, { index: index }]
42
49
  end.to_h
43
50
  end
44
51
 
45
52
  def csv_title(attribute_name)
46
- if parents.find { |parent| parent.is_a?(ActiveRecord::Bixformer::Model::Csv::Indexed) }
53
+ if parents.find { |parent| parent.is_a?(::ActiveRecord::Bixformer::Model::Csv::Indexed) }
47
54
  parents.map { |parent| parent.translator.translate_model }.join + super
48
55
  else
49
56
  super
@@ -0,0 +1,56 @@
1
+ module ActiveRecord
2
+ module Bixformer
3
+ module Plan
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :__bixformer_format
8
+
9
+ class_attribute :__bixformer_model
10
+
11
+ class_attribute :__bixformer_namespace
12
+ end
13
+
14
+ module ClassMethods
15
+ def bixformer_for(model_name)
16
+ self.__bixformer_model = model_name
17
+ end
18
+
19
+ def bixformer_load_namespace(namespace)
20
+ self.__bixformer_namespace = namespace
21
+ end
22
+ end
23
+
24
+ def entry
25
+ {}
26
+ end
27
+
28
+ def optional_attributes
29
+ []
30
+ end
31
+
32
+ def required_attributes
33
+ []
34
+ end
35
+
36
+ def unique_indexes
37
+ []
38
+ end
39
+
40
+ def required_condition
41
+ {}
42
+ end
43
+
44
+ def default_values
45
+ {}
46
+ end
47
+
48
+ def translation_config
49
+ {
50
+ scope: :bixformer,
51
+ extend_scopes: []
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,131 @@
1
+ module ActiveRecord
2
+ module Bixformer
3
+ class PlanAccessor
4
+ def initialize(plan)
5
+ @plan = plan
6
+ @module_constant_of = {}
7
+ @module_load_namespaces_of = {}
8
+ end
9
+
10
+ def raw_value
11
+ @plan
12
+ end
13
+
14
+ def value_of(config_name)
15
+ @plan.__send__(config_name)
16
+ end
17
+
18
+ def pickup_value_for(model, config_name, default_value = nil)
19
+ model_names_without_root = (model.parents.map(&:name) + [model.name]).drop(1)
20
+
21
+ # 指定された設定の全設定値を取得
22
+ entire_config_value = @plan.__send__(config_name)
23
+
24
+ if entire_config_value.is_a?(::Hash)
25
+ # Hashなら、with_indifferent_accessしておく
26
+ entire_config_value = entire_config_value.with_indifferent_access
27
+ elsif entire_config_value.is_a?(::Array) && entire_config_value.last.is_a?(::Hash)
28
+ # Arrayで最後の要素がHashなら、with_indifferent_accessしておく
29
+ config_value = entire_config_value.pop
30
+
31
+ entire_config_value.push config_value.with_indifferent_access
32
+ end
33
+
34
+ # その中から、指定のmodelに対応する設定部分を抽出
35
+ config_value = if config_name == :entry
36
+ find_entry(entire_config_value, model_names_without_root)
37
+ else
38
+ find_nested_config_value(entire_config_value, model_names_without_root)
39
+ end
40
+
41
+ if config_value.is_a?(::Array)
42
+ # Arrayで最後の要素がHashの場合、それは子要素の設定値なので、結果に含めない
43
+ config_value.pop if config_value.last.is_a?(::Hash)
44
+
45
+ # Arrayなら、要素は文字列化しておく
46
+ config_value = config_value.map { |v| v.to_s }
47
+ end
48
+
49
+ config_value || default_value
50
+ end
51
+
52
+ def new_module_instance(module_type, name_or_instance, *initializers)
53
+ name_or_instance = :base unless name_or_instance
54
+
55
+ name_or_instance = name_or_instance.to_s if name_or_instance.is_a?(::Symbol)
56
+
57
+ return name_or_instance unless name_or_instance.is_a?(::String)
58
+
59
+ if initializers.size > 0
60
+ find_module_constant(module_type, name_or_instance).new(*initializers)
61
+ else
62
+ find_module_constant(module_type, name_or_instance).new
63
+ end
64
+ end
65
+
66
+ def find_module_constant(module_type, name)
67
+ name = :base unless name
68
+
69
+ module_constant = @module_constant_of["#{module_type}/#{name}"]
70
+
71
+ return module_constant if module_constant
72
+
73
+ namespaces = @module_load_namespaces_of[module_type] ||= module_load_namespaces(module_type)
74
+
75
+ namespaces.each do |namespace|
76
+ constant = "#{namespace}::#{name.to_s.camelize}".safe_constantize
77
+
78
+ return @module_constant_of["#{module_type}/#{name}"] = constant if constant
79
+ end
80
+
81
+ raise ::ArgumentError.new "Not found module named #{name.to_s.camelize} in module_load_namespaces('#{module_type}')"
82
+ end
83
+
84
+ def parse_to_type_and_options(value)
85
+ value = value.dup if value.is_a?(::Array) || value.is_a?(::Hash)
86
+ type = value.is_a?(::Array) ? value.shift : value
87
+
88
+ arguments = if value.is_a?(::Array) && value.size == 1 && value.first.is_a?(::Hash)
89
+ value.first
90
+ elsif value.is_a?(::Array)
91
+ value
92
+ else
93
+ nil
94
+ end
95
+
96
+ [type, arguments]
97
+ end
98
+
99
+ private
100
+
101
+ def find_nested_config_value(config, keys)
102
+ return config ? config.dup : nil if keys.empty?
103
+
104
+ key = keys.shift
105
+
106
+ # config が Array なら、子要素は最後の要素にハッシュで定義してあるはず
107
+ config_map = config.is_a?(::Array) ? config.last : config
108
+
109
+ return nil unless config_map.is_a?(::Hash)
110
+
111
+ find_nested_config_value(config_map[key], keys)
112
+ end
113
+
114
+ def find_entry(config, keys)
115
+ return config ? config.dup : nil if keys.empty?
116
+
117
+ key = keys.shift
118
+
119
+ find_entry(config[:associations][key], keys)
120
+ end
121
+
122
+ def module_load_namespaces(module_type)
123
+ [
124
+ @plan.class.__bixformer_namespace,
125
+ "::ActiveRecord::Bixformer::#{module_type.to_s.camelize}::#{@plan.__bixformer_format.camelize}",
126
+ "::ActiveRecord::Bixformer::#{module_type.to_s.camelize}",
127
+ ].compact
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,56 @@
1
+ module ActiveRecord
2
+ module Bixformer
3
+ module To
4
+ class Csv < ::ActiveRecord::Bixformer::Compiler
5
+ def initialize(plan)
6
+ super(:csv, plan)
7
+ end
8
+
9
+ def csv_title_row
10
+ compile.csv_titles
11
+ end
12
+
13
+ def csv_body_row(activerecord)
14
+ model = compile
15
+ body_map = model.make_export_value(activerecord)
16
+
17
+ model.csv_titles.map { |title| body_map[title] }
18
+ end
19
+
20
+ # private
21
+
22
+ # def csv_body_map_by_model(model, activerecord_or_activerecords)
23
+ # activerecord_or_activerecords = if activerecord_or_activerecords.is_a?(::ActiveRecord::Relation)
24
+ # activerecord_or_activerecords.to_a
25
+ # else
26
+ # activerecord_or_activerecords
27
+ # end
28
+
29
+ # model.generate_export_value(activerecord_or_activerecords).merge(
30
+ # csv_body_map_by_association(model, activerecord_or_activerecords)
31
+ # )
32
+ # end
33
+
34
+ # def csv_body_map_by_association(model, activerecord_or_activerecords)
35
+ # model.associations.inject({}) do |body_map, association_model|
36
+ # activerecords = if activerecord_or_activerecords.is_a?(::Array)
37
+ # activerecord_or_activerecords
38
+ # else
39
+ # [activerecord_or_activerecords]
40
+ # end
41
+
42
+ # body_map.merge(
43
+ # activerecords.inject({}) do |body_each_map, activerecord|
44
+ # association_value = activerecord.__send__(association_model.name)
45
+
46
+ # body_each_map.merge(
47
+ # csv_body_map_by_model(association_model, association_value)
48
+ # )
49
+ # end
50
+ # )
51
+ # end
52
+ # end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Bixformer
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -1,44 +1,3 @@
1
1
  require 'activerecord-bixformer/version'
2
-
3
- module ActiveRecord
4
- module Bixformer
5
- module Attribute
6
- autoload :Base, 'activerecord-bixformer/attribute/base'
7
- autoload :Boolean, 'activerecord-bixformer/attribute/boolean'
8
- autoload :Booletania, 'activerecord-bixformer/attribute/booletania'
9
- autoload :Date, 'activerecord-bixformer/attribute/date'
10
- autoload :Enumerize, 'activerecord-bixformer/attribute/enumerize'
11
- autoload :Override, 'activerecord-bixformer/attribute/override'
12
- autoload :Time, 'activerecord-bixformer/attribute/time'
13
- end
14
-
15
- module Generator
16
- autoload :ActiveRecord, 'activerecord-bixformer/generator/active_record'
17
- autoload :Base, 'activerecord-bixformer/generator/base'
18
- autoload :CsvRow, 'activerecord-bixformer/generator/csv_row'
19
- end
20
-
21
- module Model
22
- autoload :Base, 'activerecord-bixformer/model/base'
23
-
24
- module Csv
25
- autoload :Base, 'activerecord-bixformer/model/csv/base'
26
- autoload :Indexed, 'activerecord-bixformer/model/csv/indexed'
27
- end
28
- end
29
-
30
- module Modeler
31
- autoload :Base, 'activerecord-bixformer/modeler/base'
32
- autoload :Csv, 'activerecord-bixformer/modeler/csv'
33
- end
34
-
35
- module Runner
36
- autoload :Base, 'activerecord-bixformer/runner/base'
37
- autoload :Csv, 'activerecord-bixformer/runner/csv'
38
- end
39
-
40
- module Translator
41
- autoload :I18n, 'activerecord-bixformer/translator/i18n'
42
- end
43
- end
44
- end
2
+ require 'activerecord-bixformer/autoload'
3
+ require 'activerecord-bixformer/format/csv'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-bixformer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hiroaki Otsu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-13 00:00:00.000000000 Z
11
+ date: 2016-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -222,6 +222,7 @@ files:
222
222
  - Rakefile
223
223
  - activerecord-bixformer.gemspec
224
224
  - lib/activerecord-bixformer.rb
225
+ - lib/activerecord-bixformer/assignable_attributes_normalizer.rb
225
226
  - lib/activerecord-bixformer/attribute/base.rb
226
227
  - lib/activerecord-bixformer/attribute/boolean.rb
227
228
  - lib/activerecord-bixformer/attribute/booletania.rb
@@ -229,16 +230,17 @@ files:
229
230
  - lib/activerecord-bixformer/attribute/enumerize.rb
230
231
  - lib/activerecord-bixformer/attribute/override.rb
231
232
  - lib/activerecord-bixformer/attribute/time.rb
232
- - lib/activerecord-bixformer/generator/active_record.rb
233
- - lib/activerecord-bixformer/generator/base.rb
234
- - lib/activerecord-bixformer/generator/csv_row.rb
233
+ - lib/activerecord-bixformer/autoload.rb
234
+ - lib/activerecord-bixformer/compiler.rb
235
+ - lib/activerecord-bixformer/format/csv.rb
236
+ - lib/activerecord-bixformer/from/csv.rb
237
+ - lib/activerecord-bixformer/import_value_validatable.rb
235
238
  - lib/activerecord-bixformer/model/base.rb
236
239
  - lib/activerecord-bixformer/model/csv/base.rb
237
240
  - lib/activerecord-bixformer/model/csv/indexed.rb
238
- - lib/activerecord-bixformer/modeler/base.rb
239
- - lib/activerecord-bixformer/modeler/csv.rb
240
- - lib/activerecord-bixformer/runner/base.rb
241
- - lib/activerecord-bixformer/runner/csv.rb
241
+ - lib/activerecord-bixformer/plan.rb
242
+ - lib/activerecord-bixformer/plan_accessor.rb
243
+ - lib/activerecord-bixformer/to/csv.rb
242
244
  - lib/activerecord-bixformer/translator/i18n.rb
243
245
  - lib/activerecord-bixformer/version.rb
244
246
  homepage: https://github.com/aki2o/activerecord-bixformer
@@ -1,170 +0,0 @@
1
- module ActiveRecord
2
- module Bixformer
3
- module Generator
4
- class ActiveRecord < ::ActiveRecord::Bixformer::Generator::Base
5
- private
6
-
7
- def association_generator
8
- :new_as_association_for_import
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
- attribute_value_map = model.generate_import_value_map
17
- required_attributes = @modeler.config_value_for(model, :required_attributes, [])
18
- identified_column_name = identified_column_name_of(model)
19
-
20
- # 必須な属性が渡されていない場合には、取り込みしない
21
- return {} if required_attributes.any? { |attribute_name| ! presence_value?(attribute_value_map[attribute_name]) }
22
-
23
- set_required_condition(model, attribute_value_map)
24
- set_parent_key(model, attribute_value_map, identified_column_name)
25
- set_activerecord_id(model, attribute_value_map, identified_column_name)
26
-
27
- # 空でない要素が無いなら、空ハッシュで返す
28
- presence_value?(attribute_value_map) ? attribute_value_map : {}
29
- end
30
-
31
- def generate_association_value(parent_model)
32
- association_value_map = {}.with_indifferent_access
33
-
34
- parent_model.association_map.each do |association_name, model_or_models|
35
- association_value = if model_or_models.is_a?(::Array)
36
- model_or_models.map { |m| generate_model_value(m) }.reject { |v| ! presence_value?(v) }
37
- else
38
- generate_model_value(model_or_models)
39
- end
40
-
41
- # 取り込み時は、オプショナルな関連では、空と思われる値は取り込まない
42
- next if ! presence_value?(association_value) &&
43
- parent_model.optional_attributes.include?(association_name.to_s)
44
-
45
- association_value_map["#{association_name}_attributes".to_sym] = association_value
46
- end
47
-
48
- association_value_map
49
- end
50
-
51
- def set_required_condition(model, attribute_value_map)
52
- # 結果ハッシュが空なら、取り込みしないように追加はしない
53
- return unless presence_value?(attribute_value_map)
54
-
55
- # 設定するのはルートの場合のみ
56
- return if model.parent
57
-
58
- attribute_value_map.merge!(@modeler.required_condition)
59
- end
60
-
61
- def set_parent_key(model, attribute_value_map, identified_column_name)
62
- # 結果ハッシュが空なら、取り込みしないように追加はしない
63
- return unless presence_value?(attribute_value_map)
64
-
65
- # 設定するのは親がいる場合のみ
66
- return unless model.parent
67
-
68
- parent_id = model.parent&.activerecord_id
69
-
70
- if parent_id
71
- # 親のレコードが見つかっているなら、それも結果ハッシュに追加する
72
- attribute_value_map[model.parent_foreign_key] = parent_id
73
- else
74
- # 見つかっていないなら、間違った値が指定されている可能性があるので、キー自体を削除
75
- attribute_value_map.delete(model.parent_foreign_key)
76
- end
77
- end
78
-
79
- def set_activerecord_id(model, attribute_value_map, identified_column_name)
80
- # 更新の場合は、インポートデータを元にデータベースから対象のレコードを検索してIDを取得
81
- model.activerecord_id = verified_activerecord_id(model, attribute_value_map, identified_column_name)
82
-
83
- if model.activerecord_id
84
- # 更新なら、ID属性を改めて設定
85
- attribute_value_map[identified_column_name] = model.activerecord_id
86
- else
87
- # 見つかっていないなら、間違った値が指定されている可能性があるので、キー自体を削除
88
- attribute_value_map.delete(identified_column_name)
89
- end
90
- end
91
-
92
- def verified_activerecord_id(model, attribute_value_map, identified_column_name)
93
- # 更新対象のレコードを特定できるかチェック
94
- identified_value = attribute_value_map[identified_column_name]
95
-
96
- uniqueness_condition = if identified_value
97
- { identified_column_name => identified_value }
98
- else
99
- find_unique_condition(model, attribute_value_map, identified_column_name)
100
- end
101
-
102
- # レコードが特定できないなら、更新処理ではないので終了
103
- return nil unless uniqueness_condition
104
-
105
- # 更新対象のレコードを正しく特定できているか確認するための検証条件を取得
106
- required_condition = if model.parent
107
- key = model.parent_foreign_key
108
-
109
- { key => attribute_value_map[key] }
110
- else
111
- @modeler.required_condition
112
- end
113
-
114
- # 検証条件は、必ず値がなければならない
115
- return nil if required_condition.any? { |_k, v| ! presence_value?(v) }
116
-
117
- # インポートされてきた、レコードを特定する条件が、誤った値でないかどうかを、
118
- # 特定されるレコードが、更新すべき正しいレコードであるかチェックするための
119
- # 検証条件とマージして、データベースに登録されているか確認する
120
- verified_condition = uniqueness_condition.merge(required_condition)
121
-
122
- find_verified_activerecord_by!(model.activerecord_constant, verified_condition).__send__(identified_column_name)
123
- rescue ::ActiveRecord::RecordNotFound => e
124
- # ID属性が指定されているのに、データベースに見つからない場合はエラーにする
125
- raise e if identified_value
126
- end
127
-
128
- def find_unique_condition(model, attribute_value_map, identified_column_name)
129
- unique_indexes = @modeler.config_value_for(model, :unique_indexes, [])
130
-
131
- # ユニーク条件が指定されていないなら終了
132
- return nil if unique_indexes.empty?
133
-
134
- unique_condition = unique_indexes.map do |key|
135
- [key, attribute_value_map[key]]
136
- end.to_h
137
-
138
- # ユニーク条件は、必ず値がなければならない
139
- return nil if unique_condition.any? { |_k, v| ! presence_value?(v) }
140
-
141
- unique_condition
142
- end
143
-
144
- def find_verified_activerecord_by!(klass, verified_uniqueness_condition)
145
- klass.find_by!(verified_uniqueness_condition)
146
- end
147
-
148
- def identified_column_name_of(model)
149
- model.activerecord_constant.primary_key
150
- end
151
-
152
- def presence_value?(value)
153
- # 空でない要素であるか or 空でない要素を含んでいるかどうか
154
- case value
155
- when ::Hash
156
- value.values.any? { |v| presence_value?(v) }
157
- when ::Array
158
- value.any? { |v| presence_value?(v) }
159
- when ::String
160
- ! value.blank?
161
- when ::TrueClass, ::FalseClass
162
- true
163
- else
164
- value ? true : false
165
- end
166
- end
167
- end
168
- end
169
- end
170
- end
@@ -1,66 +0,0 @@
1
- module ActiveRecord
2
- module Bixformer
3
- module Generator
4
- class Base
5
- attr_reader :data_source
6
-
7
- def initialize(modeler, data_source)
8
- @modeler = modeler
9
- @data_source = data_source
10
- end
11
-
12
- def compile
13
- return @model if @model
14
-
15
- model_name = @modeler.model_name
16
- model_type, model_options = @modeler.parse_to_type_and_options(@modeler.entry_definition[:type])
17
-
18
- @model = @modeler.new_module_instance(:model, model_type, model_name, model_options)
19
-
20
- @model.data_source = @data_source
21
-
22
- compile_model(@model)
23
-
24
- @model
25
- end
26
-
27
- def generate
28
- generate_model_value(compile)
29
- end
30
-
31
- private
32
-
33
- def compile_model(model)
34
- model.setup_with_modeler(@modeler)
35
-
36
- compile_associations(model)
37
- end
38
-
39
- def compile_associations(parent_model)
40
- association_definitions = @modeler.config_value_for(parent_model, :entry_definition, {})[:associations] || {}
41
-
42
- association_definitions.each do |association_name, association_definition|
43
- association_type, association_options = @modeler.parse_to_type_and_options(association_definition[:type])
44
- association_constant = @modeler.find_module_constant(:model, association_type)
45
-
46
- model_or_models = association_constant.__send__(
47
- association_generator, parent_model, association_name, association_options
48
- )
49
-
50
- parent_model.add_association(model_or_models)
51
-
52
- if model_or_models.is_a?(::Array)
53
- model_or_models.each { |model| compile_model(model) }
54
- else
55
- compile_model(model_or_models)
56
- end
57
- end
58
- end
59
-
60
- def association_generator
61
- raise ::NotImplementedError.new "You must implement #{self.class}##{__method__}"
62
- end
63
- end
64
- end
65
- end
66
- end