activerecord-bixformer 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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