duty_free 1.0.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/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
|