e_plat 0.3.0 → 0.4.1
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.
- checksums.yaml +4 -4
- data/README.md +631 -131
- data/lib/active_resource/connection_error.rb +29 -0
- data/lib/active_resource/formats.rb +27 -0
- data/lib/current.rb +1 -1
- data/lib/e_plat/client/default_request_args.rb +27 -3
- data/lib/e_plat/client.rb +114 -87
- data/lib/e_plat/connection.rb +4 -0
- data/lib/e_plat/mapping/base.rb +119 -12
- data/lib/e_plat/mapping/bigcommerce/v_3/metafield.rb +62 -0
- data/lib/e_plat/mapping/bigcommerce/v_3/order/billing_address.rb +14 -0
- data/lib/e_plat/mapping/bigcommerce/v_3/order/line_item.rb +85 -0
- data/lib/e_plat/mapping/bigcommerce/v_3/order/shipping_address.rb +73 -0
- data/lib/e_plat/mapping/bigcommerce/v_3/order.rb +160 -0
- data/lib/e_plat/mapping/bigcommerce/v_3/product/image.rb +12 -12
- data/lib/e_plat/mapping/bigcommerce/v_3/product/variant.rb +1 -1
- data/lib/e_plat/mapping/bigcommerce/v_3/script_tag.rb +78 -0
- data/lib/e_plat/mapping/bigcommerce/v_3/shop.rb +11 -6
- data/lib/e_plat/mapping/bigcommerce/v_3/webhook.rb +54 -0
- data/lib/e_plat/mapping/request_body_root.rb +38 -0
- data/lib/e_plat/mapping/shopify/v_2024_01/metafield.rb +26 -0
- data/lib/e_plat/mapping/shopify/v_2024_01/order/billing_address.rb +14 -0
- data/lib/e_plat/mapping/shopify/v_2024_01/order/shipping_address.rb +30 -0
- data/lib/e_plat/mapping/shopify/{v_2022_07/product.rb → v_2024_01/order.rb} +3 -3
- data/lib/e_plat/mapping/shopify/{v_2022_07 → v_2024_01}/product/image.rb +1 -1
- data/lib/e_plat/mapping/shopify/{v_2022_07 → v_2024_01}/product/variant.rb +2 -1
- data/lib/e_plat/mapping/shopify/v_2024_01/product.rb +26 -0
- data/lib/e_plat/mapping/shopify/v_2024_01/script_tag.rb +26 -0
- data/lib/e_plat/mapping/shopify/{v_2022_07 → v_2024_01}/shop.rb +1 -1
- data/lib/e_plat/mapping/shopify/v_2024_01/webhook.rb +29 -0
- data/lib/e_plat/mapping/virtual_collection/base.rb +14 -0
- data/lib/e_plat/mapping/virtual_collection/bigcommerce/order_line_items.rb +297 -0
- data/lib/e_plat/mapping.rb +3 -3
- data/lib/e_plat/resource/attribute_interface.rb +28 -28
- data/lib/e_plat/resource/base.rb +107 -66
- data/lib/e_plat/resource/collection.rb +92 -0
- data/lib/e_plat/resource/concerns/aliases.rb +102 -18
- data/lib/e_plat/resource/concerns/dirty.rb +54 -0
- data/lib/e_plat/resource/concerns/metafieldable.rb +43 -0
- data/lib/e_plat/resource/concerns/overwrite_instance_methods.rb +108 -6
- data/lib/e_plat/resource/concerns/overwrite_request_methods.rb +73 -37
- data/lib/e_plat/resource/countable.rb +43 -0
- data/lib/e_plat/resource/metafield.rb +70 -0
- data/lib/e_plat/resource/order/Consignment.rb +8 -0
- data/lib/e_plat/resource/order/billing_address.rb +6 -0
- data/lib/e_plat/resource/order/fulfillment.rb +1 -0
- data/lib/e_plat/resource/order/line_item.rb +1 -0
- data/lib/e_plat/resource/order/shipping_address.rb +44 -0
- data/lib/e_plat/resource/order/shipping_line.rb +15 -14
- data/lib/e_plat/resource/order.rb +32 -0
- data/lib/e_plat/resource/paginated/link_headers.rb +42 -0
- data/lib/e_plat/resource/paginated/link_params.rb +26 -0
- data/lib/e_plat/resource/product/image.rb +6 -0
- data/lib/e_plat/resource/product/option.rb +1 -0
- data/lib/e_plat/resource/product/variant.rb +8 -12
- data/lib/e_plat/resource/product.rb +8 -2
- data/lib/e_plat/resource/script_tag.rb +56 -0
- data/lib/e_plat/resource/shop.rb +17 -13
- data/lib/e_plat/resource/shopify_only/recurring_application_charge/usage_charge.rb +32 -0
- data/lib/e_plat/resource/shopify_only/recurring_application_charge.rb +47 -0
- data/lib/e_plat/resource/webhook.rb +50 -0
- data/lib/e_plat/session.rb +10 -7
- data/lib/e_plat/type_coercer.rb +15 -17
- data/lib/e_plat/version.rb +1 -1
- data/lib/e_plat.rb +14 -4
- metadata +67 -7
- data/lib/e_plat/resource/order/customer.rb +0 -37
| @@ -0,0 +1,92 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EPlat
         | 
| 4 | 
            +
            	class Collection < ActiveResource::Collection
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            		def initialize(parsed = {})
         | 
| 7 | 
            +
            			super parsed
         | 
| 8 | 
            +
            		end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            		def next_page?
         | 
| 11 | 
            +
            			return unless pagination_available?
         | 
| 12 | 
            +
            			@next_url.present?
         | 
| 13 | 
            +
            		end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            		def previous_page?
         | 
| 16 | 
            +
            			return unless pagination_available?
         | 
| 17 | 
            +
            			@previous_url.present?
         | 
| 18 | 
            +
            		end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            		def next_page_info
         | 
| 21 | 
            +
            			return unless pagination_available?
         | 
| 22 | 
            +
            			extract_page_info(@next_url)
         | 
| 23 | 
            +
            		end
         | 
| 24 | 
            +
            	
         | 
| 25 | 
            +
            		def previous_page_info
         | 
| 26 | 
            +
            			return unless pagination_available?
         | 
| 27 | 
            +
            			extract_page_info(@previous_url)
         | 
| 28 | 
            +
            		end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            		def fetch_next_page
         | 
| 31 | 
            +
            			return unless pagination_available?
         | 
| 32 | 
            +
            			fetch_page(@next_url)
         | 
| 33 | 
            +
            		end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            		def fetch_previous_page
         | 
| 36 | 
            +
            			return unless pagination_available?
         | 
| 37 | 
            +
            			fetch_page(@previous_url)
         | 
| 38 | 
            +
            		end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            		
         | 
| 41 | 
            +
            		private
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            		def fetch_page(url)
         | 
| 44 | 
            +
            			return [] unless url.present?
         | 
| 45 | 
            +
            			url = "#{ resource_class.collection_path }#{ url }" if client.bigcommerce?
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            			resource_class.all(from: url)
         | 
| 48 | 
            +
            		end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            		def pagination_links
         | 
| 51 | 
            +
            			case client.platform
         | 
| 52 | 
            +
            			when :shopify
         | 
| 53 | 
            +
            				@pagination_links ||= EPlat::Paginated::LinkHeaders.new(
         | 
| 54 | 
            +
            					(full_response&.headers&.to_h || {})[:link]
         | 
| 55 | 
            +
            				)
         | 
| 56 | 
            +
            			when :bigcommerce
         | 
| 57 | 
            +
            				response    = JSON.parse(full_response.body)
         | 
| 58 | 
            +
            				return unless response.is_a? Hash
         | 
| 59 | 
            +
            				link_params = response.dig('meta', 'pagination', 'links')
         | 
| 60 | 
            +
            				return unless link_params.present?
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            				@pagination_links ||= EPlat::Paginated::LinkParams.new(
         | 
| 63 | 
            +
            					link_params
         | 
| 64 | 
            +
            				)
         | 
| 65 | 
            +
            			else
         | 
| 66 | 
            +
            				raise "Unknown platform"
         | 
| 67 | 
            +
            			end
         | 
| 68 | 
            +
            		end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            		def pagination_available?
         | 
| 71 | 
            +
            			@next_url 	  = pagination_links&.next_link&.url&.to_s
         | 
| 72 | 
            +
            			@previous_url = pagination_links&.previous_link&.url&.to_s
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            			@next_url.present? or @previous_url.present?
         | 
| 75 | 
            +
            		end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            		def extract_page_info(url)
         | 
| 78 | 
            +
            			return unless url.present?
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            			query_params = URI.decode_www_form(URI(url).query).to_h 
         | 
| 81 | 
            +
            			query_params[client.pagination_param]
         | 
| 82 | 
            +
            		end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
             | 
| 85 | 
            +
            		private
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            			def client
         | 
| 88 | 
            +
            				Current.e_plat_session
         | 
| 89 | 
            +
            			end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            	end
         | 
| 92 | 
            +
            end
         | 
| @@ -3,6 +3,7 @@ module EPlat | |
| 3 3 | 
             
            	module Concerns
         | 
| 4 4 | 
             
            		module Aliases
         | 
| 5 5 | 
             
            			extend ActiveSupport::Concern
         | 
| 6 | 
            +
             | 
| 6 7 | 
             
            			attr_accessor :type_coercer
         | 
| 7 8 |  | 
| 8 9 | 
             
            			def add_aliases!(aliases, type_schema)
         | 
| @@ -14,20 +15,27 @@ module EPlat | |
| 14 15 | 
             
            					args 		= action.values.first
         | 
| 15 16 | 
             
            					e_plat_key	= args[:e_plat_key]
         | 
| 16 17 | 
             
            					native_key	= args[:native_key]
         | 
| 17 | 
            -
            					
         | 
| 18 | 
            +
            					@is_virtual = args[:virtual_collection]
         | 
| 19 | 
            +
             | 
| 18 20 | 
             
            					case action_name
         | 
| 19 21 | 
             
            					when :alias_attribute
         | 
| 20 22 | 
             
            						add_to_instance! alias_mapped_getter(e_plat_key, native_key, proc: args[:custom_e_plat_getter])
         | 
| 21 23 | 
             
            						add_to_instance! alias_mapped_setter(e_plat_key, native_key, proc: args[:custom_native_setter])
         | 
| 24 | 
            +
            						# puts alias_mapped_setter(e_plat_key, native_key, proc: args[:custom_native_setter]) if e_plat_key == 'variant_id'
         | 
| 22 25 | 
             
            						processed << e_plat_key
         | 
| 23 26 | 
             
            					end
         | 
| 24 27 | 
             
            				end
         | 
| 25 28 |  | 
| 26 29 | 
             
            				type_schema.each do |e_plat_key, type|
         | 
| 27 30 | 
             
            					next if processed.include?(e_plat_key)
         | 
| 28 | 
            -
            					add_to_instance! mapped_getter(e_plat_key)
         | 
| 31 | 
            +
            					add_to_instance! mapped_getter(e_plat_key), @is_virtual
         | 
| 29 32 | 
             
            				end
         | 
| 30 33 |  | 
| 34 | 
            +
            				native_keys.each do |native_key|
         | 
| 35 | 
            +
            					next if processed.include?(native_key)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            					add_to_instance! native_setter(native_key), @is_virtual
         | 
| 38 | 
            +
            				end
         | 
| 31 39 | 
             
            			end
         | 
| 32 40 |  | 
| 33 41 |  | 
| @@ -39,7 +47,7 @@ module EPlat | |
| 39 47 |  | 
| 40 48 | 
             
            					<<-STRING
         | 
| 41 49 | 
             
            						def #{ e_plat_key }
         | 
| 42 | 
            -
            							current_value =  | 
| 50 | 
            +
            							current_value = #{ native_key_path(native_key) }
         | 
| 43 51 | 
             
            							#{ proc_line }
         | 
| 44 52 | 
             
            							type_coercer.type_value!('#{ e_plat_key }', current_value)
         | 
| 45 53 | 
             
            						end
         | 
| @@ -50,10 +58,15 @@ module EPlat | |
| 50 58 | 
             
            					proc_line = (proc) ? "new_value = #{ proc.strip }.call(new_value)" : nil
         | 
| 51 59 |  | 
| 52 60 | 
             
            					<<-STRING
         | 
| 53 | 
            -
            						def #{ | 
| 61 | 
            +
            						def #{e_plat_key}=(value)
         | 
| 54 62 | 
             
            							new_value = value
         | 
| 55 63 | 
             
            							#{proc_line}
         | 
| 56 | 
            -
             | 
| 64 | 
            +
             | 
| 65 | 
            +
            							unless new_value == #{ native_key_path(native_key) }
         | 
| 66 | 
            +
            								#{ native_key_path(native_key) } = new_value
         | 
| 67 | 
            +
            								attribute_will_change!('#{e_plat_key}')
         | 
| 68 | 
            +
            								#{ nested_attribute_will_change(native_key) if native_key&.include?('[') }
         | 
| 69 | 
            +
            							end
         | 
| 57 70 | 
             
            						end
         | 
| 58 71 | 
             
            					STRING
         | 
| 59 72 | 
             
            				end
         | 
| @@ -66,11 +79,77 @@ module EPlat | |
| 66 79 | 
             
            					STRING
         | 
| 67 80 | 
             
            				end
         | 
| 68 81 |  | 
| 69 | 
            -
            				def  | 
| 82 | 
            +
            				def native_setter(native_key)
         | 
| 83 | 
            +
            					<<-STRING
         | 
| 84 | 
            +
            						def #{native_key}=(value)
         | 
| 85 | 
            +
            							super
         | 
| 86 | 
            +
            							attribute_will_change!('#{native_key}')
         | 
| 87 | 
            +
            						end
         | 
| 88 | 
            +
            					STRING
         | 
| 89 | 
            +
            				end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            				def native_key_path(native_key)
         | 
| 92 | 
            +
            					segments = native_key.split(/[\[\]]+/).reject(&:empty?)
         | 
| 93 | 
            +
            					path_code = @is_virtual ? "self.mapping.virtual_collection.resource" : "self" 
         | 
| 94 | 
            +
            				  
         | 
| 95 | 
            +
            					segments.each do |segment|
         | 
| 96 | 
            +
            						if segment.match(/^\d+$/) # Check if the segment is a number (array index)
         | 
| 97 | 
            +
            							path_code += "[#{segment}]"
         | 
| 98 | 
            +
            						else
         | 
| 99 | 
            +
            							path_code += ".#{segment}"
         | 
| 100 | 
            +
            						end
         | 
| 101 | 
            +
            					end
         | 
| 102 | 
            +
            				  
         | 
| 103 | 
            +
            					path_code
         | 
| 104 | 
            +
            				end
         | 
| 105 | 
            +
            				
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            				# ".send('#{ native_key_parent }').attribute_will_change!('#{ native_key }')"
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            				def add_to_instance!(meta_programming_string, is_virtual=false)
         | 
| 110 | 
            +
            					# puts meta_programming_string if meta_programming_string.include?('note')
         | 
| 111 | 
            +
             | 
| 70 112 | 
             
            					self.instance_eval { eval meta_programming_string }
         | 
| 71 113 | 
             
            				end
         | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 114 | 
            +
             | 
| 115 | 
            +
            				# doesn't support two indexes in a row. i.e. [0][0]
         | 
| 116 | 
            +
            				def nested_attribute_will_change(native_key) # "[consignments][0][shipping][0][line_items][0][variant_id]"
         | 
| 117 | 
            +
            					ref_object = @is_virtual ? "mapping.virtual_collection.resource" : "self" 
         | 
| 118 | 
            +
            					parts = native_key.scan(/(\w+)|(\d+)/).flatten.compact # ["consignments", "0", "shipping", "0", "line_items", "0", "variant_id"]
         | 
| 119 | 
            +
            				
         | 
| 120 | 
            +
            					method_chain = ""
         | 
| 121 | 
            +
            					change_statements = []
         | 
| 122 | 
            +
            					
         | 
| 123 | 
            +
            					first_attribute = parts.find { |part| !is_index?(part) }
         | 
| 124 | 
            +
            					if first_attribute
         | 
| 125 | 
            +
            						change_statements << "#{ref_object}.attribute_will_change!('#{first_attribute}')"
         | 
| 126 | 
            +
            					end
         | 
| 127 | 
            +
            				
         | 
| 128 | 
            +
            					parts.each_with_index do |part, index|
         | 
| 129 | 
            +
            						method_chain += is_index?(part) ? "[#{part}]" : ".#{part}"
         | 
| 130 | 
            +
            				
         | 
| 131 | 
            +
            						next_call     = parts[index + 1]
         | 
| 132 | 
            +
            						attribute_key = is_index?(next_call) ? parts[index + 2] : next_call 
         | 
| 133 | 
            +
            						next if (is_index?(part) && next_call && attribute_key.nil?) or !is_index?(part)
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            						change_statement = "#{ref_object}#{method_chain}.attribute_will_change!('#{attribute_key}')"
         | 
| 136 | 
            +
            						change_statements << change_statement 
         | 
| 137 | 
            +
            					end 
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            					<<-STRING
         | 
| 140 | 
            +
            						attribute_will_change!('#{parts.last}')
         | 
| 141 | 
            +
            						#{change_statements.join("\n")}
         | 
| 142 | 
            +
            					STRING
         | 
| 143 | 
            +
            				end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
             | 
| 146 | 
            +
            				def is_index?(part)
         | 
| 147 | 
            +
            					part&.match?(/\d+/)
         | 
| 148 | 
            +
            				end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
             | 
| 151 | 
            +
            				
         | 
| 152 | 
            +
            				
         | 
| 74 153 |  | 
| 75 154 | 
             
            			# Requires an array of hashes which each contain a single :alias_attribute entry. 
         | 
| 76 155 | 
             
            			# This will expose e_plat_key getter and setter methods that interact with the 
         | 
| @@ -82,19 +161,24 @@ module EPlat | |
| 82 161 | 
             
            			# Like the below example, optional stringified procs can be passed in to respect the e_plat or native interfaces version of the value. e.g. enums
         | 
| 83 162 | 
             
            			# 
         | 
| 84 163 | 
             
            			# {
         | 
| 85 | 
            -
            			# 	alias_attribute: {native_key: " | 
| 164 | 
            +
            			# 	alias_attribute: {native_key: "description",  e_plat_key: "body_html"}
         | 
| 165 | 
            +
            			# }, {
         | 
| 166 | 
            +
            			# 	alias_attribute: {
         | 
| 167 | 
            +
            			#		native_key: "description",  
         | 
| 168 | 
            +
            			#		e_plat_key: "body_html", 
         | 
| 169 | 
            +
            			#		custom_e_plat_getter: "-> (value) { value&.gsub(' ', '-') }",
         | 
| 170 | 
            +
            			#		custom_native_setter: "-> (value) { value&.gsub('-', ' ') }" 
         | 
| 171 | 
            +
            			#	}
         | 
| 172 | 
            +
            			# }, {
         | 
| 173 | 
            +
            			#    existing_entry: {native_key: "id", e_plat_key: "id"}
         | 
| 86 174 | 
             
            			# },
         | 
| 87 175 | 
             
            			# {
         | 
| 88 | 
            -
            			# | 
| 89 | 
            -
            			# 		custom_e_plat_getter: "-> (value) { 
         | 
| 90 | 
            -
            			# 			{'preorder'=>'active', 'available' => 'active', 'disabled' => 'draft'}[value] || value
         | 
| 91 | 
            -
            			# 		}",
         | 
| 92 | 
            -
            			# 		custom_native_setter: "-> (value) { 
         | 
| 93 | 
            -
            			# 			{'available' => 'active', 'disabled' => 'draft'}.invert[value] || value
         | 
| 94 | 
            -
            			# 		}" #invert will merge new duped keys
         | 
| 95 | 
            -
            			# 	}
         | 
| 176 | 
            +
            			#  	 alias_attribute: {native_key: "billing_address[email]", e_plat_key: "email"}
         | 
| 96 177 | 
             
            			# }
         | 
| 97 | 
            -
            			
         | 
| 178 | 
            +
            			# {
         | 
| 179 | 
            +
            			#  	 alias_attribute: {native_key: "[nested][deeply][in][2][attribute]", e_plat_key: "hey"}
         | 
| 180 | 
            +
            			# }
         | 
| 181 | 
            +
            			# 
         | 
| 98 182 | 
             
            		end
         | 
| 99 183 | 
             
            	end
         | 
| 100 184 | 
             
            end
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            module EPlat
         | 
| 2 | 
            +
            	module Concerns
         | 
| 3 | 
            +
            		module Dirty
         | 
| 4 | 
            +
            			extend ActiveSupport::Concern
         | 
| 5 | 
            +
            			# manages the self.changed_attributes hash
         | 
| 6 | 
            +
            			# this is important for the as_json method, which will only include changed attributes when updating an existing resource
         | 
| 7 | 
            +
            			# only supports native attributes
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            			included do
         | 
| 10 | 
            +
            				def changed_attributes
         | 
| 11 | 
            +
            					@changed_attributes ||= {}
         | 
| 12 | 
            +
            				end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            				def attribute_will_change!(attr)
         | 
| 15 | 
            +
            					changed_attributes[attr] = read_attribute(attr)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            					native_keys.include?(attr) ? e_plat_attribute_will_change!(attr) : native_attribute_will_change!(attr)
         | 
| 18 | 
            +
            				end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            				def attribute_changed?(attr)
         | 
| 21 | 
            +
            					changed_attributes.include?(attr)
         | 
| 22 | 
            +
            				end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            				def read_attribute(attr)
         | 
| 25 | 
            +
            					self.send(attr)
         | 
| 26 | 
            +
            				end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            				def reload!
         | 
| 29 | 
            +
            					try(:super)
         | 
| 30 | 
            +
            					changed_attributes.clear
         | 
| 31 | 
            +
            				end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
             | 
| 34 | 
            +
            				private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            				def native_attribute_will_change!(eplat_attr)
         | 
| 37 | 
            +
            					key = self.mapping.native_key_to(eplat_attr)
         | 
| 38 | 
            +
            					return unless key && self.respond_to?(key)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            					changed_attributes[key] = send(key)
         | 
| 41 | 
            +
            				end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            				def e_plat_attribute_will_change!(native_attr)
         | 
| 44 | 
            +
            					key = self.mapping.e_plat_key_to(native_attr)
         | 
| 45 | 
            +
            					return unless key && self.respond_to?(key)
         | 
| 46 | 
            +
            					
         | 
| 47 | 
            +
            					changed_attributes[key] = send(key)
         | 
| 48 | 
            +
            				end
         | 
| 49 | 
            +
            			end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
             | 
| 52 | 
            +
            		end
         | 
| 53 | 
            +
            	end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EPlat
         | 
| 4 | 
            +
            	module Concerns
         | 
| 5 | 
            +
            		module Metafieldable #< ActiveResource::CustomMethods
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            			def metafields(**options)
         | 
| 8 | 
            +
            				options.merge!(resource: self.class.collection_name, resource_id: id)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            				Metafield.find(:all, 
         | 
| 11 | 
            +
            					from: current_resources_metafield_path, 
         | 
| 12 | 
            +
            					params: options
         | 
| 13 | 
            +
            				)
         | 
| 14 | 
            +
            			end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            			def find_metafield(id)
         | 
| 17 | 
            +
            				metafields.find { |m| m.id == id }
         | 
| 18 | 
            +
            			end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            			def add_metafield(metafield)
         | 
| 21 | 
            +
            				raise ArgumentError, "You can only add metafields to a resource that has been saved" if new?
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            				metafield.owner_id = id
         | 
| 24 | 
            +
            				metafield.owner_resource = self.class.element_name
         | 
| 25 | 
            +
            				metafield.save
         | 
| 26 | 
            +
            				metafield
         | 
| 27 | 
            +
            			end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            			
         | 
| 30 | 
            +
            			private
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            			def current_resources_metafield_path
         | 
| 33 | 
            +
            				uri = URI.parse(element_path)
         | 
| 34 | 
            +
            				uri.path = uri.path.gsub(".json", "/metafields.json")
         | 
| 35 | 
            +
            				uri.path += "/metafields"  if uri.path.exclude?("json")
         | 
| 36 | 
            +
            				uri.path.gsub!('v2', 'v3') if client.bigcommerce? # metafield uses v3 API, even though orders are only available in v2
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            				uri.to_s
         | 
| 39 | 
            +
            			end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            		end
         | 
| 42 | 
            +
            	end
         | 
| 43 | 
            +
            end
         | 
| @@ -1,11 +1,113 @@ | |
| 1 1 | 
             
            module EPlat
         | 
| 2 2 | 
             
            	module Concerns
         | 
| 3 | 
            -
            		module OverwriteInstanceMethods | 
| 4 | 
            -
            			
         | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 3 | 
            +
            		module OverwriteInstanceMethods
         | 
| 4 | 
            +
            			extend ActiveSupport::Concern	
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            			def to_json(options = {})
         | 
| 7 | 
            +
            				#add this option here so root is added at the top of the JSON, as_json to every nested resource
         | 
| 8 | 
            +
            				root_at_top_of_json = self.mapping.include_root_in_request_body?(self)
         | 
| 9 | 
            +
            				options[:root] 		= self.element_name if root_at_top_of_json 
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            				super(options)
         | 
| 7 12 | 
             
            			end
         | 
| 8 | 
            -
             | 
| 13 | 
            +
             | 
| 14 | 
            +
            			def as_json(options = {})
         | 
| 15 | 
            +
            				full_json = super(options)
         | 
| 16 | 
            +
            				return {} if read_only?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            				if mapping.class.virtual_collections.present?
         | 
| 19 | 
            +
            					virtual_keys = mapping.class.virtual_collections.map{|c| c[:name]}.map(&:to_s)
         | 
| 20 | 
            +
            					
         | 
| 21 | 
            +
            					full_json.reject!{|key, value| virtual_keys.include?(key) } # not needed in JSON, as it's just a proxy for setting the real attributes
         | 
| 22 | 
            +
            				end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            				return full_json if new_record? # new records request with all attributes
         | 
| 25 | 
            +
            				
         | 
| 26 | 
            +
            				request_json = 
         | 
| 27 | 
            +
            					if include_root_in_json or options[:root].present?
         | 
| 28 | 
            +
            						{ element_name => select_changed_attributes_and_collection_keys(full_json[element_name]) }
         | 
| 29 | 
            +
            					else
         | 
| 30 | 
            +
            						select_changed_attributes_and_collection_keys(full_json)
         | 
| 31 | 
            +
            					end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            				if top_level_resource? # then check that any present collections have all entries represented with atleast an ID, so they're not removed
         | 
| 34 | 
            +
            					hash_without_root(request_json).each do |key, value|
         | 
| 35 | 
            +
            						next unless arrayOfPresentHashes?(value)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            						hash_without_root(request_json)[key].filter!(&:present?)
         | 
| 38 | 
            +
            						hash_without_root(request_json)[key] += unrepresented_collection_ids(key, value).map{ |id| { "id" => id } }
         | 
| 39 | 
            +
            					end
         | 
| 40 | 
            +
            				end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
             				add_root_if_needed(request_json, options[:root]).reject do |k,v| 
         | 
| 43 | 
            +
            					if v.is_a?(Array) 
         | 
| 44 | 
            +
            						v.reject(&:empty?).empty?
         | 
| 45 | 
            +
            					else 
         | 
| 46 | 
            +
            						v.nil? || v.blank?
         | 
| 47 | 
            +
            					end
         | 
| 48 | 
            +
            				end
         | 
| 49 | 
            +
            			end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            			# Create and return a class definition for a resource inside the current resource
         | 
| 52 | 
            +
            			def create_resource_for(resource_name)
         | 
| 53 | 
            +
            				resource = Class.new(EPlat::Base) # <- this line changed
         | 
| 54 | 
            +
            				resource.prefix = self.class.prefix
         | 
| 55 | 
            +
            				resource.site = self.class.site
         | 
| 56 | 
            +
            				self.class.const_set(resource_name, resource)
         | 
| 57 | 
            +
            		
         | 
| 58 | 
            +
            				resource
         | 
| 59 | 
            +
            			end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            			def create
         | 
| 62 | 
            +
            				self.attributes = mapping.via_native_attributes_where_possible(attributes)
         | 
| 63 | 
            +
            				super
         | 
| 64 | 
            +
            			end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            			def update
         | 
| 67 | 
            +
            				self.attributes = mapping.via_native_attributes_where_possible(attributes)
         | 
| 68 | 
            +
            				super
         | 
| 69 | 
            +
            			end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
             | 
| 72 | 
            +
            			private
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            			def select_changed_attributes_and_collection_keys(json)
         | 
| 75 | 
            +
            				sanitised_json = json.dup
         | 
| 76 | 
            +
            				
         | 
| 77 | 
            +
            				sanitised_json.select!{|key, value| changed_attributes.has_key?(key.to_s) || arrayOfPresentHashes?(value) }
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            				if sanitised_json.present? # then we'll add an ID so that the resource can be updated
         | 
| 80 | 
            +
            					sanitised_json['id'] = json['id'] if json['id'] && client.can_update_nested_resources?
         | 
| 81 | 
            +
            				end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            				sanitised_json || {}
         | 
| 84 | 
            +
            			end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            			def arrayOfPresentHashes?(value)
         | 
| 87 | 
            +
            				value.is_a?(Array) && value.first.is_a?(Hash) && value.first.present?
         | 
| 88 | 
            +
            			end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            			def unrepresented_collection_ids(key, arrayOfHashes)
         | 
| 91 | 
            +
            				return [] unless client.can_update_nested_resources?
         | 
| 92 | 
            +
            				
         | 
| 93 | 
            +
            				all_instance_ids 		 = try(key)&.map(&:id) || [] 
         | 
| 94 | 
            +
            				represented_instance_ids = arrayOfHashes.map{|item| item["id"] }
         | 
| 95 | 
            +
            				
         | 
| 96 | 
            +
            				all_instance_ids.compact - represented_instance_ids.compact
         | 
| 97 | 
            +
            			end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            			def hash_without_root(json)
         | 
| 100 | 
            +
            				self.include_root_in_json ? json[self.element_name] : json
         | 
| 101 | 
            +
            			end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            			def add_root_if_needed(json, root)
         | 
| 104 | 
            +
            				if root and !json.has_key?(root)
         | 
| 105 | 
            +
            					{ root => json }
         | 
| 106 | 
            +
            				else
         | 
| 107 | 
            +
            					json
         | 
| 108 | 
            +
            				end
         | 
| 109 | 
            +
            			end
         | 
| 110 | 
            +
             | 
| 9 111 | 
             
            		end
         | 
| 10 112 | 
             
            	end
         | 
| 11 | 
            -
            end
         | 
| 113 | 
            +
            end
         | 
| @@ -1,50 +1,86 @@ | |
| 1 1 | 
             
            module EPlat
         | 
| 2 2 | 
             
            	module Concerns
         | 
| 3 | 
            -
            		module OverwriteRequestMethods | 
| 3 | 
            +
            		module OverwriteRequestMethods
         | 
| 4 4 |  | 
| 5 | 
            -
            			def  | 
| 6 | 
            -
            				 | 
| 7 | 
            -
            				arguments.second[:params].merge!(client.send "#{element_name}_default_find_args")  if arguments.second
         | 
| 8 | 
            -
            				arguments.second[:params].merge!(mapping.to_native_keys arguments.second[:params]) if arguments.second
         | 
| 9 | 
            -
            				
         | 
| 10 | 
            -
            				super
         | 
| 5 | 
            +
            			def self.included(base)
         | 
| 6 | 
            +
            				base.extend ClassMethods
         | 
| 11 7 | 
             
            			end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            			 | 
| 14 | 
            -
            				 | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
            				 | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            			module ClassMethods
         | 
| 10 | 
            +
            				def find(*arguments)
         | 
| 11 | 
            +
            					initialize_singleton!
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            					if arguments.second && arguments.second[:params]
         | 
| 14 | 
            +
            						arguments.second[:params].merge!(client.try("#{element_name}_default_request_args") || {})
         | 
| 15 | 
            +
            						arguments.second[:params] = mapping.via_native_attributes_where_possible(arguments.second[:params])
         | 
| 16 | 
            +
            					else
         | 
| 17 | 
            +
            						arguments << {params: (client.try("#{element_name}_default_request_args") || {}) }
         | 
| 18 | 
            +
            					end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            					super
         | 
| 21 | 
            +
            				end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            				def find_by(params)
         | 
| 24 | 
            +
            					find(:all, params: params)&.first
         | 
| 25 | 
            +
            				end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            				def instantiate_collection(collection, original_params = {}, prefix_options = {})
         | 
| 28 | 
            +
            					collection = collection["data"] if collection.is_a?(Hash) && collection["data"]
         | 
| 29 | 
            +
            					super
         | 
| 30 | 
            +
            				end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            				def new(attributes, persisted = false)
         | 
| 33 | 
            +
            					initialize_singleton!
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            					self.mapping = EPlat::Mapping.new_instance(specifc_mapping: specifc_mapping_name, resource: nil)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            					if attributes[mapping.native_top_key]
         | 
| 38 | 
            +
            						attributes = attributes.send(*top_key_method(mapping.native_top_key))
         | 
| 39 | 
            +
            					end
         | 
| 40 | 
            +
            					attributes = attributes.with_defaults(mapping.class::DEFAULT_VALUES)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            					super
         | 
| 43 | 
            +
            				end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            				def create(attributes = {})
         | 
| 46 | 
            +
            					initialize_singleton!
         | 
| 47 | 
            +
            					attributes = mapping.via_native_attributes_where_possible(attributes)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            					super
         | 
| 50 | 
            +
            				end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            				def update(attributes = {})
         | 
| 53 | 
            +
            					initialize_singleton!
         | 
| 54 | 
            +
            					attributes = mapping.via_native_attributes_where_possible(attributes)
         | 
| 55 | 
            +
            					
         | 
| 56 | 
            +
            					super
         | 
| 57 | 
            +
            				end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            				private
         | 
| 60 | 
            +
             | 
| 39 61 | 
             
            				def top_key_method(top_key)
         | 
| 40 62 | 
             
            					(top_key == :itself) ? top_key : [:[], top_key]
         | 
| 41 63 | 
             
            				end
         | 
| 42 | 
            -
             | 
| 64 | 
            +
             | 
| 43 65 | 
             
            				def specifc_mapping_name
         | 
| 44 66 | 
             
            					class_constant_string = self.name.gsub("EPlat::", "")
         | 
| 45 | 
            -
            					"EPlat::Mapping::#{ | 
| 67 | 
            +
            					"EPlat::Mapping::#{client.platform.capitalize}::V#{client.api_version.camelize}::#{class_constant_string}"
         | 
| 46 68 | 
             
            				end
         | 
| 69 | 
            +
            			end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
             | 
| 72 | 
            +
            			def collection_path(options = {})
         | 
| 73 | 
            +
            				options = options.merge client.try("#{element_name}_default_request_args") || {}
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            				super(options)
         | 
| 76 | 
            +
            			end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            			def element_path(options = {})
         | 
| 79 | 
            +
            				options = options.merge client.try("#{element_name}_default_request_args") || {}
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            				super(options)
         | 
| 82 | 
            +
            			end
         | 
| 47 83 |  | 
| 48 84 | 
             
            		end
         | 
| 49 85 | 
             
            	end
         | 
| 50 | 
            -
            end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EPlat
         | 
| 4 | 
            +
            	module Countable
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            		def count(options = {})
         | 
| 7 | 
            +
            			initialize_singleton!
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            			if counts_via_meta_data?
         | 
| 10 | 
            +
            				raw_data  = connection.get(collection_path, headers)
         | 
| 11 | 
            +
            				data_hash = ActiveSupport::JSON.decode(raw_data.body)
         | 
| 12 | 
            +
            				data = data_hash.dig("meta", "pagination", "total")
         | 
| 13 | 
            +
            			elsif count_key_next_to_root?
         | 
| 14 | 
            +
            				raw = get(:count, options)
         | 
| 15 | 
            +
            				data = JSON.parse raw.full_response.body
         | 
| 16 | 
            +
            			else
         | 
| 17 | 
            +
            				data = get(:count, options)
         | 
| 18 | 
            +
            			end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            			count = 
         | 
| 21 | 
            +
            				case data
         | 
| 22 | 
            +
            				when Hash then data["count"]
         | 
| 23 | 
            +
            				else data
         | 
| 24 | 
            +
            				end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            			Integer(count)
         | 
| 27 | 
            +
            		end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
             | 
| 30 | 
            +
            		private
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            			def counts_via_meta_data?
         | 
| 33 | 
            +
            				return false unless client.bigcommerce?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            				self == EPlat::Product or self == EPlat::Product::Variant
         | 
| 36 | 
            +
            			end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            			def count_key_next_to_root?
         | 
| 39 | 
            +
            				client.bigcommerce? && self == EPlat::Order
         | 
| 40 | 
            +
            			end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            	end
         | 
| 43 | 
            +
            end
         |