File Coverage

blib/lib/WWW/Shopify/Field.pm
Criterion Covered Total %
statement 90 213 42.2
branch 0 54 0.0
condition 0 12 0.0
subroutine 30 109 27.5
pod 0 20 0.0
total 120 408 29.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3 1     1   5 use strict;
  1         9  
  1         27  
4 1     1   4 use warnings;
  1         2  
  1         26  
5              
6 1     1   516 use WWW::Shopify::Field::Text;
  1         3  
  1         27  
7 1     1   863 use WWW::Shopify::Field::Relation;
  1         3  
  1         28  
8 1     1   908 use WWW::Shopify::Field::String;
  1         6  
  1         49  
9 1     1   678 use WWW::Shopify::Field::Identifier;
  1         3  
  1         31  
10              
11             package main;
12 1     1   6 use String::Random qw(random_regex random_string);
  1         2  
  1         53  
13 1     1   5 use Data::Random qw(rand_datetime rand_words);
  1         2  
  1         681  
14              
15             =head2 WWW::Shopify::Model::Field
16              
17             The main object representing a field on a Shopify object. Contains mainly a name, and a type. Can also contain more data when the need arises to represent something more specifically.
18              
19             There are a lot of these, so I'm not going to list them; most of the distinctions are made so that databases with reasonable data can be generated.
20              
21             =cut
22              
23              
24             package WWW::Shopify::Field;
25             sub new($) {
26 0     0 0   my $package = shift;
27 0           my $calling_package = caller(0);
28 0           return bless {
29             arguments => [@_],
30             name => undef,
31             owner => $calling_package
32             }, $package;
33             }
34 0 0   0 0   sub name { $_[0]->{name} = $_[1] if defined $_[1]; return $_[0]->{name}; }
  0            
35 0     0 0   sub sql_type($) { die ref($_[0]); }
36 0     0 0   sub is_relation { return undef; }
37 0     0 0   sub is_qualifier { return undef; }
38 0     0 0   sub qualifier { return undef; }
39 0     0 0   sub to_shopify($$) { return $_[1]; }
40 0     0 0   sub from_shopify($$) { return $_[1]; }
41 0     0 0   sub generate($) { die "Can't generate $_[0]"; }
42 0     0 0   sub validate($) { die "Can't validate $_[0]"; }
43 0 0   0 0   sub owner { $_[0]->{owner} = $_[1] if defined $_[1]; return $_[0]->{owner}; }
  0            
44              
45 0     0 0   sub is_db_belongs_to { return undef; }
46 0     0 0   sub is_db_has_many { return undef; }
47 0     0 0   sub is_db_many_many { return undef; }
48 0     0 0   sub is_db_has_one { return undef; }
49              
50             use constant {
51 1         119 TYPE_QUANTITATIVE => 0,
52             TYPE_QUALITATIVE => 1,
53             TYPE_BOOLEAN => 2
54 1     1   6 };
  1         2  
55              
56             use constant {
57             # No order, no set distance (E.G. Telephone Numbers)
58 1         81 SET_NOMINAL => 0,
59             # No set distance between values.
60             SET_ORDINAL => 1,
61             # Distinguishable distance between values. (E.G. Real Numbers)
62             SET_CARDINAL => 2
63 1     1   6 };
  1         36  
64              
65 1     1   5 use base 'Exporter';
  1         2  
  1         533  
66             our @EXPORT_OK = qw(TYPE_QUANTITATIVE TYPE_QUALITATIVE TYPE_BOOLEAN SET_NOMINAL SET_ORDINAL SET_CARDINAL);
67              
68 0     0 0   sub data_type { return TYPE_QUALITATIVE; }
69 0     0 0   sub is_qualitative { return $_[0]->data_type == TYPE_QUANTITATIVE; }
70 0     0 0   sub is_quantitative { return $_[0]->data_type == TYPE_QUALITATIVE; }
71 0     0 0   sub is_boolean { return $_[0]->data_type == TYPE_BOOLEAN; }
72 0 0   0 0   sub number_type { return $_[0]->is_quantitative ? SET_CARDINAL : SET_NOMINAL }
73              
74             package WWW::Shopify::Field::Hook;
75 1     1   5 use parent 'WWW::Shopify::Field';
  1         1  
  1         4  
76 0     0     sub new($) { return bless { internal => $_[1], qualifier => $_[2] }, $_[0]; }
77 0     0     sub sql_type($) { return shift->{internal}->sql_type(@_); }
78 0     0     sub is_relation { return shift->{internal}->is_relation(@_); }
79 0     0     sub to_shopify($$) { return shift->{internal}->to_shopify(@_); }
80 0     0     sub from_shopify($$) { return shift->{internal}->from_shopify(@_); }
81 0     0     sub generate($) { return shift->{internal}->generate(@_); }
82 0     0     sub relation($) { return shift->{internal}->relation(@_); }
83 0     0     sub is_many { return shift->{internal}->is_many(@_); }
84 0     0     sub is_one { return shift->{internal}->is_one(@_); }
85 0     0     sub is_own { return shift->{internal}->is_own(@_); }
86 0     0     sub is_reference { return shift->{internal}->is_reference(@_); }
87              
88             package WWW::Shopify::Field::Int;
89 1     1   357 use parent 'WWW::Shopify::Field';
  1         2  
  1         3  
90 1     1   819 use String::Numeric qw(is_int);
  1         3164  
  1         218  
91 0     0     sub sql_type { return "int"; }
92             sub generate($) {
93 0 0   0     return int(rand(10000)) if (int(@{$_[0]->{arguments}}) == 0);
  0            
94 0 0         return $_[0]->{arguments}->[0] if (int(@{$_[0]->{arguments}}) == 1);
  0            
95 0           return int(rand($_[0]->{arguments}->[1] - $_[0]->{arguments}->[0] + 1) + $_[0]->{arguments}->[0]);
96             }
97 0     0     sub data_type { return WWW::Shopify::Field::TYPE_QUANTITATIVE; }
98 0     0     sub validate($$) { return is_int($_[1]); }
99              
100             package WWW::Shopify::Field::BigInt;
101 1     1   6 use parent 'WWW::Shopify::Field';
  1         1  
  1         6  
102 1     1   54 use String::Numeric qw(is_int);
  1         2  
  1         195  
103 0     0     sub sql_type { return "bigint"; }
104             sub generate($) {
105 0 0   0     return int(rand(10000000)) if (int(@{$_[0]->{arguments}}) == 0);
  0            
106 0 0         return $_[0]->{arguments}->[0] if (int(@{$_[0]->{arguments}}) == 1);
  0            
107 0           return int(rand($_[0]->{arguments}->[1] - $_[0]->{arguments}->[0] + 1) + $_[0]->{arguments}->[0]);
108             }
109 0     0     sub data_type { return WWW::Shopify::Field::TYPE_QUANTITATIVE; }
110 0     0     sub validate { return is_int($_[1]); }
111              
112             package WWW::Shopify::Field::Boolean;
113 1     1   5 use Scalar::Util qw(looks_like_number);
  1         2  
  1         44  
114 1     1   1052 use JSON;
  1         13199  
  1         5  
115 1     1   166 use parent 'WWW::Shopify::Field';
  1         2  
  1         7  
116 0     0     sub sql_type { return "bool"; }
117 0     0     sub generate { return (rand() < 0.5); }
118 0 0 0 0     sub validate { return looks_like_number($_[1]) ? ($_[1] == 0 || $_[1] == 1) : (lc($_[1]) == 'true' || lc($_[1]) == 'false'); }
      0        
119 0     0     sub data_type { return WWW::Shopify::Field::TYPE_BOOLEAN; }
120 0 0   0     sub to_shopify { return $_[1] ? JSON::true : JSON::false; }
121 0 0   0     sub from_shopify { return $_[1] ? 1 : 0; }
122              
123             package WWW::Shopify::Field::Float;
124 1     1   225 use parent 'WWW::Shopify::Field';
  1         2  
  1         4  
125 1     1   51 use String::Numeric qw(is_float);
  1         3  
  1         199  
126 0     0     sub sql_type { return "float"; }
127             sub generate($) {
128 0 0   0     return rand() * 10000.0 if (int(@{$_[0]->{arguments}}) == 0);
  0            
129 0 0         return $_[0]->{arguments}->[0] if (int(@{$_[0]->{arguments}}) == 1);
  0            
130 0           return rand($_[0]->{arguments}->[1] - $_[0]->{arguments}->[0] + 1) + $_[0]->{arguments}->[0];
131             }
132 0     0     sub data_type { return WWW::Shopify::Field::TYPE_QUANTITATIVE; }
133 0     0     sub validate($$) { return is_float($_[1]); }
134              
135             package WWW::Shopify::Field::Timezone;
136 1     1   6 use parent 'WWW::Shopify::Field';
  1         2  
  1         5  
137 0     0     sub sql_type { return "varchar(255)"; }
138              
139             my %from_shopify_timezones = (
140             "(GMT-05:00) Eastern Time (US & Canada)" => "America/New_York" ,
141             "(GMT-11:00) International Date Line West" => "-1100",
142             "(GMT-11:00) Midway Island" => "Pacific/Midway",
143             "(GMT-11:00) American Samoa" => "Pacific/Pago_Pago",
144             "(GMT-10:00) Hawaii" => "Pacific/Honolulu",
145             "(GMT-09:00) Alaska" => "America/Anchorage",
146             "(GMT-08:00) Pacific Time (US & Canada)" => "PST8PDT",
147             "(GMT-08:00) Tijuana" => "America/Tijuana",
148             "(GMT-07:00) Mountain Time (US & Canada)" => "MST7MDT",
149             "(GMT-07:00) Arizona" => "MST",
150             "(GMT-07:00) Chihuahua" => "America/Chihuahua",
151             "(GMT-07:00) Mazatlan" => "MST",
152             "(GMT-06:00) Central Time (US & Canada)" => "CST6CDT",
153             "(GMT-06:00) Saskatchewan" => "-0600",
154             "(GMT-06:00) Guadalajara" => "CST6CDT",
155             "(GMT-06:00) Mexico City" => "CST6CDT",
156             "(GMT-06:00) Monterrey" => "America/Monterrey",
157             "(GMT-06:00) Central America" => "CST6CDT",
158             "(GMT-05:00) Indiana (East)" => "EST5EDT",
159             "(GMT-05:00) Bogota" => "America/Bogota",
160             "(GMT-05:00) Lima" => "America/Lima",
161             "(GMT-05:00) Quito" => "-0500",
162             "(GMT-04:00) Atlantic Time (Canada)" => "America/Halifax",
163             "(GMT-04:30) Caracas" => "America/Caracas",
164             "(GMT-04:00) La Paz" => "-04:00",
165             "(GMT-04:00) Santiago" => "America/Santiago",
166             "(GMT-03:30) Newfoundland" => "America/La_Paz",
167             "(GMT-03:00) Brasilia" => "America/Sao_Paulo",
168             "(GMT-03:00) Buenos Aires" => "America/Argentina/Buenos_Aires",
169             "(GMT-04:00) Georgetown" => "America/Guyana",
170             "(GMT-03:00) Greenland" => "-0300",
171             "(GMT-02:00) Mid-Atlantic" => "-0200",
172             "(GMT-01:00) Azores" => "Atlantic/Azores",
173             "(GMT-01:00) Cape Verde Is." => "Atlantic/Cape_Verde",
174             "(GMT+00:00) Dublin" => "Europe/Dublin",
175             "(GMT+00:00) Edinburgh" => "Europe/London",
176             "(GMT+00:00) Lisbon" => "Europe/Lisbon",
177             "(GMT+00:00) London" => "Europe/London",
178             "(GMT+00:00) Casablanca" => "Africa/Casablanca",
179             "(GMT+00:00) Monrovia" => "Africa/Monrovia",
180             "(GMT+00:00) UTC" => "UTC",
181             "(GMT+01:00) Belgrade" => "Europe/Belgrade",
182             "(GMT+01:00) Bratislava" => "Europe/Bratislava",
183             "(GMT+01:00) Budapest" => "Europe/Budapest",
184             "(GMT+01:00) Ljubljana" => "Europe/Ljubljana",
185             "(GMT+01:00) Prague" => "Europe/Prague",
186             "(GMT+01:00) Sarajevo" => "Europe/Sarajevo",
187             "(GMT+01:00) Skopje" => "Europe/Skopje",
188             "(GMT+01:00) Warsaw" => "Europe/Warsaw",
189             "(GMT+01:00) Zagreb" => "Europe/Zagreb",
190             "(GMT+01:00) Brussels" => "Europe/Brussels",
191             "(GMT+01:00) Copenhagen" => "Europe/Copenhagen",
192             "(GMT+01:00) Madrid" => "Europe/Madrid",
193             "(GMT+01:00) Paris" => "Europe/Paris",
194             "(GMT+01:00) Amsterdam" => "Europe/Amsterdam",
195             "(GMT+01:00) Berlin" => "Europe/Berlin",
196             "(GMT+01:00) Bern" => "Europe/Zurich",
197             "(GMT+01:00) Rome" => "Europe/Rome",
198             "(GMT+01:00) Stockholm" => "Europe/Stockholm",
199             "(GMT+01:00) Vienna" => "Europe/Vienna",
200             "(GMT+01:00) West Central Africa" => "+0100",
201             "(GMT+02:00) Bucharest" => "Europe/Bucharest",
202             "(GMT+02:00) Cairo" => "Africa/Cairo",
203             "(GMT+02:00) Helsinki" => "Europe/Helsinki",
204             "(GMT+02:00) Kyiv" => "Europe/Kiev",
205             "(GMT+02:00) Riga" => "Europe/Riga",
206             "(GMT+02:00) Sofia" => "Europe/Sofia",
207             "(GMT+02:00) Tallinn" => "Europe/Tallinn",
208             "(GMT+02:00) Vilnius" => "Europe/Vilnius",
209             "(GMT+02:00) Athens" => "Europe/Athens",
210             "(GMT+02:00) Istanbul" => "Europe/Istanbul",
211             "(GMT+03:00) Minsk" => "Europe/Minsk",
212             "(GMT+02:00) Jerusalem" => "Asia/Jerusalem",
213             "(GMT+02:00) Harare" => "Africa/Harare",
214             "(GMT+02:00) Pretoria" => "Africa/Maseru",
215             "(GMT+04:00) Moscow" => "Europe/Moscow",
216             "(GMT+04:00) St. Petersburg" => "Europe/Moscow",
217             "(GMT+04:00) Volgograd" => "Europe/Volgograd",
218             "(GMT+03:00) Kuwait" => "Asia/Kuwait",
219             "(GMT+03:00) Riyadh" => "Asia/Riyadh",
220             "(GMT+03:00) Nairobi" => "Africa/Nairobi",
221             "(GMT+03:00) Baghdad" => "Asia/Baghdad",
222             "(GMT+03:30) Tehran" => "Asia/Tehran",
223             "(GMT+04:00) Abu Dhabi" => "+0400",
224             "(GMT+04:00) Muscat" => "Asia/Muscat",
225             "(GMT+04:00) Baku" => "Asia/Baku",
226             "(GMT+04:00) Tbilisi" => "Asia/Tbilisi",
227             "(GMT+04:00) Yerevan" => "Asia/Yerevan",
228             "(GMT+04:30) Kabul" => "Asia/Kabul",
229             "(GMT+06:00) Ekaterinburg" => "Asia/Yekaterinburg",
230             "(GMT+05:00) Islamabad" => "+05:00",
231             "(GMT+05:00) Karachi" => "Asia/Karachi",
232             "(GMT+05:00) Tashkent" => "Asia/Tashkent",
233             "(GMT+05:30) Chennai" => "+05:30",
234             "(GMT+05:30) Kolkata" => "Asia/Kolkata",
235             "(GMT+05:30) Mumbai" => "+05:30",
236             "(GMT+05:30) New Delhi" => "+05:30",
237             "(GMT+05:45) Kathmandu" => "Asia/Kathmandu",
238             "(GMT+06:00) Astana" => "Asia/Thimphu",
239             "(GMT+06:00) Dhaka" => "Asia/Dhaka",
240             "(GMT+05:30) Sri Jayawardenepura" => "+0530",
241             "(GMT+06:00) Almaty" => "Asia/Almaty",
242             "(GMT+07:00) Novosibirsk" => "Asia/Novosibirsk",
243             "(GMT+06:30) Rangoon" => "Asia/Rangoon",
244             "(GMT+07:00) Bangkok" => "Asia/Bangkok",
245             "(GMT+07:00) Hanoi" => "+0700",
246             "(GMT+07:00) Jakarta" => "Asia/Jakarta",
247             "(GMT+08:00) Krasnoyarsk" => "Asia/Krasnoyarsk",
248             "(GMT+08:00) Beijing" => "Asia/Shanghai",
249             "(GMT+08:00) Chongqing" => "Asia/Chongqing",
250             "(GMT+08:00) Hong Kong" => "Asia/Hong_Kong",
251             "(GMT+08:00) Urumqi" => "Asia/Urumqi",
252             "(GMT+08:00) Kuala Lumpur" => "Asia/Kuala_Lumpur",
253             "(GMT+08:00) Singapore" => "Asia/Singapore",
254             "(GMT+08:00) Taipei" => "Asia/Taipei",
255             "(GMT+08:00) Perth" => "Australia/Perth",
256             "(GMT+09:00) Irkutsk" => "Asia/Irkutsk",
257             "(GMT+08:00) Ulaan Bataar" => "Asia/Ulaanbaatar",
258             "(GMT+09:00) Seoul" => "Asia/Seoul",
259             "(GMT+09:00) Osaka" => "Asia/Tokyo",
260             "(GMT+09:00) Sapporo" => "Asia/Tokyo",
261             "(GMT+09:00) Tokyo" => "Asia/Tokyo",
262             "(GMT+10:00) Yakutsk" => "Asia/Yakutsk",
263             "(GMT+09:30) Darwin" => "Australia/Darwin",
264             "(GMT+09:30) Adelaide" => "Australia/Adelaide",
265             "(GMT+10:00) Canberra" => "Australia/Canberra",
266             "(GMT+10:00) Melbourne" => "Australia/Melbourne",
267             "(GMT+10:00) Sydney" => "Australia/Sydney",
268             "(GMT+10:00) Brisbane" => "Australia/Brisbane",
269             "(GMT+10:00) Hobart" => "Australia/Hobart",
270             "(GMT+11:00) Vladivostok" => "Asia/Vladivostok",
271             "(GMT+10:00) Guam" => "Pacific/Guam",
272             "(GMT+10:00) Port Moresby" => "Pacific/Port_Moresby",
273             "(GMT+12:00) Magadan" => "Asia/Magadan",
274             "(GMT+12:00) Solomon Is." => "+1100",
275             "(GMT+11:00) New Caledonia" => "+1100",
276             "(GMT+12:00) Fiji" => "Pacific/Fiji",
277             "(GMT+12:00) Kamchatka" => "Asia/Kamchatka",
278             "(GMT+12:00) Marshall Is." => "Pacific/Majuro",
279             "(GMT+12:00) Auckland" => "Pacific/Auckland",
280             "(GMT+12:00) Wellington" => "Pacific/Auckland",
281             "(GMT+13:00) Nuku'alofa" => "Pacific/Tongatapu",
282             "(GMT+13:00) Tokelau Is." => "Pacific/Fakaofo",
283             "(GMT+13:00) Samoa" => "Pacific/Apia"
284             );
285             my %to_shopify_timezones = reverse(%from_shopify_timezones);
286              
287 0     0     sub timezone_mapping { return \%to_shopify_timezones; }
288 0     0     sub shopify_timezones { return keys(%from_shopify_timezones); }
289              
290             sub to_shopify {
291 0     0     my $dttz = $_[1];
292 0 0         return undef unless $dttz;
293 0           my $mapping = $to_shopify_timezones{$dttz->name};
294 0 0         $mapping = "(GMT+00:00) UTC" unless $mapping;
295 0           return $mapping;
296             }
297              
298             sub from_shopify {
299 0     0     my $dttz = $_[1];
300 0 0         return undef unless $dttz;
301 0           my $mapping = $from_shopify_timezones{$dttz};
302 0 0         $mapping = "UTC" unless $mapping;
303 0           return DateTime::TimeZone->new(name => $mapping);
304             }
305              
306             sub generate {
307 0     0     my @timezones = keys(%from_shopify_timezones);
308 0           return $timezones[int(rand(@timezones))];
309             }
310              
311              
312             package WWW::Shopify::Field::Currency;
313 1     1   688 use parent 'WWW::Shopify::Field';
  1         2  
  1         5  
314 0     0     sub sql_type { return "varchar(255)"; }
315 0 0   0     sub generate($) { return rand() < 0.5 ? "USD" : "CAD"; }
316              
317             package WWW::Shopify::Field::Money;
318 1     1   99 use parent 'WWW::Shopify::Field';
  1         2  
  1         3  
319 1     1   48 use String::Numeric qw(is_float);
  1         3  
  1         183  
320 0     0     sub sql_type { return "decimal(10,2)"; }
321 0     0     sub generate($) { return sprintf("%.2f", rand(500)); }
322 0 0   0     sub validate($) { return undef unless $_[1] =~ m/\s*\$?\s*$/; return is_float($`); }
  0            
323 0     0     sub data_type { return WWW::Shopify::Field::TYPE_QUANTITATIVE; }
324              
325             package WWW::Shopify::Field::Money::USD;
326 1     1   5 use parent -norequire, 'WWW::Shopify::Field::Money';
  1         3  
  1         5  
327              
328             package WWW::Shopify::Field::Date;
329 1     1   46 use parent 'WWW::Shopify::Field';
  1         2  
  1         4  
330 1     1   1547 use DateTime;
  1         157765  
  1         592  
331 0     0     sub sql_type { return 'datetime'; }
332             sub to_shopify {
333 0     0     my $dt = $_[1];
334 0 0         return undef unless $dt;
335 0 0         if (ref($dt) eq "DateTime") {
336 0           my $t = $dt->strftime('%Y-%m-%dT%H:%M:%S%z');
337 0           $t =~ s/(\d\d)$/:$1/;
338 0           return $t;
339             }
340 0 0         die new WWW::Shopify::Exception($dt) unless $dt =~ m/([\d-]+)\s*T?\s*([\d:]+)/;
341 0           return "$1T$2";
342             }
343             sub from_shopify {
344 0     0     my $dt;
345 0 0         return undef unless $_[1];
346 0 0         if ($_[1] =~ m/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)([+-]\d+):(\d+)/) {
    0          
347 0           $dt = DateTime->new(
348             year => $1,
349             month => $2,
350             day => $3,
351             hour => $4,
352             minute => $5,
353             second => $6,
354             time_zone => $7 . $8,
355             );
356             }
357             elsif ($_[1] =~ m/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)/) {
358 0           $dt = DateTime->new(
359             year => $1,
360             month => $2,
361             day => $3,
362             hour => $4,
363             minute => $5,
364             second => $6,
365             );
366             }
367             else {
368 0 0         die new WWW::Shopify::Exception("Unable to parse date " . $_[1]) unless $_[1] =~ m/(\d+)-(\d+)-(\d+)/;
369 0           $dt = DateTime->new(
370             year => $1,
371             month => $2,
372             day => $3
373             );
374             }
375 0           return $dt;
376             }
377 0     0     sub validate($) { return scalar($_[1] =~ m/(\d+-\d+-\d+)T?(\d+:\d+:\d+)/); }
378              
379             sub generate($) {
380 0     0     my %hash = @{$_[0]->{arguments}};
  0            
381 0 0         $hash{min} = '2010-01-01 00:00:00' unless $hash{min};
382 0 0         $hash{max} = 'now' unless $hash{max};
383 0           return ::rand_datetime(%hash);
384             }
385 0     0     sub data_type { return WWW::Shopify::Field::TYPE_QUANTITATIVE; }
386              
387             # Freeform datastructure. Meaning we convert it to a JSON hash, but we don't do any further processing at all.
388             package WWW::Shopify::Field::Freeform;
389 1     1   10 use parent 'WWW::Shopify::Field';
  1         2  
  1         6  
390 0     0     sub sql_type { return "text"; }
391 0     0     sub generate { return {}; }
392 0     0     sub data_type { return WWW::Shopify::Field::TYPE_QUALITATIVE; }
393 0   0 0     sub validate { return !defined $_[1] || (ref($_[1]) && ref($_[1]) eq "HASH"); }
394 0     0     sub to_shopify { return $_[1]; }
395 0     0     sub from_shopify { return $_[1]; }
396              
397             package WWW::Shopify::Field::Freeform::Array;
398 1     1   193 use parent 'WWW::Shopify::Field';
  1         2  
  1         3  
399 0     0     sub sql_type { return "text"; }
400 0     0     sub generate { return []; }
401 0     0     sub data_type { return WWW::Shopify::Field::TYPE_QUALITATIVE; }
402 0   0 0     sub validate { return !defined $_[1] || (ref($_[1]) && ref($_[1]) eq "ARRAY"); }
403 0     0     sub to_shopify { return $_[1]; }
404 0     0     sub from_shopify { return $_[1]; }
405              
406             1;