business_catalyst 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +13 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +1 -0
- data/business_catalyst.gemspec +24 -0
- data/lib/business_catalyst/csv/catalog_row.rb +29 -0
- data/lib/business_catalyst/csv/file_splitter.rb +125 -0
- data/lib/business_catalyst/csv/product_attribute.rb +129 -0
- data/lib/business_catalyst/csv/product_row.rb +110 -0
- data/lib/business_catalyst/csv/row.rb +108 -0
- data/lib/business_catalyst/csv/transformers/catalog_transformer.rb +31 -0
- data/lib/business_catalyst/csv/transformers/currency_transformer.rb +53 -0
- data/lib/business_catalyst/csv/transformers/invalid_input_error.rb +9 -0
- data/lib/business_catalyst/csv/transformers/product_attributes_transformer.rb +45 -0
- data/lib/business_catalyst/csv/transformers/product_code_transformer.rb +21 -0
- data/lib/business_catalyst/csv/transformers/seo_friendly_url_transformer.rb +38 -0
- data/lib/business_catalyst/csv/transformers/transformer.rb +22 -0
- data/lib/business_catalyst/csv/transformers/uri_array_transformer.rb +15 -0
- data/lib/business_catalyst/csv/transformers/uri_transformer.rb +14 -0
- data/lib/business_catalyst/csv/transformers.rb +58 -0
- data/lib/business_catalyst/version.rb +3 -0
- data/lib/business_catalyst.rb +40 -0
- data/spec/lib/business_catalyst/business_catalyst_spec.rb +34 -0
- data/spec/lib/business_catalyst/csv/catalog_row_spec.rb +5 -0
- data/spec/lib/business_catalyst/csv/file_splitter_spec.rb +103 -0
- data/spec/lib/business_catalyst/csv/product_attribute_spec.rb +65 -0
- data/spec/lib/business_catalyst/csv/product_row_spec.rb +5 -0
- data/spec/lib/business_catalyst/csv/row_spec.rb +110 -0
- data/spec/lib/business_catalyst/csv/transformers/catalog_transformer_spec.rb +37 -0
- data/spec/lib/business_catalyst/csv/transformers/currency_transformer_spec.rb +65 -0
- data/spec/lib/business_catalyst/csv/transformers/product_attributes_transformer_spec.rb +76 -0
- data/spec/lib/business_catalyst/csv/transformers/seo_friendly_url_transformer_spec.rb +36 -0
- data/spec/lib/business_catalyst/csv/transformers/transformer_spec.rb +25 -0
- data/spec/lib/business_catalyst/csv/transformers/urI_transformer_spec.rb +17 -0
- data/spec/lib/business_catalyst/csv/transformers_spec.rb +81 -0
- data/spec/spec_helper.rb +6 -0
- metadata +140 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'csv'
|
|
3
|
+
|
|
4
|
+
module BusinessCatalyst
|
|
5
|
+
module CSV
|
|
6
|
+
|
|
7
|
+
class NoSuchColumnError < StandardError; end
|
|
8
|
+
|
|
9
|
+
# Shared logic for building a row for a CSV export for Business Catalust.
|
|
10
|
+
# Instead of sublcassing Row directly in your project, subclass CatalogRow
|
|
11
|
+
# or ProductRow, which have column definitions set up for those tables.
|
|
12
|
+
class Row
|
|
13
|
+
def initialize(*args)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Override to return Array of column config Arrays in the format:
|
|
17
|
+
#
|
|
18
|
+
# [
|
|
19
|
+
# ["Header", :mapping_name, default_value, TransformerClass],
|
|
20
|
+
# ...
|
|
21
|
+
# ]
|
|
22
|
+
#
|
|
23
|
+
# "default_value" and "TransformerClass" are optional, and will default to nil and
|
|
24
|
+
# GenericTransformer, respectively, if not specified.
|
|
25
|
+
def self.columns
|
|
26
|
+
raise NotImplementedError, "Implement to return Array of column config Arrays in the format: [[\"Header\", :mapping_name, default_value, TransformerClass]]"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Define value for BC column using a block. Column argument should be
|
|
30
|
+
# one of the mapping name symbols returned by Row.columns.
|
|
31
|
+
def self.map(column, &block)
|
|
32
|
+
unless columns.any? {|c| c[1] == column}
|
|
33
|
+
raise NoSuchColumnError, "no such column '#{column.inspect}'"
|
|
34
|
+
end
|
|
35
|
+
define_method(column, &block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Set the default currency for the current application.
|
|
39
|
+
# Alias for CurrencyTransformer.default_currency=
|
|
40
|
+
#
|
|
41
|
+
# class MyRow < BusinessCatalyst::CSV:Row
|
|
42
|
+
# default_currency "US"
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
def self.default_currency(currency)
|
|
46
|
+
CurrencyTransformer.default_currency = currency
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Nice shortcut for generating a csv.
|
|
50
|
+
# TODO: add option to usea file splitter.
|
|
51
|
+
#
|
|
52
|
+
# Manual:
|
|
53
|
+
#
|
|
54
|
+
# ProductRow.generate("products.csv") do |csv|
|
|
55
|
+
# products.each do |product|
|
|
56
|
+
# csv << ProductRow.new(product).to_a
|
|
57
|
+
# end
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# Automatic collection handling:
|
|
61
|
+
#
|
|
62
|
+
# ProductRow.generate("products.csv", products) do |product|
|
|
63
|
+
# ProductRow.new(product)
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
def self.generate(file_name, collection = nil)
|
|
67
|
+
::CSV.open(file_name, 'wb') do |csv|
|
|
68
|
+
csv << headers
|
|
69
|
+
if collection.respond_to?(:each)
|
|
70
|
+
collection.each do |item|
|
|
71
|
+
row = yield(item)
|
|
72
|
+
raise ArgumentError, "input must be a valid Row" unless row.kind_of?(self)
|
|
73
|
+
csv << row.to_a
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
yield csv
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def to_a
|
|
82
|
+
self.class.columns.map { |column|
|
|
83
|
+
csv_value(column[1], column)
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.headers
|
|
88
|
+
@headers ||= columns.map(&:first)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def csv_value(method, config = nil)
|
|
92
|
+
config ||= self.class.columns.find {|c| c[1] == method}
|
|
93
|
+
raise NoSuchColumnError, "no configuration found for #{method.inspect} in #{self.class.to_s}.columns" if config.nil?
|
|
94
|
+
|
|
95
|
+
input = if respond_to?(method)
|
|
96
|
+
send(method)
|
|
97
|
+
else
|
|
98
|
+
config.fetch(2, nil)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
transformer = config.fetch(3, GenericTransformer)
|
|
102
|
+
transformer.transform(input)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
module BusinessCatalyst
|
|
3
|
+
module CSV
|
|
4
|
+
|
|
5
|
+
class CatalogTransformer < Transformer
|
|
6
|
+
def normalized_input
|
|
7
|
+
# ensure at least a 1D array
|
|
8
|
+
normalized_input = input.kind_of?(Array) ? input : [input]
|
|
9
|
+
|
|
10
|
+
# now convert to 2D array
|
|
11
|
+
unless normalized_input.first.kind_of?(Array)
|
|
12
|
+
normalized_input = [normalized_input]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
normalized_input
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def transform
|
|
19
|
+
normalized_input.map { |path|
|
|
20
|
+
if path.any?
|
|
21
|
+
sanitized_names = path.map { |name|
|
|
22
|
+
BusinessCatalyst.sanitize_catalog_name(name)
|
|
23
|
+
}
|
|
24
|
+
"/" + sanitized_names.join("/")
|
|
25
|
+
end
|
|
26
|
+
}.join(";")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'bigdecimal'
|
|
3
|
+
|
|
4
|
+
module BusinessCatalyst
|
|
5
|
+
module CSV
|
|
6
|
+
|
|
7
|
+
class CurrencyTransformer < Transformer
|
|
8
|
+
BC_CURRENCY_REGEX = /\A\w+\/\d/.freeze
|
|
9
|
+
|
|
10
|
+
def self.default_currency
|
|
11
|
+
@default_currency || "US"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.default_currency=(currency)
|
|
15
|
+
@default_currency = currency
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_accessor :currency
|
|
19
|
+
|
|
20
|
+
def initialize(input, currency = nil)
|
|
21
|
+
@currency = currency || self.class.default_currency
|
|
22
|
+
super(input)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def transform
|
|
26
|
+
if input
|
|
27
|
+
inputs = Array(input).map {|n| number_to_currency(n) }.compact
|
|
28
|
+
if inputs.any?
|
|
29
|
+
inputs.join(";")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def number_to_currency(input)
|
|
35
|
+
if input
|
|
36
|
+
input_s = input.kind_of?(BigDecimal) ? input.to_s('F') : input.to_s.strip
|
|
37
|
+
if input_s != ""
|
|
38
|
+
if is_bc_currency_string?(input_s)
|
|
39
|
+
input_s
|
|
40
|
+
else
|
|
41
|
+
"#{currency}/#{input_s}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def is_bc_currency_string?(input)
|
|
48
|
+
!!(input =~ BC_CURRENCY_REGEX)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module BusinessCatalyst
|
|
3
|
+
module CSV
|
|
4
|
+
|
|
5
|
+
# Example: Chain*|5|N:Rope Chain||US/0,Box Chain||US/5,Snake Chain||US/5;Length*|5|N:16 inch||US/0,18 inch||US/0,20 inch||US/0,24 inch||US/0
|
|
6
|
+
#
|
|
7
|
+
# Name1REQ|display_as|keep_stock:Option1.name|Option1.image|Option1.price,Option2.name|...;Name2...
|
|
8
|
+
class ProductAttributesTransformer < Transformer
|
|
9
|
+
|
|
10
|
+
def transform
|
|
11
|
+
if input.kind_of?(String)
|
|
12
|
+
input
|
|
13
|
+
elsif input
|
|
14
|
+
attributes.map {|attribute|
|
|
15
|
+
name = BusinessCatalyst.sanitize_catalog_name(attribute.name)
|
|
16
|
+
required = attribute.required ? '*' : ''
|
|
17
|
+
display_as = attribute.display_as.to_i
|
|
18
|
+
keep_stock = BooleanTransformer.transform(attribute.keep_stock)
|
|
19
|
+
|
|
20
|
+
text = ["#{name}#{required}", display_as, keep_stock].join("|")
|
|
21
|
+
text << ":"
|
|
22
|
+
|
|
23
|
+
text << attribute.options.map {|option|
|
|
24
|
+
name = BusinessCatalyst.sanitize_catalog_name(option.name.to_s)
|
|
25
|
+
image = option.image.to_s.strip
|
|
26
|
+
price = CurrencyTransformer.transform(option.price)
|
|
27
|
+
[name, image, price].join("|")
|
|
28
|
+
}.join(",")
|
|
29
|
+
|
|
30
|
+
text
|
|
31
|
+
}.join(";")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def attributes
|
|
36
|
+
if input.kind_of?(Array)
|
|
37
|
+
input.compact
|
|
38
|
+
else
|
|
39
|
+
[input].compact
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module BusinessCatalyst
|
|
3
|
+
module CSV
|
|
4
|
+
|
|
5
|
+
class ProductCodeTransformer < Transformer
|
|
6
|
+
|
|
7
|
+
def initialize(input)
|
|
8
|
+
if input.blank?
|
|
9
|
+
raise InvalidInputError, "product_code must not be blank"
|
|
10
|
+
end
|
|
11
|
+
super(input)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def transform
|
|
15
|
+
input.to_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module BusinessCatalyst
|
|
3
|
+
module CSV
|
|
4
|
+
|
|
5
|
+
class SEOFriendlyUrlTransformer < Transformer
|
|
6
|
+
|
|
7
|
+
def initialize(input)
|
|
8
|
+
input = input.to_s
|
|
9
|
+
raise InvalidInputError, "seo_friendly_url must not be blank" if input.nil? || input.strip == ""
|
|
10
|
+
raise InvalidInputError, "seo_friendly_url '#{input}' is not globally unique" unless self.class.is_globally_unique?(input)
|
|
11
|
+
self.class.register_url(input)
|
|
12
|
+
super(input)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def transform
|
|
16
|
+
input
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.reset_global_urls!
|
|
20
|
+
@global_urls = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.global_urls
|
|
24
|
+
@global_urls ||= {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.register_url(url)
|
|
28
|
+
global_urls[url] = true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.is_globally_unique?(url)
|
|
32
|
+
!global_urls.fetch(url, false)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module BusinessCatalyst
|
|
3
|
+
module CSV
|
|
4
|
+
|
|
5
|
+
class Transformer
|
|
6
|
+
attr_accessor :input
|
|
7
|
+
|
|
8
|
+
def initialize(input)
|
|
9
|
+
@input = input
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def transform
|
|
13
|
+
raise NotImplementedError, "Transformer subclasses must implement #transform"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.transform(input)
|
|
17
|
+
self.new(input).transform
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'business_catalyst/csv/transformers/invalid_input_error'
|
|
3
|
+
require 'business_catalyst/csv/transformers/transformer'
|
|
4
|
+
require 'business_catalyst/csv/transformers/catalog_transformer'
|
|
5
|
+
require 'business_catalyst/csv/transformers/currency_transformer'
|
|
6
|
+
require 'business_catalyst/csv/transformers/product_attributes_transformer'
|
|
7
|
+
require 'business_catalyst/csv/transformers/product_code_transformer'
|
|
8
|
+
require 'business_catalyst/csv/transformers/seo_friendly_url_transformer'
|
|
9
|
+
require 'business_catalyst/csv/transformers/uri_array_transformer'
|
|
10
|
+
require 'business_catalyst/csv/transformers/uri_transformer'
|
|
11
|
+
|
|
12
|
+
module BusinessCatalyst
|
|
13
|
+
module CSV
|
|
14
|
+
|
|
15
|
+
# Just calls to_s on input
|
|
16
|
+
class GenericTransformer < Transformer
|
|
17
|
+
def transform
|
|
18
|
+
input.to_s if input
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ArrayTransformer < Transformer
|
|
24
|
+
def transform
|
|
25
|
+
if input
|
|
26
|
+
Array(input).map {|s| s.to_s.gsub(";", " ") }.join(";")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BooleanTransformer < Transformer
|
|
33
|
+
def transform
|
|
34
|
+
input ? "Y" : "N"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class TemplateIDTransformer < Transformer
|
|
39
|
+
def transform
|
|
40
|
+
if input.kind_of?(Symbol)
|
|
41
|
+
case input
|
|
42
|
+
when :default
|
|
43
|
+
0
|
|
44
|
+
when :none
|
|
45
|
+
-1
|
|
46
|
+
when :parent
|
|
47
|
+
-2
|
|
48
|
+
else
|
|
49
|
+
raise InvalidInputError, "#{input} is not a valid template ID"
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
input
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require "business_catalyst/version"
|
|
3
|
+
require "business_catalyst/csv/product_attribute"
|
|
4
|
+
require "business_catalyst/csv/transformers"
|
|
5
|
+
require "business_catalyst/csv/row"
|
|
6
|
+
require "business_catalyst/csv/product_row"
|
|
7
|
+
require "business_catalyst/csv/catalog_row"
|
|
8
|
+
require "business_catalyst/csv/file_splitter"
|
|
9
|
+
|
|
10
|
+
module BusinessCatalyst
|
|
11
|
+
|
|
12
|
+
# The following characters cause an error message if they appear in a catalog name.
|
|
13
|
+
CATALOG_CHAR_BLACKLIST = /[\\\/;#:"\|_@=\?]/.freeze
|
|
14
|
+
MORE_STRICT_BLACKLIST = /[\\\/;&,#:"\|\._@=\?]/.freeze
|
|
15
|
+
|
|
16
|
+
# Strip all characters out of a catalog name that will cause an error. Replaces them with
|
|
17
|
+
# " " and then squishes all whitespace to single space to preserve word structure.
|
|
18
|
+
def self.sanitize_catalog_name(name)
|
|
19
|
+
return name if name.nil?
|
|
20
|
+
sanitized = name.strip
|
|
21
|
+
sanitized.gsub!(CATALOG_CHAR_BLACKLIST, " ")
|
|
22
|
+
sanitized.gsub!(/\s+/, " ")
|
|
23
|
+
sanitized
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# A guess as to how business catalyst converts names to URL's, based on this blog entry:
|
|
27
|
+
# http://www.businesscatalyst.com/bc-blog/seo-friendly-urls-for-products-and-catalogs
|
|
28
|
+
#
|
|
29
|
+
# Downcases, converts invalid characters and whitespace to '-', and finally removes multiple
|
|
30
|
+
# consecutive dashes and leading and trailing dashes. Does NOT append
|
|
31
|
+
# numbers to ensure uniqueness, you must do this yourself after conversion.
|
|
32
|
+
def self.seo_friendly_url(name)
|
|
33
|
+
name.strip.downcase.gsub(/[^a-z0-9\-]/, "-").gsub(/-{2,}/, "-").gsub(/\A-+|-+\Z/, "")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.reset_global_urls!
|
|
37
|
+
BusinessCatalyst::CSV::SEOFriendlyUrlTransformer.reset_global_urls!
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe BusinessCatalyst do
|
|
5
|
+
subject { BusinessCatalyst }
|
|
6
|
+
|
|
7
|
+
describe "#seo_friendly_url" do
|
|
8
|
+
it "replaces bad SEO chars with '-'" do
|
|
9
|
+
subject.seo_friendly_url("a&b").should eq("a-b")
|
|
10
|
+
end
|
|
11
|
+
it "replaces multiple consecutive bad SEO chars with single '-'" do
|
|
12
|
+
subject.seo_friendly_url("a/;&,#:\"|._@=?()b").should eq("a-b")
|
|
13
|
+
end
|
|
14
|
+
it "replaces whitespace with '-'" do
|
|
15
|
+
subject.seo_friendly_url("a b").should eq("a-b")
|
|
16
|
+
end
|
|
17
|
+
it "removes leading and trailing whitespace" do
|
|
18
|
+
subject.seo_friendly_url(" a b \n").should eq("a-b")
|
|
19
|
+
end
|
|
20
|
+
it "removes leading and trailing '-'" do
|
|
21
|
+
subject.seo_friendly_url("-a-b-").should eq("a-b")
|
|
22
|
+
end
|
|
23
|
+
it "squishes multiple consecutive '-'" do
|
|
24
|
+
subject.seo_friendly_url("a--b").should eq("a-b")
|
|
25
|
+
end
|
|
26
|
+
it "downcases input" do
|
|
27
|
+
subject.seo_friendly_url("A B").should eq("a-b")
|
|
28
|
+
end
|
|
29
|
+
it "strips random unicode characters" do
|
|
30
|
+
subject.seo_friendly_url("test”®").should eq("test")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module BusinessCatalyst
|
|
5
|
+
module CSV
|
|
6
|
+
describe FileSplitter do
|
|
7
|
+
subject {
|
|
8
|
+
FileSplitter.new("test", :max_rows_per_file => 10, :header_row => ["Column 1"], :verbose => false, :logger => nil)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
after(:each) do
|
|
12
|
+
# cleanup any files generated
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
subject.close
|
|
16
|
+
rescue IOError
|
|
17
|
+
# if it's already closed, awesome!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Dir.glob("test_*").each do |test_file|
|
|
21
|
+
FileUtils.rm(test_file)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get_csv_writer_path(writer)
|
|
26
|
+
if RUBY_VERSION =~ /\A1\.8/
|
|
27
|
+
writer.send(:instance_variable_get, :@dev).path
|
|
28
|
+
else
|
|
29
|
+
writer.path
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "#start" do
|
|
34
|
+
it "yields self" do
|
|
35
|
+
expect {|b| subject.start(&b) }.to yield_control.once
|
|
36
|
+
end
|
|
37
|
+
it "opens a new file with the new row range appended" do
|
|
38
|
+
subject.start do |splitter|
|
|
39
|
+
expect(get_csv_writer_path(splitter.current_file)).to eq("test_1-10.csv")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "#start_row" do
|
|
45
|
+
it "increments #current_row" do
|
|
46
|
+
expect { subject.start_row }.to change(subject, :current_row).by(1)
|
|
47
|
+
end
|
|
48
|
+
it "increments #total_rows" do
|
|
49
|
+
expect { subject.start_row }.to change(subject, :total_rows).by(1)
|
|
50
|
+
end
|
|
51
|
+
context "when current_row becomes greater than max_rows_per_file" do
|
|
52
|
+
before do
|
|
53
|
+
subject.max_rows_per_file = 10
|
|
54
|
+
subject.send(:instance_variable_set, :@current_row, 10)
|
|
55
|
+
subject.send(:instance_variable_set, :@total_rows, 10)
|
|
56
|
+
end
|
|
57
|
+
it "resets current_row to 1" do
|
|
58
|
+
expect { subject.start_row }.to change(subject, :current_row).to(1)
|
|
59
|
+
end
|
|
60
|
+
it "closes #current_file" do
|
|
61
|
+
file = double("File", :close => nil)
|
|
62
|
+
subject.send(:instance_variable_set, :@current_file, file)
|
|
63
|
+
subject.start_row
|
|
64
|
+
expect(file).to have_received(:close)
|
|
65
|
+
end
|
|
66
|
+
it "opens a new file with the new row range appended" do
|
|
67
|
+
subject.start_row
|
|
68
|
+
expect(get_csv_writer_path(subject.current_file)).to eq("test_11-20.csv")
|
|
69
|
+
end
|
|
70
|
+
it "appends the new file name to #all_files" do
|
|
71
|
+
subject.start_row
|
|
72
|
+
expect(subject.all_files.last).to eq("test_11-20.csv")
|
|
73
|
+
end
|
|
74
|
+
it "prepends the header row to the new file" do
|
|
75
|
+
subject.header_row = ["Column 1", "Column 2"]
|
|
76
|
+
subject.start_row
|
|
77
|
+
file = get_csv_writer_path(subject.current_file)
|
|
78
|
+
subject.close
|
|
79
|
+
line = File.open(file, 'r') do |f|
|
|
80
|
+
f.readline
|
|
81
|
+
end
|
|
82
|
+
expect(line).to eq("Column 1,Column 2\n")
|
|
83
|
+
end
|
|
84
|
+
it "calls a block provided to #on_file_change" do
|
|
85
|
+
it_works = false
|
|
86
|
+
subject.on_file_change do
|
|
87
|
+
it_works = true
|
|
88
|
+
end
|
|
89
|
+
subject.start_row
|
|
90
|
+
expect(it_works).to eq(true)
|
|
91
|
+
end
|
|
92
|
+
it "calls a block provided to #on_file_change with itself, if block arity is 1 or more" do
|
|
93
|
+
subject.on_file_change do |s|
|
|
94
|
+
expect(s).to eq(subject)
|
|
95
|
+
end
|
|
96
|
+
subject.start_row
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
module BusinessCatalyst
|
|
5
|
+
module CSV
|
|
6
|
+
describe ProductAttribute do
|
|
7
|
+
|
|
8
|
+
describe "#new" do
|
|
9
|
+
it "raises ArgumentError when display_as is invalid symbol" do
|
|
10
|
+
expect {
|
|
11
|
+
ProductAttribute.new("Test", :display_as => :invalid, :required => false, :keep_stock => false)
|
|
12
|
+
}.to raise_error(ArgumentError)
|
|
13
|
+
end
|
|
14
|
+
it "raises ArgumentError when display_as is invalid integer" do
|
|
15
|
+
expect {
|
|
16
|
+
ProductAttribute.new("Test", :display_as => -1)
|
|
17
|
+
}.to raise_error(ArgumentError)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "#add_options" do
|
|
22
|
+
context "when given an Array of Arrays" do
|
|
23
|
+
it "calls add_option with the splat of each array item" do
|
|
24
|
+
attribute = ProductAttribute.new("Test", :display_as => :dropdown, :required => false, :keep_stock => false)
|
|
25
|
+
attribute.add_options([["Opt", "img", 1]])
|
|
26
|
+
expect(attribute.options).to eq([ProductAttribute::Option.new("Opt", "img", 1)])
|
|
27
|
+
end
|
|
28
|
+
it "coerces arguments to array" do
|
|
29
|
+
attribute = ProductAttribute.new("Test", :display_as => :dropdown, :required => false, :keep_stock => false)
|
|
30
|
+
attribute.add_options(["Opt1", "img", 1], ["Opt2", "img", 1])
|
|
31
|
+
expect(attribute.options).to eq([
|
|
32
|
+
ProductAttribute::Option.new("Opt1", "img", 1),
|
|
33
|
+
ProductAttribute::Option.new("Opt2", "img", 1)
|
|
34
|
+
])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
context "when given an Array of Hashes" do
|
|
38
|
+
it "handles hashes correctly" do
|
|
39
|
+
attribute = ProductAttribute.new("Test", :display_as => :dropdown, :required => false, :keep_stock => false)
|
|
40
|
+
attribute.add_options([{:name => "Opt1", :image => "img", :price => 1}])
|
|
41
|
+
expect(attribute.options).to eq([ProductAttribute::Option.new("Opt1", "img", 1)])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
context "when given an Array of ProductAttribute::Option instances" do
|
|
45
|
+
it "adds just appends the object(s) to #options" do
|
|
46
|
+
attribute = ProductAttribute.new("Test", :display_as => :dropdown, :required => false, :keep_stock => false)
|
|
47
|
+
attribute.add_options([ProductAttribute::Option.new("Opt1", "img", 1)])
|
|
48
|
+
expect(attribute.options).to eq([ProductAttribute::Option.new("Opt1", "img", 1)])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
context "when given several strings" do
|
|
52
|
+
it "it turns each into an option with a name only" do
|
|
53
|
+
attribute = ProductAttribute.new("Test", :display_as => :dropdown, :required => false, :keep_stock => false)
|
|
54
|
+
attribute.add_options("Opt1", "Opt2")
|
|
55
|
+
expect(attribute.options).to eq([
|
|
56
|
+
ProductAttribute::Option.new("Opt1", nil, nil),
|
|
57
|
+
ProductAttribute::Option.new("Opt2", nil, nil)
|
|
58
|
+
])
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|