shopify_transporter 1.0.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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +23 -0
  3. data/LICENSE +20 -0
  4. data/README.md +290 -0
  5. data/RELEASING +16 -0
  6. data/Rakefile +11 -0
  7. data/exe/shopify_transporter +47 -0
  8. data/lib/shopify_transporter/generators/base_group.rb +25 -0
  9. data/lib/shopify_transporter/generators/generate.rb +64 -0
  10. data/lib/shopify_transporter/generators/new.rb +23 -0
  11. data/lib/shopify_transporter/generators.rb +3 -0
  12. data/lib/shopify_transporter/pipeline/all_platforms/metafields.rb +61 -0
  13. data/lib/shopify_transporter/pipeline/magento/customer/addresses_attribute.rb +60 -0
  14. data/lib/shopify_transporter/pipeline/magento/customer/top_level_attributes.rb +47 -0
  15. data/lib/shopify_transporter/pipeline/magento/order/addresses_attribute.rb +46 -0
  16. data/lib/shopify_transporter/pipeline/magento/order/line_items.rb +55 -0
  17. data/lib/shopify_transporter/pipeline/magento/order/top_level_attributes.rb +39 -0
  18. data/lib/shopify_transporter/pipeline/magento/product/top_level_attributes.rb +36 -0
  19. data/lib/shopify_transporter/pipeline/stage.rb +16 -0
  20. data/lib/shopify_transporter/pipeline.rb +4 -0
  21. data/lib/shopify_transporter/record_builder.rb +51 -0
  22. data/lib/shopify_transporter/shopify/attributes_accumulator.rb +43 -0
  23. data/lib/shopify_transporter/shopify/attributes_helpers.rb +53 -0
  24. data/lib/shopify_transporter/shopify/customer.rb +79 -0
  25. data/lib/shopify_transporter/shopify/order.rb +107 -0
  26. data/lib/shopify_transporter/shopify/product.rb +118 -0
  27. data/lib/shopify_transporter/shopify/record.rb +60 -0
  28. data/lib/shopify_transporter/shopify.rb +7 -0
  29. data/lib/shopify_transporter/version.rb +4 -0
  30. data/lib/shopify_transporter.rb +247 -0
  31. data/lib/tasks/factory_bot.rake +9 -0
  32. data/lib/templates/custom_stage.tt +31 -0
  33. data/lib/templates/gemfile.tt +5 -0
  34. data/lib/templates/magento/config.tt +23 -0
  35. data/shopify_transporter.gemspec +36 -0
  36. metadata +152 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e731ee32bf65a13db82226613fcdfe4de06e5dcd
4
+ data.tar.gz: 6f52f02f93b67cc3bb96a709ccc895126bd2dfe6
5
+ SHA512:
6
+ metadata.gz: 624777e136f3cb0d659935f6fbe5152ccba71e17fa38e0b884bad43074f5e730315916aa52250758255dd28aec3c47fabc2042567f22b5a217a40d6a5ed47c2c
7
+ data.tar.gz: 505991f408682f301e7af3be5e4805e74ed2130bbb3c8ed3d42ae85ec1f5159bf6ea49350979e374924163d85ddb0db9fc43432415b5ad6e096129405a3a55dd
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
3
+
4
+ # Specify your gem's dependencies in shopify_transporter.gemspec
5
+ gemspec
6
+
7
+ group :development do
8
+ gem 'package_cloud'
9
+ gem 'rubocop'
10
+ gem 'rubocop-git', '~> 0.1'
11
+ end
12
+
13
+ group :test do
14
+ gem 'codecov', require: false
15
+ gem 'simplecov', require: false
16
+ end
17
+
18
+ group :development, :test do
19
+ gem 'factory_bot', '~> 4.8'
20
+ gem 'pry', '~> 0.11'
21
+ gem 'pry-byebug'
22
+ gem 'rspec', '~> 3.0'
23
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2018 "Shopify Inc."
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,290 @@
1
+ # Transporter Tools
2
+
3
+ The Transporter tool helps converts data from a third-party platform format into a format that can
4
+ be imported into Shopify via the [Transporter app](https://help.shopify.com/manual/migrating-to-shopify/transporter-app).
5
+
6
+ The conversions are currently limited to JSON structured data that has been exported from Magento 1.x via its
7
+ SOAP API. More details about the exact structure of the JSON data, and additional scripts for procuding such
8
+ data, will be made available in the near future.
9
+
10
+ Note: the Transporter app is available to Shopify Plus plans only.
11
+
12
+ ## Installation
13
+
14
+ ### Building and installing locally
15
+ To build locally run the following command:
16
+
17
+ ```
18
+ $ bundle exec rake build
19
+
20
+ shopify_transporter 1.0.0 built to pkg/shopify_transporter-1.0.0.gem.
21
+ ```
22
+
23
+ Then, you can install the gem system-wide by running:
24
+
25
+ ```
26
+ $ gem install pkg/shopify_transporter-1.0.0.gem
27
+
28
+ Successfully installed shopify_transporter-1.0.0
29
+ Parsing documentation for shopify_transporter-1.0.0
30
+ Installing ri documentation for shopify_transporter-1.0.0
31
+ Done installing documentation for shopify_transporter after 0 seconds
32
+ 1 gem installed
33
+ ```
34
+ Your locally built gem is now installed system-wide.
35
+
36
+ ### Installing the Transporter tool gem from rubygems:
37
+
38
+ ```
39
+ $ gem install shopify_transporter
40
+ ```
41
+
42
+ ### Running
43
+ After you install the gem, you should find the executable `shopify_transporter` available in your path:
44
+
45
+ ```
46
+ $ which shopify_transporter
47
+ /usr/local/bin/shopify_transporter
48
+ ```
49
+ ## Help and usage
50
+
51
+ To view the usage and help for the `shopify_transporter` run the following command:
52
+
53
+ ```
54
+ $ shopify_transporter -h
55
+
56
+ Commands:
57
+ shopify_transporter convert --config=CONFIG --object=OBJECT file1.csv file2.csv ... # Converts your files into shopify formatted files.
58
+ shopify_transporter generate STAGE_NAME --object=OBJECT # Generates a new pipeline stage for the specified object type
59
+ shopify_transporter help [COMMAND] # Describe available commands or one specific command
60
+ shopify_transporter new PROJECTNAME --platform=PLATFORM # Generates a new project structure for a platform
61
+ ```
62
+
63
+ ## Create a conversion project
64
+
65
+ It's convenient to create a conversion project for each store that you want to migrate to Shopify.
66
+ To create it, use the `new` sub-command:
67
+
68
+ ```
69
+ $ shopify_transporter new example-magento-conversion --platform=magento
70
+ create example_magento_migration/Gemfile
71
+ create example_magento_migration/config.yml
72
+ create example_magento_migration/lib/magento/custom_pipeline_stages
73
+ ```
74
+
75
+ The `new` sub-command creates a project folder with the following:
76
+
77
+ * a `Gemfile` that references the `shopify_transporter` gem. This Gemfile allows custom pipeline stages
78
+ to refer to the base classes that are defined in the `shopify_transporter` gem.
79
+
80
+ * a configuration file (`config.yml`) that provides the configuration required for each pipeline stage
81
+ in the conversion process.
82
+
83
+ * a folder to hold additional custom pipeline stages you may define later
84
+
85
+ Switch to the project directory. For example:
86
+
87
+ ```
88
+ cd example_magento_migration
89
+ ```
90
+
91
+ As with any Ruby project, you need to run the following command before you create and run any custom
92
+ pipeline stages:
93
+
94
+ ```
95
+ $ bundle install
96
+ ```
97
+
98
+
99
+ ## Convert records from the third-party platform to Shopify
100
+
101
+ Run `shopify_transporter` and use the `convert` command to convert your objects from the
102
+ third-party platform to the Shopify format. For example, the following command converts a
103
+ JSON file that contains customers (*magento_customers.json*) from Magento to Shopify:
104
+
105
+ ```
106
+ shopify_transporter convert --config=config.yml --object=customer magento_customers.json > shopify_customers.csv
107
+ ```
108
+
109
+ In this example, the converted customer objects are saved to *shopify_customers.csv*. If errors occur during
110
+ the conversion, then they appear in your terminal.
111
+
112
+ ## Convert multiple files
113
+
114
+ To convert multiple files to Shopify, separate the file names with a space:
115
+
116
+ ```
117
+ shopify_transporter convert --config=config.yml --object=customer magento_customers_1.json magento_customers_2.json ...
118
+ ```
119
+
120
+ ## Configuration file (_config.yml_)
121
+
122
+ The configuraton file is generated when you create your conversion project. This file is specific to the
123
+ third-party platform that you are converting to Shopify.
124
+
125
+ Here's an example of a _config.yml_ file for converting customers from Magento:
126
+
127
+ ```
128
+ platform_type: Magento
129
+ object_types:
130
+ customer:
131
+ record_key: email
132
+ pipeline_stages:
133
+ - TopLevelAttributes
134
+ - AddressesAttribute
135
+ - Metafields:
136
+ type: all_platforms
137
+ params:
138
+ # Specify a custom namespace for your metafields with metafield_namespace.
139
+ # Uses migrated_data by default.
140
+ # metafield_namespace: migrated_data
141
+ metafields:
142
+ - website
143
+ - group
144
+ - free_trial_start_at
145
+ ```
146
+
147
+ ### record_key
148
+
149
+ The `config.yml` file allows you to define the object type to convert. An object type needs a `record_key`,
150
+ whose values must be unique among the other records in the file. For example, the default `record_key` for
151
+ customers is the customer's email address.
152
+
153
+ ```
154
+ platform_type: Magento
155
+ object_types:
156
+ customer:
157
+ record_key: email
158
+ ...
159
+ ```
160
+
161
+ When you run `shopify_transporter` with the `convert` command, the input (third-party platform) files are
162
+ read one-by-one and line-by-line. Each object in the input file must have a `record_key` value. Rows that
163
+ have the same `record_key` value are considered to be part of the same object.
164
+
165
+ ### Pipeline_stages
166
+
167
+ The Transporter tool processes the input (third-party platform objects) through a series of pipeline
168
+ stages. These stages, and the order in which they are to be processed, are defined in the `config.yml` file.
169
+
170
+ In the example below, there are two pipeline stages for converting objects from Magento to
171
+ Shopify: TopLevelAttributes and AddressAttributes.
172
+
173
+ ```
174
+ platform_type: Magento
175
+ object_types:
176
+ customer:
177
+ record_key: email
178
+ pipeline_stages:
179
+ - TopLevelAttributes
180
+ - AddressesAttribute
181
+ ```
182
+
183
+ Each pipeline stage's _convert_ method receives the row currently being processed, as well as the
184
+ current state of the corresponding Shopify object being converted. The method's responsibility is to
185
+ inject into the Shopify object the relevant attributes from the input row.
186
+
187
+ The role of a pipeline stage is to examine the input rows and populate attributes on the Shopify object.
188
+ For example, the `TopLevelAttributes` stage of a Magento customer migration looks for a column
189
+ named `firstname` on the input, and then populates the Shopify object accordingly:
190
+
191
+
192
+ ```
193
+ record['first_name'] = input['firstname']
194
+ ```
195
+
196
+ Any changes that are made to the this record in a pipeline stage are permanent to the Shopify record
197
+ associated with the `record_key`.
198
+
199
+ The next pipeline stage that receives this record, receives the same input and the existing record which
200
+ consist of:
201
+
202
+ ```
203
+ {
204
+ 'first_name' => 'John',
205
+ }
206
+ ```
207
+
208
+ Existing pipeline stages and the attributes are populated below.
209
+
210
+ ### Magento v1.x customer
211
+
212
+
213
+ ### AddressesAttribute
214
+
215
+ Addresses are built from the `shipping_` and `billing_` prefixed fields. The Shopify object's `addresses`
216
+ attribute is an array that consists of the `shipping_` prefixed attributes as the first address, and
217
+ the `billing_` prefixed attributes as the second.
218
+
219
+ ```
220
+ {
221
+ 'addresses': [
222
+ {
223
+ 'first_name': ...,
224
+ 'last_name': ...,
225
+ 'address1': ...,
226
+ 'address2': ...,
227
+ 'city': ...,
228
+ 'province': ...,
229
+ 'country_code': ...,
230
+ 'zip': ...,
231
+ 'company': ...,
232
+ 'phone': ...,
233
+ },
234
+ ]
235
+ }
236
+ ```
237
+
238
+ ### Metafields
239
+
240
+ See the metafields section in the All Platforms section below.
241
+
242
+ ### All Platforms
243
+
244
+ Some stages are a little more versatile and can support input rows from arbitrary third-party platforms. These stages
245
+ are usually extensible through defining parameters in the `config.yml`.
246
+
247
+ #### Metafields
248
+
249
+ The `convert` command converts the most popular metafields or custom fields from the third-party platforms into
250
+ Shopify [metafields](https://help.shopify.com/manual/products/metafields). You can view the default metafields and
251
+ add others in your `config.yml` file.
252
+
253
+ ## Adding customized stages
254
+
255
+ If the Shopify object that is generated is missing attributes or if a pipeline stage fails, it could be because there are
256
+ unexpected headers defined in the third-party CSV data.
257
+
258
+ To define your own customized stages, run the following command:
259
+
260
+ `shopify_transporter generate YourCustomStage --object customer`
261
+
262
+ A file named `your_custom_stage.rb` is added to the `lib/magento/custom_pipeline_stages` directory. You can modify this file to add your custom conversion logic.
263
+
264
+ ## Limitations
265
+
266
+ The `convert` command currently only converts customer and order JSON objects that have been exported by SOAP API
267
+ from Magento 1.x.
268
+
269
+ ## Running unit tests
270
+
271
+ We use rspec to run our test suite:
272
+ `bundle exec rspec`
273
+
274
+ ## Running the linter
275
+
276
+ It's important that all of the code introduced passes linting. To run it manually:
277
+ `bundle exec rake rubocop`
278
+ To automatically resolve any basic linting issues:
279
+ `bundle exec rake rubocop:autocorrect`
280
+
281
+ # Submitting Issues
282
+
283
+ Please open an issue here if you encounter a specific bug with this library or if something is documented
284
+ incorrectly.
285
+
286
+ When filing an issue, please ensure that:
287
+
288
+ - The issue is not already covered in another open issue
289
+ - The issue does not contain any confidential or personally identifying information
290
+ - The issue is specifically regarding the `shopify_transporter` gem and not related to the Transporter App.
data/RELEASING ADDED
@@ -0,0 +1,16 @@
1
+ Releasing ShopifyTransporter
2
+
3
+ 1. Check the Semantic Versioning page for info on how to version the new release: http://semver.org
4
+ 2. Update the version of ShopifyTransporter in lib/shopify_transporter/version.rb
5
+ 3. Add a CHANGELOG entry for the new release with the date
6
+ 4. Commit the changes with a commit message like "Packaging for release X.Y.Z"
7
+ 5. Tag the release with the version (Leave REV blank for HEAD or provide a SHA)
8
+ $ git tag vX.Y.Z REV
9
+ 6. Push out the changes
10
+ $ git push
11
+ 7. Push out the tags
12
+ $ git push --tags
13
+ 8. Build the new .gem from the updated .gemspec
14
+ $ gem build shopify_transporter.gemspec
15
+ 9. Publish the Gem to gemcutter
16
+ $ gem push shopify_transporter-X.Y.Z.gem
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ RuboCop::RakeTask.new
8
+
9
+ task default: :spec
10
+
11
+ Dir.glob('lib/tasks/*.rake').each { |r| import r }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift("#{__dir__}/../lib")
5
+
6
+ require 'thor'
7
+ require 'thor/group'
8
+ require 'shopify_transporter'
9
+ require 'yaml'
10
+
11
+ module ShopifyTransporter
12
+ class CLI < Thor
13
+ def self.exit_on_failure?
14
+ true
15
+ end
16
+
17
+ method_option :config, type: :string, default: 'config.yml',
18
+ desc: "The config file that lists the conversions to run."
19
+ method_option :object, type: :string, required: true,
20
+ desc: "The object type (customer, product, or order) to convert.",
21
+ enum: %w(customer product order)
22
+
23
+ desc 'convert FILE_NAMES', 'Converts objects into a Shopify-format. (accepts a list of space-separated files).'
24
+ def convert(*files)
25
+ conversion_error('Missing file name at the end of the command.') if files.empty?
26
+ migration_tool = TransporterTool.new(*files, options[:config], options[:object])
27
+ migration_tool.run
28
+ rescue TransporterTool::ConversionError => error
29
+ conversion_error(error.message)
30
+ end
31
+
32
+ no_commands do
33
+ def conversion_error(message)
34
+ say("Conversion error: #{message}", :red)
35
+ exit 1
36
+ end
37
+ end
38
+
39
+ register ShopifyTransporter::New,
40
+ 'new', 'new PROJECTNAME --platform=PLATFORM', 'Generate a project for the platform'
41
+ register ShopifyTransporter::Generate,
42
+ 'generate', 'generate STAGE_NAME --object=OBJECT',
43
+ 'Generate a custom pipeline stage for the object'
44
+ end
45
+ end
46
+
47
+ ShopifyTransporter::CLI.start(ARGV)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyTransporter
3
+ class BaseGroup < Thor::Group
4
+ include Thor::Actions
5
+ argument :name
6
+
7
+ SUPPORTED_PLATFORMS_MAPPING = {
8
+ 'Magento' => 'magento',
9
+ }
10
+
11
+ def name_components
12
+ @name_components ||= name.scan(/[[:alnum:]]+/)
13
+ end
14
+
15
+ def config_filename
16
+ 'config.yml'
17
+ end
18
+
19
+ class << self
20
+ def source_root
21
+ File.expand_path('../../', File.dirname(__FILE__))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+ require_relative './base_group.rb'
3
+
4
+ module ShopifyTransporter
5
+ class Generate < BaseGroup
6
+ class_option :object, type: :string, required: :true,
7
+ enum: %w(customer order), aliases: '-O',
8
+ desc: 'The object to add to the pipeline stage'
9
+
10
+ def object_type
11
+ @object_type ||= options[:object].capitalize
12
+ end
13
+
14
+ def validate_config_exists
15
+ unless File.exist?(config_filename)
16
+ say("Cannot find config.yml at project root", :red)
17
+ exit 1
18
+ end
19
+ end
20
+
21
+ def pipeline_snake_name
22
+ name_components.map(&:underscore).join('_')
23
+ end
24
+
25
+ def pipeline_class
26
+ @pipeline_class ||= @name_components.map(&:classify).join('')
27
+ end
28
+
29
+ def platform
30
+ platform_type = YAML.load_file(config_filename)['platform_type']
31
+ @platform_file_path ||= SUPPORTED_PLATFORMS_MAPPING[platform_type]
32
+ @platform ||= platform_type.underscore
33
+ end
34
+
35
+ def generate_stage
36
+ template(
37
+ "templates/custom_stage.tt",
38
+ "lib/custom_pipeline_stages/#{pipeline_snake_name}.rb"
39
+ )
40
+ end
41
+
42
+ def add_stage_to_config
43
+ config_file = YAML.load_file(config_filename)
44
+ unless class_included_in?(config_file)
45
+ pipeline_class_hash = { @pipeline_class.to_s => nil, "type" => "custom" }
46
+ config_file['object_types'][@object_type.downcase]['pipeline_stages'] << pipeline_class_hash
47
+ File.open(config_filename, 'w') { |f| f.write config_file.to_yaml }
48
+ say('Updated config.yml with the new pipeline stage', :green)
49
+ return
50
+ end
51
+
52
+ say("Warning: The pipeline stage #{@pipeline_class} already exists in the config.yml", :blue)
53
+ end
54
+
55
+ private
56
+
57
+ def class_included_in?(config_file)
58
+ hash_stages = config_file['object_types'][@object_type.downcase]['pipeline_stages'].select do |stage|
59
+ stage.is_a? Hash
60
+ end
61
+ hash_stages.find { |h| h.keys.include? @pipeline_class.to_s }.present?
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ require_relative './base_group.rb'
3
+
4
+ module ShopifyTransporter
5
+ class New < BaseGroup
6
+ include Thor::Actions
7
+ class_option :platform, type: :string, required: :true, enum: %w(magento)
8
+
9
+ def snake_name
10
+ @snake_name ||= name_components.map(&:downcase).join("_")
11
+ end
12
+
13
+ def platform
14
+ @platform ||= options[:platform]
15
+ end
16
+
17
+ def generate_config
18
+ template("templates/#{@platform}/config.tt", "#{@snake_name}/config.yml")
19
+ template("templates/gemfile.tt", "#{@snake_name}/Gemfile")
20
+ empty_directory("#{@snake_name}/lib/custom_pipeline_stages")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'generators/generate.rb'
3
+ require_relative 'generators/new.rb'
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../stage'
3
+ require_relative '../../shopify'
4
+
5
+ module ShopifyTransporter
6
+ module Pipeline
7
+ module AllPlatforms
8
+ class Metafields < ShopifyTransporter::Pipeline::Stage
9
+ def convert(input, record)
10
+ raise 'Metafields not specified.' unless metafields_specified(params)
11
+
12
+ accumulator = MetafieldAttributesAccumulator.new(
13
+ initial_value: record,
14
+ metafields_to_extract: params['metafields'],
15
+ metafield_namespace: params['metafield_namespace'] || ShopifyTransporter::DEFAULT_METAFIELD_NAMESPACE,
16
+ )
17
+ accumulator.accumulate(input)
18
+ end
19
+
20
+ private
21
+
22
+ def metafields_specified(params)
23
+ params['metafields'].class == Array && params['metafields'].any?
24
+ end
25
+
26
+ class MetafieldAttributesAccumulator < ShopifyTransporter::Shopify::AttributesAccumulator
27
+ attr_reader :metafields_to_extract, :metafield_namespace
28
+
29
+ def initialize(initial_value:, metafields_to_extract:, metafield_namespace:)
30
+ @metafields_to_extract = metafields_to_extract
31
+ @metafield_namespace = metafield_namespace
32
+ super(initial_value)
33
+ end
34
+
35
+ private
36
+
37
+ def input_applies?(input)
38
+ attributes_present?(input, *metafields_to_extract)
39
+ end
40
+
41
+ def attributes_from(input)
42
+ {
43
+ 'metafields' => extract_metafields(input),
44
+ }
45
+ end
46
+
47
+ def extract_metafields(input)
48
+ metafields_to_extract.map do |metafield_key|
49
+ next if input[metafield_key].nil?
50
+ shopify_metafield_hash(
51
+ key: metafield_key,
52
+ value: input[metafield_key],
53
+ namespace: metafield_namespace
54
+ )
55
+ end.compact
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require 'shopify_transporter/pipeline/stage'
3
+ require 'shopify_transporter/shopify'
4
+
5
+ module ShopifyTransporter
6
+ module Pipeline
7
+ module Magento
8
+ module Customer
9
+ class AddressesAttribute < Pipeline::Stage
10
+ def convert(input, record)
11
+ addresses = magento_addresses_from(input)
12
+ return unless addresses
13
+
14
+ record.merge!(convert_addresses(addresses))
15
+ end
16
+
17
+ private
18
+
19
+ COLUMN_MAPPING = {
20
+ 'firstname' => 'first_name',
21
+ 'lastname' => 'last_name',
22
+ 'street' => 'address1',
23
+ 'city' => 'city',
24
+ 'region' => 'province',
25
+ 'country_id' => 'country_code',
26
+ 'postcode' => 'zip',
27
+ 'company' => 'company',
28
+ 'telephone' => 'phone',
29
+ }
30
+
31
+ def convert_addresses(addresses)
32
+ {
33
+ 'addresses' => [
34
+ *addresses_from(addresses.select { |address| address['is_default_shipping'] }),
35
+ *addresses_from(addresses.select { |address| !address['is_default_shipping'] }),
36
+ ].compact,
37
+ }
38
+ end
39
+
40
+ def addresses_from(addresses)
41
+ return nil unless addresses.present?
42
+
43
+ addresses.map do |address|
44
+ COLUMN_MAPPING.each_with_object({}) do |(key, value), obj|
45
+ obj[value] = address[key] if address[key]
46
+ end
47
+ end
48
+ end
49
+
50
+ def magento_addresses_from(input)
51
+ addresses = input.dig('address_list', 'customer_address_list_response', 'result', 'item')
52
+ return nil unless addresses
53
+
54
+ addresses.is_a?(Array) ? addresses : [addresses]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end