duty_free 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/duty_free.rb +86 -0
- data/lib/duty_free/column.rb +56 -0
- data/lib/duty_free/config.rb +32 -0
- data/lib/duty_free/extensions.rb +525 -0
- data/lib/duty_free/frameworks/cucumber.rb +28 -0
- data/lib/duty_free/frameworks/rails.rb +4 -0
- data/lib/duty_free/frameworks/rails/controller.rb +41 -0
- data/lib/duty_free/frameworks/rails/engine.rb +14 -0
- data/lib/duty_free/frameworks/rspec.rb +18 -0
- data/lib/duty_free/serializers/json.rb +36 -0
- data/lib/duty_free/serializers/yaml.rb +26 -0
- data/lib/duty_free/suggest_template.rb +315 -0
- data/lib/duty_free/util.rb +73 -0
- data/lib/duty_free/version_number.rb +19 -0
- data/lib/generators/duty_free/USAGE +2 -0
- data/lib/generators/duty_free/install_generator.rb +98 -0
- data/lib/generators/duty_free/templates/add_object_changes_to_versions.rb.erb +12 -0
- data/lib/generators/duty_free/templates/create_versions.rb.erb +36 -0
- metadata +257 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# before hook for Cucumber
|
4
|
+
Before do
|
5
|
+
DutyFree.enabled = false
|
6
|
+
DutyFree.request.enabled = true
|
7
|
+
DutyFree.request.whodunnit = nil
|
8
|
+
DutyFree.request.controller_info = {} if defined?(::Rails)
|
9
|
+
end
|
10
|
+
|
11
|
+
module DutyFree
|
12
|
+
module Cucumber
|
13
|
+
# Helper method for enabling DutyFree in Cucumber features.
|
14
|
+
module Extensions
|
15
|
+
def with_df_importing
|
16
|
+
was_enabled = ::DutyFree.enabled?
|
17
|
+
::DutyFree.enabled = true
|
18
|
+
begin
|
19
|
+
yield
|
20
|
+
ensure
|
21
|
+
::DutyFree.enabled = was_enabled
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
World DutyFree::Cucumber::Extensions
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DutyFree
|
4
|
+
module Rails
|
5
|
+
# Extensions to rails controllers. Provides convenient ways to pass certain
|
6
|
+
# information to the model layer, with `controller_info` and `whodunnit`.
|
7
|
+
# Also includes a convenient on/off switch,
|
8
|
+
# `duty_free_enabled_for_controller`.
|
9
|
+
module Controller
|
10
|
+
def self.included(controller)
|
11
|
+
controller.before_action(
|
12
|
+
:set_duty_free_enabled_for_controller,
|
13
|
+
:set_duty_free_controller_info
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Returns the user who is responsible for any changes that occur.
|
20
|
+
# By default this calls `current_user` and returns the result.
|
21
|
+
#
|
22
|
+
# Override this method in your controller to call a different
|
23
|
+
# method, e.g. `current_person`, or anything you like.
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def user_for_duty_free
|
27
|
+
return unless defined?(current_user)
|
28
|
+
|
29
|
+
ActiveSupport::VERSION::MAJOR >= 4 ? current_user.try!(:id) : current_user.try(:id)
|
30
|
+
rescue NoMethodError
|
31
|
+
current_user
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if defined?(::ActionController)
|
38
|
+
::ActiveSupport.on_load(:action_controller) do
|
39
|
+
include ::DutyFree::Rails::Controller
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DutyFree
|
4
|
+
module Rails
|
5
|
+
# See http://guides.rubyonrails.org/engines.html
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
# paths['app/models'] << 'lib/duty_free/frameworks/active_record/models'
|
8
|
+
config.duty_free = ActiveSupport::OrderedOptions.new
|
9
|
+
initializer 'duty_free.initialisation' do |app|
|
10
|
+
DutyFree.enabled = app.config.duty_free.fetch(:enabled, true)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/matchers'
|
5
|
+
# require "duty_free/frameworks/rspec/helpers"
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
# config.include ::DutyFree::RSpec::Helpers::InstanceMethods
|
9
|
+
# config.extend ::DutyFree::RSpec::Helpers::ClassMethods
|
10
|
+
|
11
|
+
# config.before(:each) do
|
12
|
+
# ::DutyFree.enabled = false
|
13
|
+
# end
|
14
|
+
|
15
|
+
# config.before(:each, df_importing: true) do
|
16
|
+
# ::DutyFree.enabled = true
|
17
|
+
# end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DutyFree
|
4
|
+
module Serializers
|
5
|
+
# An alternate serializer for, e.g. `versions.object`.
|
6
|
+
module JSON
|
7
|
+
extend self # makes all instance methods become module methods as well
|
8
|
+
|
9
|
+
def load(string)
|
10
|
+
ActiveSupport::JSON.decode string
|
11
|
+
end
|
12
|
+
|
13
|
+
def dump(object)
|
14
|
+
ActiveSupport::JSON.encode object
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a SQL LIKE condition to be used to match the given field and
|
18
|
+
# value in the serialized object.
|
19
|
+
def where_object_condition(arel_field, field, value)
|
20
|
+
# Convert to JSON to handle strings and nulls correctly.
|
21
|
+
json_value = value.to_json
|
22
|
+
|
23
|
+
# If the value is a number, we need to ensure that we find the next
|
24
|
+
# character too, which is either `,` or `}`, to ensure that searching
|
25
|
+
# for the value 12 doesn't yield false positives when the value is
|
26
|
+
# 123.
|
27
|
+
if value.is_a? Numeric
|
28
|
+
arel_field.matches("%\"#{field}\":#{json_value},%")
|
29
|
+
.or(arel_field.matches("%\"#{field}\":#{json_value}}%"))
|
30
|
+
else
|
31
|
+
arel_field.matches("%\"#{field}\":#{json_value}%")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module DutyFree
|
6
|
+
module Serializers
|
7
|
+
# The default serializer for, e.g. `versions.object`.
|
8
|
+
module YAML
|
9
|
+
extend self # makes all instance methods become module methods as well
|
10
|
+
|
11
|
+
def load(string)
|
12
|
+
::YAML.safe_load string
|
13
|
+
end
|
14
|
+
|
15
|
+
def dump(object)
|
16
|
+
::YAML.dump object
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a SQL LIKE condition to be used to match the given field and
|
20
|
+
# value in the serialized object.
|
21
|
+
def where_object_condition(arel_field, field, value)
|
22
|
+
arel_field.matches("%\n#{field}: #{value}\n%")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,315 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :nodoc:
|
4
|
+
module DutyFree
|
5
|
+
module SuggestTemplate
|
6
|
+
module ClassMethods
|
7
|
+
# Helpful suggestions to get started creating a template
|
8
|
+
# Pass in -1 for hops if you want to traverse all possible links
|
9
|
+
def suggest_template(hops = 0, do_has_many = false, show_output = true, this_klass = self)
|
10
|
+
::DutyFree.instance_variable_set(:@errored_assocs, [])
|
11
|
+
::DutyFree.instance_variable_set(:@errored_columns, [])
|
12
|
+
uniques, _required = ::DutyFree::SuggestTemplate._suggest_unique_column(this_klass, nil, '')
|
13
|
+
template, required = ::DutyFree::SuggestTemplate._suggest_template(hops, do_has_many, this_klass)
|
14
|
+
template = {
|
15
|
+
uniques: uniques,
|
16
|
+
required: required.map(&:to_sym),
|
17
|
+
all: template,
|
18
|
+
as: {}
|
19
|
+
}
|
20
|
+
# puts "Errors: #{::DutyFree.instance_variable_get(:@errored_assocs).inspect}"
|
21
|
+
|
22
|
+
if show_output
|
23
|
+
path = this_klass.name.split('::').map(&:underscore).join('/')
|
24
|
+
puts "\n# Place the following into app/models/#{path}.rb:"
|
25
|
+
arguments = method(__method__).parameters[0..2].map { |_, name| binding.local_variable_get(name).to_s }
|
26
|
+
puts "# Generated by: #{this_klass.name}.suggest_template(#{arguments.join(', ')})"
|
27
|
+
::DutyFree::SuggestTemplate._template_pretty_print(template)
|
28
|
+
puts '# ------------------------------------------'
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
template
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self._suggest_template(hops, do_has_many, this_klass, poison_links = [], path = '')
|
36
|
+
errored_assocs = ::DutyFree.instance_variable_get(:@errored_assocs)
|
37
|
+
this_primary_key = Array(this_klass.primary_key)
|
38
|
+
# Find all associations, and track all belongs_tos
|
39
|
+
this_belongs_tos = []
|
40
|
+
assocs = {}
|
41
|
+
this_klass.reflect_on_all_associations.each do |assoc|
|
42
|
+
# PolymorphicReflection AggregateReflection RuntimeReflection
|
43
|
+
is_belongs_to = assoc.is_a?(ActiveRecord::Reflection::BelongsToReflection)
|
44
|
+
# Figure out if it's belongs_to, has_many, or has_one
|
45
|
+
belongs_to_or_has_many =
|
46
|
+
if is_belongs_to
|
47
|
+
'belongs_to'
|
48
|
+
elsif (is_habtm = assoc.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection))
|
49
|
+
'has_and_belongs_to_many'
|
50
|
+
else
|
51
|
+
(assoc.is_a?(ActiveRecord::Reflection::HasManyReflection) ? 'has_many' : 'has_one')
|
52
|
+
end
|
53
|
+
# Always process belongs_to, and also process has_one and has_many if do_has_many is chosen.
|
54
|
+
# Skip any HMT or HABTM. (Maybe break out HABTM into a combo HM and BT in the future.)
|
55
|
+
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
|
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
|
63
|
+
end
|
64
|
+
if (is_through = assoc.is_a?(ActiveRecord::Reflection::ThroughReflection)) && assoc.options.include?(:as)
|
65
|
+
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
|
+
end
|
67
|
+
next if is_through || is_habtm || (!is_belongs_to && !do_has_many) || errored_assocs.include?(assoc)
|
68
|
+
|
69
|
+
if is_belongs_to && assoc.polymorphic? # Polymorphic belongs_to?
|
70
|
+
# Load all models
|
71
|
+
# %%% 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
|
+
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
73
|
+
# Find all current possible polymorphic relations
|
74
|
+
ActiveRecord::Base.descendants.each do |model|
|
75
|
+
# Skip auto-generated HABTM_DestinationModel models
|
76
|
+
next if model.respond_to?(:table_name_resolver) &&
|
77
|
+
model.name.start_with?('HABTM_') &&
|
78
|
+
model.table_name_resolver.is_a?(
|
79
|
+
ActiveRecord::Associations::Builder::HasAndBelongsToMany::JoinTableResolver::KnownClass
|
80
|
+
)
|
81
|
+
|
82
|
+
# Find applicable polymorphic has_many associations from each real model
|
83
|
+
model.reflect_on_all_associations.each do |poly_assoc|
|
84
|
+
next unless poly_assoc.is_a?(ActiveRecord::Reflection::HasManyReflection) &&
|
85
|
+
poly_assoc.inverse_of == assoc
|
86
|
+
|
87
|
+
this_belongs_tos += (fkeys = [poly_assoc.type, poly_assoc.foreign_key])
|
88
|
+
assocs["#{assoc.name}_#{poly_assoc.active_record.name.underscore}".to_sym] = [[fkeys, assoc.active_record], poly_assoc.active_record]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
else
|
92
|
+
# Is it a polymorphic has_many, which is defined using as: :somethingable ?
|
93
|
+
is_polymorphic_hm = assoc.inverse_of&.polymorphic?
|
94
|
+
begin
|
95
|
+
# Standard has_one, or has_many, and belongs_to uses assoc.klass.
|
96
|
+
# Also polymorphic belongs_to uses assoc.klass.
|
97
|
+
assoc_klass = is_polymorphic_hm ? assoc.inverse_of.active_record : assoc.klass
|
98
|
+
rescue NameError # For models which cannot be found by name
|
99
|
+
end
|
100
|
+
new_assoc =
|
101
|
+
if assoc_klass.nil?
|
102
|
+
puts "* In the #{this_klass.name} model there's a problem with: \"#{belongs_to_or_has_many} :#{assoc.name}\" because there is no \"#{assoc.class_name}\" model."
|
103
|
+
nil # Cause this one to be excluded
|
104
|
+
elsif is_belongs_to
|
105
|
+
this_belongs_tos << (fk = assoc.foreign_key.to_s)
|
106
|
+
[[[fk], assoc.active_record], assoc_klass]
|
107
|
+
else # has_many or has_one
|
108
|
+
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
|
+
missing_key_columns = inverse_foreign_keys - assoc_klass.columns.map(&:name)
|
111
|
+
if missing_key_columns.empty?
|
112
|
+
# puts "Has columns #{inverse_foreign_keys.inspect}"
|
113
|
+
[[inverse_foreign_keys, assoc_klass], assoc_klass]
|
114
|
+
else
|
115
|
+
if inverse_foreign_keys.length > 1
|
116
|
+
puts "* The #{assoc_klass.name} model is missing #{missing_key_columns.join(' and ')} columns to allow it to support polymorphic inheritance."
|
117
|
+
else
|
118
|
+
print "* In the #{this_klass.name} model there's a problem with: \"#{belongs_to_or_has_many} :#{assoc.name}\"."
|
119
|
+
|
120
|
+
if (inverses = _find_belongs_tos(assoc_klass, this_klass, errored_assocs)).empty?
|
121
|
+
if inverse_foreign_keys.first.nil?
|
122
|
+
puts " Consider adding \"foreign_key: :#{this_klass.name.underscore}_id\" regarding some column in #{assoc_klass.name} to this #{belongs_to_or_has_many} entry."
|
123
|
+
else
|
124
|
+
puts " (Cannot find foreign key \"#{inverse_foreign_keys.first.inspect}\" in #{assoc_klass.name}.)"
|
125
|
+
end
|
126
|
+
else
|
127
|
+
puts " Consider adding \"#{inverses.map { |x| "inverse_of: :#{x.name}" }.join(' or ')}\" to this entry."
|
128
|
+
end
|
129
|
+
end
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
if new_assoc.nil?
|
134
|
+
errored_assocs << assoc
|
135
|
+
else
|
136
|
+
assocs[assoc.name] = new_assoc
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Include all columns except for the primary key, any foreign keys, and excluded_columns
|
142
|
+
# %%% add EXCLUDED_ALL_COLUMNS || ...
|
143
|
+
excluded_columns = %w[created_at updated_at deleted_at]
|
144
|
+
template = (this_klass.columns.map(&:name) - this_primary_key - this_belongs_tos - excluded_columns)
|
145
|
+
template.map!(&:to_sym)
|
146
|
+
requireds = _find_requireds(this_klass).map { |r| "#{path}#{r}".to_sym }
|
147
|
+
# Now add the foreign keys and any has_manys in the form of references to associated models
|
148
|
+
assocs.each do |k, assoc|
|
149
|
+
# assoc.first describes this foreign key and class, and is used for a "reverse poison"
|
150
|
+
# detection so we don't fold back on ourselves
|
151
|
+
next if poison_links.include?(assoc.first)
|
152
|
+
|
153
|
+
is_has_many = (assoc.first.last == assoc.last)
|
154
|
+
# puts "#{k} #{hops}"
|
155
|
+
unique, new_requireds =
|
156
|
+
if hops.zero?
|
157
|
+
# For has_one or has_many, exclude with priority the foreign key column(s) we rode in here on
|
158
|
+
priority_excluded_columns = assoc.first.first if is_has_many
|
159
|
+
# puts "Excluded: #{priority_excluded_columns.inspect}"
|
160
|
+
_suggest_unique_column(assoc.last, priority_excluded_columns, "#{path}#{k}_")
|
161
|
+
else
|
162
|
+
new_poison_links =
|
163
|
+
if is_has_many
|
164
|
+
# has_many is simple, just exclude how we got here from the foreign table
|
165
|
+
[assoc.first]
|
166
|
+
else
|
167
|
+
# belongs_to is more involved since there may be multiple foreign keys which point
|
168
|
+
# from the foreign table to this primary one, so exclude all these links.
|
169
|
+
_find_belongs_tos(assoc.first.last, assoc.last, errored_assocs).map do |f_assoc|
|
170
|
+
[f_assoc.foreign_key.to_s, f_assoc.active_record]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
# puts "New Poison: #{new_poison_links.inspect}"
|
174
|
+
_suggest_template(hops - 1, do_has_many, assoc.last, poison_links + new_poison_links, "#{path}#{k}_")
|
175
|
+
end
|
176
|
+
template << { k => unique }
|
177
|
+
requireds += new_requireds
|
178
|
+
end
|
179
|
+
[template, requireds]
|
180
|
+
end
|
181
|
+
|
182
|
+
# Find belongs_tos for this model to one more more other klasses
|
183
|
+
def self._find_belongs_tos(klass, to_klass, errored_assocs)
|
184
|
+
klass.reflect_on_all_associations.each_with_object([]) do |bt_assoc, s|
|
185
|
+
next unless bt_assoc.is_a?(ActiveRecord::Reflection::BelongsToReflection) && !errored_assocs.include?(bt_assoc)
|
186
|
+
|
187
|
+
begin
|
188
|
+
s << bt_assoc if !bt_assoc.polymorphic? && bt_assoc.klass == to_klass
|
189
|
+
rescue NameError
|
190
|
+
errored_assocs << bt_assoc
|
191
|
+
puts "* In the #{bt_assoc.active_record.name} model \"belongs_to :#{bt_assoc.name}\" could not find a model named #{bt_assoc.class_name}."
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def self._suggest_unique_column(klass, priority_excluded_columns, path)
|
197
|
+
# %%% Try to find out if this klass already has an import template, and if so then
|
198
|
+
# bring in its first unique column set as a suggestion
|
199
|
+
# ...
|
200
|
+
# Not available, so grasping at straws, just search for any available column
|
201
|
+
# %%% add EXCLUDED_UNIQUE_COLUMNS || ...
|
202
|
+
klass_columns = klass.columns
|
203
|
+
|
204
|
+
# Requireds takes its cues from all attributes having a presence validator
|
205
|
+
requireds = _find_requireds(klass)
|
206
|
+
if priority_excluded_columns
|
207
|
+
klass_columns = klass_columns.reject { |col| priority_excluded_columns.include?(col.name) }
|
208
|
+
end
|
209
|
+
excluded_columns = %w[created_at updated_at deleted_at]
|
210
|
+
unique = [(
|
211
|
+
# Find the first text field of a required if one exists
|
212
|
+
klass_columns.find { |col| requireds.include?(col.name) && col.type == :string }&.name ||
|
213
|
+
# Find the first text field, now of a non-required, if one exists
|
214
|
+
klass_columns.find { |col| col.type == :string }&.name ||
|
215
|
+
# If no string then look for the first non-PK that is also not a foreign key or created_at or updated_at
|
216
|
+
klass_columns.find do |col|
|
217
|
+
requireds.include?(col.name) && col.name != klass.primary_key && !excluded_columns.include?(col.name)
|
218
|
+
end&.name ||
|
219
|
+
# And now the same but not a required, the first non-PK that is also not a foreign key or created_at or updated_at
|
220
|
+
klass_columns.find do |col|
|
221
|
+
col.name != klass.primary_key && !excluded_columns.include?(col.name)
|
222
|
+
end&.name ||
|
223
|
+
# Finally just accept the PK if nothing else
|
224
|
+
klass.primary_key
|
225
|
+
).to_sym]
|
226
|
+
|
227
|
+
[unique, requireds.map { |r| "#{path}#{r}".to_sym }]
|
228
|
+
end
|
229
|
+
|
230
|
+
def self._find_requireds(klass)
|
231
|
+
errored_columns = ::DutyFree.instance_variable_get(:@errored_columns)
|
232
|
+
klass.validators.select do |v|
|
233
|
+
v.is_a?(ActiveRecord::Validations::PresenceValidator)
|
234
|
+
end.each_with_object([]) do |v, s|
|
235
|
+
v.attributes.each do |a|
|
236
|
+
attrib = a.to_s
|
237
|
+
klass_col = [klass, attrib]
|
238
|
+
next if errored_columns.include?(klass_col)
|
239
|
+
|
240
|
+
if klass.columns.map(&:name).include?(attrib)
|
241
|
+
s << attrib
|
242
|
+
else
|
243
|
+
puts "* In the #{klass.name} model \"validates_presence_of :#{attrib}\" should be removed as it does not refer to any existing column."
|
244
|
+
errored_columns << klass_col
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Show a "pretty" version of IMPORT_COLUMNS, to be placed in a model
|
251
|
+
def self._template_pretty_print(template, indent = 0, child_count = 0, is_hash_in_hash = false)
|
252
|
+
unless indent.negative?
|
253
|
+
if indent.zero?
|
254
|
+
print 'IMPORT_COLUMNS = '
|
255
|
+
else
|
256
|
+
puts unless is_hash_in_hash
|
257
|
+
end
|
258
|
+
print "#{' ' * indent unless is_hash_in_hash}{"
|
259
|
+
if indent.zero?
|
260
|
+
indent = 2
|
261
|
+
print "\n#{' ' * indent}"
|
262
|
+
else
|
263
|
+
print ' ' unless is_hash_in_hash
|
264
|
+
end
|
265
|
+
end
|
266
|
+
is_first = true
|
267
|
+
template.each do |k, v|
|
268
|
+
# Skip past this when doing a child count
|
269
|
+
child_count = _template_pretty_print(v, -10_000) if indent >= 0
|
270
|
+
if is_first
|
271
|
+
is_first = false
|
272
|
+
elsif indent == 2 || (indent >= 0 && child_count > 5)
|
273
|
+
print ",\n#{' ' * indent}" # Comma, newline, and indentation
|
274
|
+
end
|
275
|
+
if indent.negative?
|
276
|
+
child_count += 1
|
277
|
+
else
|
278
|
+
# Fairly good to troubleshoot child_count things with: "#{k}#{child_count}: "
|
279
|
+
print "#{k}: "
|
280
|
+
end
|
281
|
+
if v.is_a?(Array)
|
282
|
+
print '[' unless indent.negative?
|
283
|
+
v.each_with_index do |item, idx|
|
284
|
+
# This is where most of the commas get printed, so you can do "#{child_count}," to diagnose things
|
285
|
+
print ',' if idx.positive? && indent >= 0
|
286
|
+
if item.is_a?(Hash)
|
287
|
+
# puts '^' unless child_count < 5 || indent.negative?
|
288
|
+
child_count = _template_pretty_print(item, indent + 2, child_count)
|
289
|
+
elsif item.is_a?(Symbol)
|
290
|
+
if indent.negative?
|
291
|
+
child_count += 1
|
292
|
+
else
|
293
|
+
print ' ' if idx.positive?
|
294
|
+
print item.inspect
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
print ']' unless indent.negative?
|
299
|
+
elsif v.is_a?(Hash) # A hash in a hash
|
300
|
+
child_count = _template_pretty_print(v, indent + 2, child_count, true)
|
301
|
+
elsif v.nil?
|
302
|
+
puts 'nil' unless indent.negative?
|
303
|
+
end
|
304
|
+
end
|
305
|
+
if indent == 2
|
306
|
+
puts
|
307
|
+
indent = 0
|
308
|
+
puts '}'
|
309
|
+
elsif indent >= 0
|
310
|
+
print "#{' ' unless child_count.zero?}}"
|
311
|
+
end
|
312
|
+
child_count
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|