duty_free 1.0.3 → 1.0.8
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/lib/duty_free.rb +358 -18
- data/lib/duty_free/column.rb +3 -7
- data/lib/duty_free/extensions.rb +800 -484
- data/lib/duty_free/suggest_template.rb +26 -23
- data/lib/duty_free/util.rb +145 -13
- data/lib/duty_free/version_number.rb +1 -1
- metadata +16 -31
@@ -40,33 +40,31 @@ module DutyFree
|
|
40
40
|
assocs = {}
|
41
41
|
this_klass.reflect_on_all_associations.each do |assoc|
|
42
42
|
# PolymorphicReflection AggregateReflection RuntimeReflection
|
43
|
-
is_belongs_to = assoc.
|
43
|
+
is_belongs_to = assoc.belongs_to?
|
44
44
|
# Figure out if it's belongs_to, has_many, or has_one
|
45
45
|
belongs_to_or_has_many =
|
46
46
|
if is_belongs_to
|
47
47
|
'belongs_to'
|
48
|
-
elsif (is_habtm = assoc.
|
48
|
+
elsif (is_habtm = assoc.macro == :has_and_belongs_to_many)
|
49
49
|
'has_and_belongs_to_many'
|
50
|
+
elsif assoc.macro == :has_many
|
51
|
+
'has_many'
|
50
52
|
else
|
51
|
-
|
53
|
+
'has_one'
|
52
54
|
end
|
53
55
|
# Always process belongs_to, and also process has_one and has_many if do_has_many is chosen.
|
54
56
|
# Skip any HMT or HABTM. (Maybe break out HABTM into a combo HM and BT in the future.)
|
55
57
|
if is_habtm
|
56
|
-
unless ActiveRecord::Base.connection.table_exists?(assoc.join_table)
|
57
|
-
puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because join table \"#{assoc.join_table}\" does not exist. You can create it with a create_join_table migration."
|
58
|
-
end
|
58
|
+
puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because join table \"#{assoc.join_table}\" does not exist. You can create it with a create_join_table migration." unless ActiveRecord::Base.connection.table_exists?(assoc.join_table)
|
59
59
|
# %%% Search for other associative candidates to use instead of this HABTM contraption
|
60
|
-
if assoc.options.include?(:through)
|
61
|
-
puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because it includes \"through: #{assoc.options[:through].inspect}\" which is pointless and should be removed."
|
62
|
-
end
|
60
|
+
puts "* In the #{this_klass.name} model there's a problem with: \"has_and_belongs_to_many :#{assoc.name}\" because it includes \"through: #{assoc.options[:through].inspect}\" which is pointless and should be removed." if assoc.options.include?(:through)
|
63
61
|
end
|
64
62
|
if (is_through = assoc.is_a?(ActiveRecord::Reflection::ThroughReflection)) && assoc.options.include?(:as)
|
65
63
|
puts "* In the #{this_klass.name} model there's a problem with: \"has_many :#{assoc.name} through: #{assoc.options[:through].inspect}\" because it also includes \"as: #{assoc.options[:as].inspect}\", so please choose either for this line to be a \"has_many :#{assoc.name} through:\" or to be a polymorphic \"has_many :#{assoc.name} as:\". It can't be both."
|
66
64
|
end
|
67
65
|
next if is_through || is_habtm || (!is_belongs_to && !do_has_many) || errored_assocs.include?(assoc)
|
68
66
|
|
69
|
-
if is_belongs_to && assoc.polymorphic
|
67
|
+
if is_belongs_to && assoc.options[:polymorphic] # Polymorphic belongs_to?
|
70
68
|
# Load all models
|
71
69
|
# %%% Note that this works in Rails 5.x, but may not work in Rails 6.0 and later, which uses the Zeitwerk loader by default:
|
72
70
|
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
@@ -81,8 +79,7 @@ module DutyFree
|
|
81
79
|
|
82
80
|
# Find applicable polymorphic has_many associations from each real model
|
83
81
|
model.reflect_on_all_associations.each do |poly_assoc|
|
84
|
-
next unless poly_assoc.
|
85
|
-
poly_assoc.inverse_of == assoc
|
82
|
+
next unless poly_assoc.macro == :has_many && poly_assoc.inverse_of == assoc
|
86
83
|
|
87
84
|
this_belongs_tos += (fkeys = [poly_assoc.type, poly_assoc.foreign_key])
|
88
85
|
assocs["#{assoc.name}_#{poly_assoc.active_record.name.underscore}".to_sym] = [[fkeys, assoc.active_record], poly_assoc.active_record]
|
@@ -90,7 +87,7 @@ module DutyFree
|
|
90
87
|
end
|
91
88
|
else
|
92
89
|
# Is it a polymorphic has_many, which is defined using as: :somethingable ?
|
93
|
-
is_polymorphic_hm = assoc.inverse_of&.polymorphic
|
90
|
+
is_polymorphic_hm = assoc.inverse_of&.options&.fetch(:polymorphic) { nil }
|
94
91
|
begin
|
95
92
|
# Standard has_one, or has_many, and belongs_to uses assoc.klass.
|
96
93
|
# Also polymorphic belongs_to uses assoc.klass.
|
@@ -106,9 +103,9 @@ module DutyFree
|
|
106
103
|
[[[fk], assoc.active_record], assoc_klass]
|
107
104
|
else # has_many or has_one
|
108
105
|
inverse_foreign_keys = is_polymorphic_hm ? [assoc.type, assoc.foreign_key] : [assoc.inverse_of&.foreign_key&.to_s]
|
109
|
-
puts "* Missing inverse foreign key for #{assoc.inspect}" if inverse_foreign_keys.first.nil?
|
110
106
|
missing_key_columns = inverse_foreign_keys - assoc_klass.columns.map(&:name)
|
111
107
|
if missing_key_columns.empty?
|
108
|
+
puts "* Missing inverse foreign key for #{this_klass.name} #{belongs_to_or_has_many} :#{assoc.name}" if inverse_foreign_keys.first.nil?
|
112
109
|
# puts "Has columns #{inverse_foreign_keys.inspect}"
|
113
110
|
[[inverse_foreign_keys, assoc_klass], assoc_klass]
|
114
111
|
else
|
@@ -182,10 +179,11 @@ module DutyFree
|
|
182
179
|
# Find belongs_tos for this model to one more more other klasses
|
183
180
|
def self._find_belongs_tos(klass, to_klass, errored_assocs)
|
184
181
|
klass.reflect_on_all_associations.each_with_object([]) do |bt_assoc, s|
|
185
|
-
|
182
|
+
# .is_a?(ActiveRecord::Reflection::BelongsToReflection)
|
183
|
+
next unless bt_assoc.belongs_to? && !errored_assocs.include?(bt_assoc)
|
186
184
|
|
187
185
|
begin
|
188
|
-
s << bt_assoc if !bt_assoc.polymorphic
|
186
|
+
s << bt_assoc if !bt_assoc.options[:polymorphic] && bt_assoc.klass == to_klass
|
189
187
|
rescue NameError
|
190
188
|
errored_assocs << bt_assoc
|
191
189
|
puts "* In the #{bt_assoc.active_record.name} model \"belongs_to :#{bt_assoc.name}\" could not find a model named #{bt_assoc.class_name}."
|
@@ -203,9 +201,7 @@ module DutyFree
|
|
203
201
|
|
204
202
|
# Requireds takes its cues from all attributes having a presence validator
|
205
203
|
requireds = _find_requireds(klass)
|
206
|
-
if priority_excluded_columns
|
207
|
-
klass_columns = klass_columns.reject { |col| priority_excluded_columns.include?(col.name) }
|
208
|
-
end
|
204
|
+
klass_columns = klass_columns.reject { |col| priority_excluded_columns.include?(col.name) } if priority_excluded_columns
|
209
205
|
excluded_columns = %w[created_at updated_at deleted_at]
|
210
206
|
unique = [(
|
211
207
|
# Find the first text field of a required if one exists
|
@@ -240,8 +236,14 @@ module DutyFree
|
|
240
236
|
if klass.columns.map(&:name).include?(attrib)
|
241
237
|
s << attrib
|
242
238
|
else
|
243
|
-
|
244
|
-
|
239
|
+
hm_and_bt_names = klass.reflect_on_all_associations.each_with_object([]) do |assoc, names|
|
240
|
+
names << assoc.name.to_s if [:belongs_to, :has_many, :has_one].include?(assoc.macro)
|
241
|
+
names
|
242
|
+
end
|
243
|
+
unless hm_and_bt_names.include?(attrib)
|
244
|
+
puts "* In the #{klass.name} model \"validates_presence_of :#{attrib}\" should be removed as it does not refer to any existing column."
|
245
|
+
errored_columns << klass_col
|
246
|
+
end
|
245
247
|
end
|
246
248
|
end
|
247
249
|
end
|
@@ -283,10 +285,11 @@ module DutyFree
|
|
283
285
|
v.each_with_index do |item, idx|
|
284
286
|
# This is where most of the commas get printed, so you can do "#{child_count}," to diagnose things
|
285
287
|
print ',' if idx.positive? && indent >= 0
|
286
|
-
|
288
|
+
case item
|
289
|
+
when Hash
|
287
290
|
# puts '^' unless child_count < 5 || indent.negative?
|
288
291
|
child_count = _template_pretty_print(item, indent + 2, child_count)
|
289
|
-
|
292
|
+
when Symbol
|
290
293
|
if indent.negative?
|
291
294
|
child_count += 1
|
292
295
|
else
|
data/lib/duty_free/util.rb
CHANGED
@@ -25,34 +25,59 @@ module DutyFree
|
|
25
25
|
def self._recurse_arel(piece, prefix = '')
|
26
26
|
names = []
|
27
27
|
# Our JOINs mashup of nested arrays and hashes
|
28
|
-
|
28
|
+
case piece
|
29
|
+
when Array
|
29
30
|
names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
|
30
|
-
|
31
|
+
when Hash
|
31
32
|
names += piece.inject([]) do |s, v|
|
32
33
|
new_prefix = "#{prefix}#{v.first}_"
|
33
|
-
s << new_prefix
|
34
|
+
s << [v.last.shift, new_prefix]
|
34
35
|
s + _recurse_arel(v.last, new_prefix)
|
35
36
|
end
|
36
37
|
|
37
38
|
# ActiveRecord AREL objects
|
38
|
-
|
39
|
+
when Arel::Nodes::Join # INNER or OUTER JOIN
|
40
|
+
# rubocop:disable Style/IdenticalConditionalBranches
|
41
|
+
if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
|
42
|
+
# Arel 2.x and older is a little curious because these JOINs work "back to front".
|
43
|
+
# The left side here is either another earlier JOIN, or at the end of the whole tree, it is
|
44
|
+
# the first table.
|
45
|
+
names += _recurse_arel(piece.left)
|
46
|
+
# The right side here at the top is the very last table, and anywhere else down the tree it is
|
47
|
+
# the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
|
48
|
+
# from the left side.)
|
49
|
+
names << [_arel_table_type(piece.right), (piece.right.table_alias || piece.right.name)]
|
50
|
+
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
51
|
+
# The left side is the "JOIN" table
|
52
|
+
names += _recurse_arel(piece.left)
|
53
|
+
# (The right side of these is the "ON" clause)
|
54
|
+
end
|
55
|
+
# rubocop:enable Style/IdenticalConditionalBranches
|
56
|
+
when Arel::Table # Table
|
57
|
+
names << [_arel_table_type(piece), (piece.table_alias || piece.name)]
|
58
|
+
when Arel::Nodes::TableAlias # Alias
|
59
|
+
# Can get the real table name from: self._recurse_arel(piece.left)
|
60
|
+
names << [_arel_table_type(piece.left), piece.right.to_s] # This is simply a string; the alias name itself
|
61
|
+
when Arel::Nodes::JoinSource # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
|
39
62
|
# The left side is the "FROM" table
|
40
63
|
# names += _recurse_arel(piece.left)
|
64
|
+
names << [_arel_table_type(piece.left), (piece.left.table_alias || piece.left.name)]
|
41
65
|
# The right side is an array of all JOINs
|
42
66
|
names += piece.right.inject([]) { |s, v| s + _recurse_arel(v) }
|
43
|
-
elsif piece.is_a?(Arel::Nodes::Join) # INNER or OUTER JOIN
|
44
|
-
# The left side is the "JOIN" table
|
45
|
-
names += _recurse_arel(piece.left)
|
46
|
-
# (The right side of these is the "ON" clause)
|
47
|
-
elsif piece.is_a?(Arel::Table) # Table
|
48
|
-
names << piece.name
|
49
|
-
elsif piece.is_a?(Arel::Nodes::TableAlias) # Alias
|
50
|
-
# Can get the real table name from: self._recurse_arel(piece.left)
|
51
|
-
names << piece.right.to_s # This is simply a string; the alias name itself
|
52
67
|
end
|
53
68
|
names
|
54
69
|
end
|
55
70
|
|
71
|
+
def self._arel_table_type(tbl)
|
72
|
+
# AR < 4.2 doesn't have type_caster at all, so rely on an instance variable getting set
|
73
|
+
# AR 4.2 - 5.1 have buggy type_caster entries for the root node
|
74
|
+
tbl.instance_variable_get(:@_arel_table_type) ||
|
75
|
+
# 5.2-6.1 does type_caster just fine, no bugs there, but the property with the type differs:
|
76
|
+
# 5.2 has "types" as public, 6.0 "types" as private, and 6.1 "klass" as private.
|
77
|
+
((tc = tbl.send(:type_caster)) && tc.instance_variable_get(:@types)) ||
|
78
|
+
tc.send(:klass)
|
79
|
+
end
|
80
|
+
|
56
81
|
def self._prefix_join(prefixes, separator = nil)
|
57
82
|
prefixes.reject(&:blank?).join(separator || '.')
|
58
83
|
end
|
@@ -69,5 +94,112 @@ module DutyFree
|
|
69
94
|
end
|
70
95
|
name
|
71
96
|
end
|
97
|
+
|
98
|
+
# ===================================
|
99
|
+
# Epic require patch
|
100
|
+
def self._patch_require(module_filename, folder_matcher, search_text, replacement_text, autoload_symbol = nil)
|
101
|
+
mod_name_parts = module_filename.split('.')
|
102
|
+
extension = case mod_name_parts.last
|
103
|
+
when 'rb', 'so', 'o'
|
104
|
+
module_filename = mod_name_parts[0..-2].join('.')
|
105
|
+
".#{mod_name_parts.last}"
|
106
|
+
else
|
107
|
+
'.rb'
|
108
|
+
end
|
109
|
+
|
110
|
+
if autoload_symbol
|
111
|
+
unless Object.const_defined?('ActiveSupport::Dependencies')
|
112
|
+
require 'active_support'
|
113
|
+
require 'active_support/dependencies'
|
114
|
+
end
|
115
|
+
alp = ActiveSupport::Dependencies.autoload_paths
|
116
|
+
custom_require_dir = ::DutyFree::Util._custom_require_dir
|
117
|
+
# Create any missing folder structure leading up to this file
|
118
|
+
module_filename.split('/')[0..-2].inject(custom_require_dir) do |s, part|
|
119
|
+
new_part = File.join(s, part)
|
120
|
+
Dir.mkdir(new_part) unless Dir.exist?(new_part)
|
121
|
+
new_part
|
122
|
+
end
|
123
|
+
if ::DutyFree::Util._write_patched(folder_matcher, module_filename, extension, custom_require_dir, nil, search_text, replacement_text) &&
|
124
|
+
!alp.include?(custom_require_dir)
|
125
|
+
alp.unshift(custom_require_dir)
|
126
|
+
end
|
127
|
+
else
|
128
|
+
unless (require_overrides = ::DutyFree::Util.instance_variable_get(:@_require_overrides))
|
129
|
+
::DutyFree::Util.instance_variable_set(:@_require_overrides, (require_overrides = {}))
|
130
|
+
|
131
|
+
# Patch "require" itself so that when it specifically sees "active_support/values/time_zone" then
|
132
|
+
# a copy is taken of the original, an attempt is made to find the line with a circular error, that
|
133
|
+
# single line is patched, and then an updated version is written to a temporary folder which is
|
134
|
+
# then required in place of the original.
|
135
|
+
|
136
|
+
Kernel.module_exec do
|
137
|
+
# class << self
|
138
|
+
alias_method :orig_require, :require
|
139
|
+
# end
|
140
|
+
# To be most faithful to Ruby's normal behaviour, this should look like a public singleton
|
141
|
+
define_method(:require) do |name|
|
142
|
+
if (require_override = ::DutyFree::Util.instance_variable_get(:@_require_overrides)[name])
|
143
|
+
extension, folder_matcher, search_text, replacement_text, autoload_symbol = require_override
|
144
|
+
patched_filename = "/patched_#{name.tr('/', '_')}#{extension}"
|
145
|
+
if $LOADED_FEATURES.find { |f| f.end_with?(patched_filename) }
|
146
|
+
false
|
147
|
+
else
|
148
|
+
is_replaced = false
|
149
|
+
if (replacement_path = ::DutyFree::Util._write_patched(folder_matcher, name, extension, ::DutyFree::Util._custom_require_dir, patched_filename, search_text, replacement_text))
|
150
|
+
is_replaced = Kernel.send(:orig_require, replacement_path)
|
151
|
+
elsif replacement_path.nil?
|
152
|
+
puts "Couldn't find #{name} to require it!"
|
153
|
+
end
|
154
|
+
is_replaced
|
155
|
+
end
|
156
|
+
else
|
157
|
+
Kernel.send(:orig_require, name)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
require_overrides[module_filename] = [extension, folder_matcher, search_text, replacement_text, autoload_symbol]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def self._custom_require_dir
|
167
|
+
unless (custom_require_dir = ::DutyFree::Util.instance_variable_get(:@_custom_require_dir))
|
168
|
+
::DutyFree::Util.instance_variable_set(:@_custom_require_dir, (custom_require_dir = Dir.mktmpdir))
|
169
|
+
# So normal Ruby require will now pick this one up
|
170
|
+
$LOAD_PATH.unshift(custom_require_dir)
|
171
|
+
# When Ruby is exiting, remove this temporary directory
|
172
|
+
at_exit do
|
173
|
+
FileUtils.rm_rf(::DutyFree::Util.instance_variable_get(:@_custom_require_dir))
|
174
|
+
end
|
175
|
+
end
|
176
|
+
custom_require_dir
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns the full path to the replaced filename, or
|
180
|
+
# false if the file already exists, and nil if it was unable to write anything.
|
181
|
+
def self._write_patched(folder_matcher, name, extension, dir, patched_filename, search_text, replacement_text)
|
182
|
+
# See if our replacement file might already exist for some reason
|
183
|
+
name = +"/#{name}" unless name.start_with?('/')
|
184
|
+
name << extension unless name.end_with?(extension)
|
185
|
+
return false if File.exist?(replacement_path = "#{dir}#{patched_filename || name}")
|
186
|
+
|
187
|
+
# Dredge up the original .rb file, doctor it, and then require it instead
|
188
|
+
num_written = nil
|
189
|
+
orig_path = nil
|
190
|
+
orig_as = nil
|
191
|
+
# Using Ruby's approach to find files to require
|
192
|
+
$LOAD_PATH.each do |path|
|
193
|
+
orig_path = "#{path}#{name}"
|
194
|
+
break if path.include?(folder_matcher) && (orig_as = File.open(orig_path))
|
195
|
+
end
|
196
|
+
if (orig_text = orig_as&.read)
|
197
|
+
File.open(replacement_path, 'w') do |replacement|
|
198
|
+
num_written = replacement.write(orig_text.gsub(search_text, replacement_text))
|
199
|
+
end
|
200
|
+
orig_as.close
|
201
|
+
end
|
202
|
+
(num_written&.> 0) ? replacement_path : nil
|
203
|
+
end
|
72
204
|
end
|
73
205
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duty_free
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,20 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.0'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '6.
|
22
|
+
version: '6.2'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
29
|
+
version: '3.0'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '6.
|
32
|
+
version: '6.2'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: appraisal
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,14 +134,14 @@ dependencies:
|
|
134
134
|
requirements:
|
135
135
|
- - "~>"
|
136
136
|
- !ruby/object:Gem::Version
|
137
|
-
version: 0.
|
137
|
+
version: '0.93'
|
138
138
|
type: :development
|
139
139
|
prerelease: false
|
140
140
|
version_requirements: !ruby/object:Gem::Requirement
|
141
141
|
requirements:
|
142
142
|
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
|
-
version: 0.
|
144
|
+
version: '0.93'
|
145
145
|
- !ruby/object:Gem::Dependency
|
146
146
|
name: rubocop-rspec
|
147
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,20 +156,6 @@ dependencies:
|
|
156
156
|
- - "~>"
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: 1.42.0
|
159
|
-
- !ruby/object:Gem::Dependency
|
160
|
-
name: mysql2
|
161
|
-
requirement: !ruby/object:Gem::Requirement
|
162
|
-
requirements:
|
163
|
-
- - "~>"
|
164
|
-
- !ruby/object:Gem::Version
|
165
|
-
version: '0.5'
|
166
|
-
type: :development
|
167
|
-
prerelease: false
|
168
|
-
version_requirements: !ruby/object:Gem::Requirement
|
169
|
-
requirements:
|
170
|
-
- - "~>"
|
171
|
-
- !ruby/object:Gem::Version
|
172
|
-
version: '0.5'
|
173
159
|
- !ruby/object:Gem::Dependency
|
174
160
|
name: pg
|
175
161
|
requirement: !ruby/object:Gem::Requirement
|
@@ -204,10 +190,9 @@ dependencies:
|
|
204
190
|
- - "~>"
|
205
191
|
- !ruby/object:Gem::Version
|
206
192
|
version: '1.4'
|
207
|
-
description:
|
208
|
-
|
209
|
-
|
210
|
-
XLSX, ODT, HTML tables, or simple Ruby arrays.
|
193
|
+
description: 'Simplify data imports and exports with this slick ActiveRecord extension
|
194
|
+
|
195
|
+
'
|
211
196
|
email: lorint@gmail.com
|
212
197
|
executables: []
|
213
198
|
extensions: []
|
@@ -235,7 +220,7 @@ homepage: https://github.com/lorint/duty_free
|
|
235
220
|
licenses:
|
236
221
|
- MIT
|
237
222
|
metadata: {}
|
238
|
-
post_install_message:
|
223
|
+
post_install_message:
|
239
224
|
rdoc_options: []
|
240
225
|
require_paths:
|
241
226
|
- lib
|
@@ -243,15 +228,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
243
228
|
requirements:
|
244
229
|
- - ">="
|
245
230
|
- !ruby/object:Gem::Version
|
246
|
-
version: 2.
|
231
|
+
version: 2.3.5
|
247
232
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
248
233
|
requirements:
|
249
234
|
- - ">="
|
250
235
|
- !ruby/object:Gem::Version
|
251
236
|
version: 1.3.6
|
252
237
|
requirements: []
|
253
|
-
rubygems_version: 3.
|
254
|
-
signing_key:
|
238
|
+
rubygems_version: 3.2.3
|
239
|
+
signing_key:
|
255
240
|
specification_version: 4
|
256
241
|
summary: Import and Export Data
|
257
242
|
test_files: []
|