shopify_transporter 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +39 -31
  4. data/exe/shopify_transporter +33 -2
  5. data/lib/shopify_transporter/exporters/exporter.rb +103 -0
  6. data/lib/shopify_transporter/exporters/magento/customer_exporter.rb +44 -0
  7. data/lib/shopify_transporter/exporters/magento/database_cache.rb +21 -0
  8. data/lib/shopify_transporter/exporters/magento/database_table_exporter.rb +68 -0
  9. data/lib/shopify_transporter/exporters/magento/magento_exporter.rb +28 -0
  10. data/lib/shopify_transporter/exporters/magento/order_exporter.rb +46 -0
  11. data/lib/shopify_transporter/exporters/magento/product_exporter.rb +129 -0
  12. data/lib/shopify_transporter/exporters/magento/product_options.rb +98 -0
  13. data/lib/shopify_transporter/exporters/magento/soap.rb +101 -0
  14. data/lib/shopify_transporter/exporters/magento/sql.rb +38 -0
  15. data/lib/shopify_transporter/exporters/magento.rb +5 -0
  16. data/lib/shopify_transporter/exporters.rb +3 -0
  17. data/lib/shopify_transporter/generators/generate.rb +4 -4
  18. data/lib/shopify_transporter/generators/new.rb +2 -2
  19. data/lib/shopify_transporter/pipeline/magento/order/line_items.rb +7 -5
  20. data/lib/shopify_transporter/pipeline/magento/product/top_level_attributes.rb +73 -4
  21. data/lib/shopify_transporter/pipeline/magento/product/top_level_variant_attributes.rb +50 -0
  22. data/lib/shopify_transporter/pipeline/magento/product/variant_attributes.rb +28 -0
  23. data/lib/shopify_transporter/pipeline/magento/product/variant_image.rb +50 -0
  24. data/lib/shopify_transporter/record_builder/product_record_builder.rb +33 -0
  25. data/lib/shopify_transporter/{record_builder.rb → record_builder/record_builder.rb} +0 -0
  26. data/lib/shopify_transporter/shopify/attributes_helpers.rb +1 -1
  27. data/lib/shopify_transporter/shopify/order.rb +1 -1
  28. data/lib/shopify_transporter/shopify/product.rb +4 -3
  29. data/lib/shopify_transporter/shopify/record.rb +1 -1
  30. data/lib/shopify_transporter/version.rb +1 -1
  31. data/lib/shopify_transporter.rb +15 -7
  32. data/lib/tasks/factory_bot.rake +1 -1
  33. data/lib/templates/magento/config.tt +19 -0
  34. data/shopify_transporter.gemspec +3 -0
  35. metadata +61 -3
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'magento/magento_exporter.rb'
4
+ require_relative 'magento/soap.rb'
5
+ require_relative 'magento/sql.rb'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'exporters/exporter'
3
+ require_relative 'exporters/magento'
@@ -4,7 +4,7 @@ require_relative './base_group.rb'
4
4
  module ShopifyTransporter
5
5
  class Generate < BaseGroup
6
6
  class_option :object, type: :string, required: :true,
7
- enum: %w(customer order), aliases: '-O',
7
+ enum: %w(customer order product), aliases: '-O',
8
8
  desc: 'The object to add to the pipeline stage'
9
9
 
10
10
  def object_type
@@ -13,7 +13,7 @@ module ShopifyTransporter
13
13
 
14
14
  def validate_config_exists
15
15
  unless File.exist?(config_filename)
16
- say("Cannot find config.yml at project root", :red)
16
+ say('Cannot find config.yml at project root', :red)
17
17
  exit 1
18
18
  end
19
19
  end
@@ -34,7 +34,7 @@ module ShopifyTransporter
34
34
 
35
35
  def generate_stage
36
36
  template(
37
- "templates/custom_stage.tt",
37
+ 'templates/custom_stage.tt',
38
38
  "lib/custom_pipeline_stages/#{pipeline_snake_name}.rb"
39
39
  )
40
40
  end
@@ -42,7 +42,7 @@ module ShopifyTransporter
42
42
  def add_stage_to_config
43
43
  config_file = YAML.load_file(config_filename)
44
44
  unless class_included_in?(config_file)
45
- pipeline_class_hash = { @pipeline_class.to_s => nil, "type" => "custom" }
45
+ pipeline_class_hash = { @pipeline_class.to_s => nil, 'type' => 'custom' }
46
46
  config_file['object_types'][@object_type.downcase]['pipeline_stages'] << pipeline_class_hash
47
47
  File.open(config_filename, 'w') { |f| f.write config_file.to_yaml }
48
48
  say('Updated config.yml with the new pipeline stage', :green)
@@ -7,7 +7,7 @@ module ShopifyTransporter
7
7
  class_option :platform, type: :string, required: :true, enum: %w(magento)
8
8
 
9
9
  def snake_name
10
- @snake_name ||= name_components.map(&:downcase).join("_")
10
+ @snake_name ||= name_components.map(&:downcase).join('_')
11
11
  end
12
12
 
13
13
  def platform
@@ -16,7 +16,7 @@ module ShopifyTransporter
16
16
 
17
17
  def generate_config
18
18
  template("templates/#{@platform}/config.tt", "#{@snake_name}/config.yml")
19
- template("templates/gemfile.tt", "#{@snake_name}/Gemfile")
19
+ template('templates/gemfile.tt', "#{@snake_name}/Gemfile")
20
20
  empty_directory("#{@snake_name}/lib/custom_pipeline_stages")
21
21
  end
22
22
  end
@@ -42,11 +42,13 @@ module ShopifyTransporter
42
42
  end
43
43
 
44
44
  def tax_lines(item)
45
- {
46
- title: 'Tax',
47
- price: item['tax_amount'],
48
- rate: item['tax_percent'],
49
- }.stringify_keys
45
+ [
46
+ {
47
+ title: 'Tax',
48
+ price: item['tax_amount'],
49
+ rate: item['tax_percent'],
50
+ }.stringify_keys,
51
+ ]
50
52
  end
51
53
  end
52
54
  end
@@ -8,25 +8,94 @@ module ShopifyTransporter
8
8
  module Product
9
9
  class TopLevelAttributes < Pipeline::Stage
10
10
  def convert(hash, record)
11
+ warn_if_too_many_options(hash)
11
12
  accumulator = TopLevelAttributesAccumulator.new(record)
12
13
  accumulator.accumulate(hash)
13
14
  end
14
15
 
16
+ private
17
+
18
+ MAX_OPTION_COUNT = 3
19
+
20
+ def too_many_options?(input)
21
+ input["option#{MAX_OPTION_COUNT + 1}_name"].present?
22
+ end
23
+
24
+ def warn_if_too_many_options(input)
25
+ if too_many_options?(input)
26
+ $stderr.puts "Warning: Product #{input['product_id']} has too many options."\
27
+ " Only the first #{MAX_OPTION_COUNT} options will be converted."
28
+ end
29
+ end
30
+
15
31
  class TopLevelAttributesAccumulator < Shopify::AttributesAccumulator
16
32
  COLUMN_MAPPING = {
17
- 'sku' => 'sku',
18
33
  'name' => 'title',
19
34
  'description' => 'body_html',
35
+ 'url_key' => 'handle',
20
36
  }
21
37
 
22
38
  private
23
39
 
24
- def input_applies?(_input)
25
- true
40
+ def input_applies?(input)
41
+ true unless input['parent_id'].present?
26
42
  end
27
43
 
28
44
  def attributes_from(input)
29
- map_from_key_to_val(COLUMN_MAPPING, input)
45
+ attributes = map_from_key_to_val(COLUMN_MAPPING, input)
46
+ attributes['published'] = published?(input)
47
+ attributes['published_scope'] = published?(input) ? 'global' : ''
48
+ attributes['published_at'] = published?(input) ? input['updated_at'] : ''
49
+ append_images_to_current_record!(input) if input['images'].present?
50
+ attributes['tags'] = product_tags(input) if input['tags'].present?
51
+ attributes['options'] = product_options(input) if input['option1_name'].present?
52
+ attributes
53
+ end
54
+
55
+ def published?(input)
56
+ input['visibility'].present? && input['visibility'].to_i != 1
57
+ end
58
+
59
+ def apply_image_column_mapping(input_image)
60
+ {
61
+ 'src' => input_image['url'],
62
+ 'position' => input_image['position'],
63
+ 'alt' => image_alt_text(input_image['label']),
64
+ }
65
+ end
66
+
67
+ def construct_images(input)
68
+ return [apply_image_column_mapping(input['images'])] if input['images'].is_a?(Hash)
69
+ images = input['images'].map do |image|
70
+ apply_image_column_mapping(image).compact
71
+ end
72
+ images.sort_by { |image| image['position'] }
73
+ end
74
+
75
+ def append_images_to_current_record!(input)
76
+ images = construct_images(input)
77
+ if @output['images'].present?
78
+ @output['images'].concat(images)
79
+ else
80
+ @output['images'] = images
81
+ end
82
+ end
83
+
84
+ def product_options(input)
85
+ %w(option1_name option2_name option3_name).map do |option_name|
86
+ { 'name' => input[option_name] }
87
+ end
88
+ end
89
+
90
+ def image_alt_text(label)
91
+ if label.is_a? String
92
+ label
93
+ end
94
+ end
95
+
96
+ def product_tags(input)
97
+ return input['tags']['name'] if input['tags'].is_a?(Hash)
98
+ input['tags'].map { |tag| tag['name'] }.join(', ')
30
99
  end
31
100
  end
32
101
  end
@@ -0,0 +1,50 @@
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 Product
9
+ class TopLevelVariantAttributes < Pipeline::Stage
10
+ def convert(hash, record)
11
+ return unless input_applies?(hash)
12
+ simple_product_in_magento_format = record['variants'].select do |product|
13
+ product['product_id'] == hash['product_id']
14
+ end.first
15
+ accumulator = TopLevelVariantAttributesAccumulator.new(simple_product_in_magento_format)
16
+ accumulator.accumulate(hash)
17
+ end
18
+
19
+ def input_applies?(input)
20
+ true unless input['parent_id'].nil?
21
+ end
22
+
23
+ class TopLevelVariantAttributesAccumulator < Shopify::AttributesAccumulator
24
+ COLUMN_MAPPING = {
25
+ 'sku' => 'sku',
26
+ 'weight' => 'weight',
27
+ 'price' => 'price',
28
+ 'inventory_quantity' => 'inventory_qty',
29
+ }
30
+
31
+ def accumulate(input)
32
+ accumulate_attributes(map_from_key_to_val(COLUMN_MAPPING, input))
33
+ accumulate_attributes(variant_options(input))
34
+ end
35
+
36
+ private
37
+
38
+ def variant_options(input)
39
+ {
40
+ 'option1' => input['option1_value'],
41
+ 'option2' => input['option2_value'],
42
+ 'option3' => input['option3_value'],
43
+ }.compact
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,28 @@
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 Product
9
+ class VariantAttributes < Pipeline::Stage
10
+ def convert(hash, record)
11
+ accumulator = VariantAttributesAccumulator.new(record)
12
+ accumulator.accumulate(hash)
13
+ end
14
+
15
+ class VariantAttributesAccumulator < Shopify::AttributesAccumulator
16
+ def accumulate(current_product)
17
+ if current_product['parent_id'].present?
18
+ @output['variants'] ||= []
19
+ @output['variants'] << current_product
20
+ @output
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
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 Product
9
+ class VariantImage < Pipeline::Stage
10
+ def convert(hash, record)
11
+ return unless input_applied?(hash)
12
+ add_variant_image!(hash, record)
13
+ add_variant_image_to_parent_images!(hash, record)
14
+ record
15
+ end
16
+
17
+ def input_applied?(input)
18
+ input['images'].present? && input['parent_id'].present?
19
+ end
20
+
21
+ def current_variant(input, record)
22
+ record['variants'].select do |variant|
23
+ variant['product_id'] == input['product_id']
24
+ end.first
25
+ end
26
+
27
+ def add_variant_image!(input, record)
28
+ current_variant(input, record).merge!(
29
+ 'variant_image' => {
30
+ 'src' => variant_image_url(input),
31
+ }
32
+ )
33
+ end
34
+
35
+ def variant_image_url(input)
36
+ return input['images']['url'] if input['images'].is_a?(Hash)
37
+ input['images'].sort_by { |image| image['position'] }.first['url']
38
+ end
39
+
40
+ def add_variant_image_to_parent_images!(input, record)
41
+ record['images'] ||= []
42
+ record['images'] << {
43
+ 'src' => variant_image_url(input),
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ module ShopifyTransporter
4
+ class ProductRecordBuilder < RecordBuilder
5
+ def initialize(key_name, key_required)
6
+ super(key_name, key_required)
7
+ end
8
+
9
+ def build(input)
10
+ validate_related(input)
11
+
12
+ if has_parent?(input)
13
+ yield parent_record(input)
14
+ return
15
+ end
16
+
17
+ yield record_from(input)
18
+ end
19
+
20
+ private
21
+
22
+ def has_parent?(product)
23
+ product['parent_id'].present?
24
+ end
25
+
26
+ def parent_record(product)
27
+ parent_id = product['parent_id']
28
+ record = @instances[parent_id] ||= {}
29
+ record['variants'] ||= []
30
+ @last_record = record
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require "active_support/core_ext/string"
2
+ require 'active_support/core_ext/string'
3
3
  module ShopifyTransporter
4
4
  module Shopify
5
5
  module AttributesHelpers
@@ -61,7 +61,7 @@ module ShopifyTransporter
61
61
  company name phone first_name last_name address1 address2 city province province_code zip country country_code
62
62
  )
63
63
 
64
- LINE_ITEM_PREFIX = "lineitem_"
64
+ LINE_ITEM_PREFIX = 'lineitem_'
65
65
 
66
66
  LINE_ITEM_ATTRIBUTES = %w(
67
67
  name quantity price discount compare_at_price sku requires_shipping taxable fulfillment_status
@@ -18,7 +18,7 @@ module ShopifyTransporter
18
18
  VARIANT_ATTRIBUTES = %w(
19
19
  sku grams inventory_tracker inventory_qty inventory_policy
20
20
  fulfillment_service price compare_at_price requires_shipping
21
- taxable barcode weight_unit tax_code
21
+ taxable barcode weight weight_unit tax_code
22
22
  ).freeze
23
23
 
24
24
  class << self
@@ -33,7 +33,8 @@ module ShopifyTransporter
33
33
  'Metafield Value Type', 'Variant Grams', 'Variant Inventory Tracker', 'Variant Inventory Qty',
34
34
  'Variant Inventory Policy', 'Variant Fulfillment Service', 'Variant Price', 'Variant Compare At Price',
35
35
  'Variant Requires Shipping', 'Variant Taxable', 'Variant Barcode', 'Image Attachment', 'Image Src',
36
- 'Image Position', 'Image Alt Text', 'Variant Image', 'Variant Weight Unit', 'Variant Tax Code'
36
+ 'Image Position', 'Image Alt Text', 'Variant Image', 'Variant Weight', 'Variant Weight Unit',
37
+ 'Variant Tax Code'
37
38
  ].to_csv
38
39
  end
39
40
 
@@ -95,9 +96,9 @@ module ShopifyTransporter
95
96
 
96
97
  def image_row_values
97
98
  return [] if record_hash['images'].blank?
99
+
98
100
  record_hash['images'].map do |image_hash|
99
101
  image = {
100
- 'image_attachment' => image_hash['attachment'],
101
102
  'image_src' => image_hash['src'],
102
103
  'image_position' => image_hash['position'],
103
104
  'image_alt_text' => image_hash['alt'],
@@ -2,7 +2,7 @@
2
2
  module ShopifyTransporter
3
3
  module Shopify
4
4
  class Record
5
- METAFIELD_PREFIX = "metafield_"
5
+ METAFIELD_PREFIX = 'metafield_'
6
6
 
7
7
  METAFIELD_ATTRIBUTES = %w(namespace key value value_type).freeze
8
8
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ShopifyTransporter
3
- VERSION = "1.0.0"
3
+ VERSION = '2.0.0'
4
4
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  require 'thor'
3
+
3
4
  require_relative 'shopify_transporter/generators.rb'
5
+ require_relative 'shopify_transporter/exporters.rb'
4
6
  require_relative 'shopify_transporter/pipeline.rb'
5
7
  require_relative 'shopify_transporter/shopify.rb'
6
- require_relative 'shopify_transporter/record_builder.rb'
8
+ require_relative 'shopify_transporter/record_builder/record_builder.rb'
9
+ require_relative 'shopify_transporter/record_builder/product_record_builder'
7
10
  Dir["#{Dir.pwd}/lib/custom_pipeline_stages/**/*.rb"].each { |f| require f }
8
11
 
9
12
  module ShopifyTransporter
@@ -149,11 +152,11 @@ class TransporterTool
149
152
  def stage_class_from(name, type)
150
153
  class_name = stage_classname(name, type)
151
154
  klass = begin
152
- k = Object.const_get(class_name)
153
- k.is_a?(Class) && k
154
- rescue NameError
155
- nil
156
- end
155
+ k = Object.const_get(class_name)
156
+ k.is_a?(Class) && k
157
+ rescue NameError
158
+ nil
159
+ end
157
160
  raise StageNotFoundError, name unless class_exists?(klass)
158
161
  klass
159
162
  end
@@ -232,7 +235,12 @@ class TransporterTool
232
235
 
233
236
  def build_classes_based_on_config
234
237
  @record_class = Object.const_get("ShopifyTransporter::Shopify::#{@object_type.capitalize}")
235
- @record_builder = ShopifyTransporter::RecordBuilder.new(
238
+ record_builder_class = if @object_type.capitalize == 'Product'
239
+ ShopifyTransporter::ProductRecordBuilder
240
+ else
241
+ ShopifyTransporter::RecordBuilder
242
+ end
243
+ @record_builder = record_builder_class.new(
236
244
  record_key_from_config, key_required_from_config
237
245
  )
238
246
  end