barkest_core 1.5.3.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 +13 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +254 -0
- data/MIT-LICENSE +20 -0
- data/README.md +364 -0
- data/Rakefile +37 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-Bold.ttf +0 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-BoldItalic.ttf +0 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-Italic.ttf +0 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-Regular.ttf +0 -0
- data/app/assets/images/barkest_core/.keep +0 -0
- data/app/assets/images/barkest_core/barcode-B.svg +181 -0
- data/app/assets/javascripts/barkest_core/.keep +0 -0
- data/app/assets/javascripts/barkest_core/application.js +22 -0
- data/app/assets/javascripts/barkest_core/bootstrap-datepicker.js +1800 -0
- data/app/assets/javascripts/barkest_core/field_init.js +7 -0
- data/app/assets/javascripts/barkest_core/jquery.doubleScroll.js +112 -0
- data/app/assets/javascripts/barkest_core/masked_edit.js +25 -0
- data/app/assets/javascripts/barkest_core/system_status.js.erb +201 -0
- data/app/assets/stylesheets/barkest_core/.keep +0 -0
- data/app/assets/stylesheets/barkest_core/application.css +17 -0
- data/app/assets/stylesheets/barkest_core/custom.css.scss +264 -0
- data/app/assets/stylesheets/barkest_core/datepicker3.css +790 -0
- data/app/controllers/.keep +0 -0
- data/app/controllers/access_groups_controller.rb +74 -0
- data/app/controllers/account_activations_controller.rb +29 -0
- data/app/controllers/application_controller.rb +5 -0
- data/app/controllers/barkest_core/application_controller_base.rb +113 -0
- data/app/controllers/barkest_core/engine_controller_base.rb +15 -0
- data/app/controllers/barkest_core/testsub_controller.rb +21 -0
- data/app/controllers/contact_controller.rb +32 -0
- data/app/controllers/log_view_controller.rb +31 -0
- data/app/controllers/password_resets_controller.rb +126 -0
- data/app/controllers/sessions_controller.rb +64 -0
- data/app/controllers/status_controller.rb +150 -0
- data/app/controllers/system_config_controller.rb +238 -0
- data/app/controllers/system_update_controller.rb +164 -0
- data/app/controllers/test_access_controller.rb +44 -0
- data/app/controllers/test_report_controller.rb +75 -0
- data/app/controllers/users_controller.rb +218 -0
- data/app/helpers/.keep +0 -0
- data/app/helpers/barkest_core/application_helper.rb +134 -0
- data/app/helpers/barkest_core/form_helper.rb +469 -0
- data/app/helpers/barkest_core/html_helper.rb +70 -0
- data/app/helpers/barkest_core/misc_helper.rb +68 -0
- data/app/helpers/barkest_core/pdf_helper.rb +180 -0
- data/app/helpers/barkest_core/recaptcha_helper.rb +115 -0
- data/app/helpers/barkest_core/sessions_helper.rb +94 -0
- data/app/helpers/barkest_core/status_helper.rb +118 -0
- data/app/helpers/barkest_core/users_helper.rb +32 -0
- data/app/mailers/.keep +0 -0
- data/app/mailers/application_mailer.rb +5 -0
- data/app/mailers/barkest_core/application_mailer_base.rb +30 -0
- data/app/mailers/barkest_core/contact_form.rb +20 -0
- data/app/mailers/barkest_core/user_mailer.rb +44 -0
- data/app/models/.keep +0 -0
- data/app/models/access_group.rb +121 -0
- data/app/models/access_group_group_member.rb +13 -0
- data/app/models/access_group_user_member.rb +11 -0
- data/app/models/barkest_core/auth_config.rb +95 -0
- data/app/models/barkest_core/authorize_failure.rb +7 -0
- data/app/models/barkest_core/contact_message.rb +37 -0
- data/app/models/barkest_core/database_config.rb +223 -0
- data/app/models/barkest_core/db_table.rb +21 -0
- data/app/models/barkest_core/email_config.rb +132 -0
- data/app/models/barkest_core/global_status.rb +267 -0
- data/app/models/barkest_core/log_entry.rb +101 -0
- data/app/models/barkest_core/log_view_options.rb +51 -0
- data/app/models/barkest_core/ms_sql_db_definition.rb +441 -0
- data/app/models/barkest_core/ms_sql_definition.rb +221 -0
- data/app/models/barkest_core/ms_sql_function.rb +423 -0
- data/app/models/barkest_core/not_logged_in.rb +7 -0
- data/app/models/barkest_core/pdf_table_builder.rb +407 -0
- data/app/models/barkest_core/self_update_config.rb +37 -0
- data/app/models/barkest_core/user_alert.rb +29 -0
- data/app/models/barkest_core/user_alert_generators.rb +58 -0
- data/app/models/barkest_core/user_manager.rb +404 -0
- data/app/models/barkest_core/work_path.rb +74 -0
- data/app/models/disable_user.rb +18 -0
- data/app/models/ldap_access_group.rb +15 -0
- data/app/models/system_config.rb +99 -0
- data/app/models/user.rb +405 -0
- data/app/models/user_login_history.rb +11 -0
- data/app/views/.keep +0 -0
- data/app/views/access_groups/_form.html.erb +19 -0
- data/app/views/access_groups/edit.html.erb +2 -0
- data/app/views/access_groups/index.html.erb +32 -0
- data/app/views/access_groups/new.html.erb +2 -0
- data/app/views/access_groups/show.html.erb +4 -0
- data/app/views/barkest_core/contact_form/contact.html.erb +16 -0
- data/app/views/barkest_core/contact_form/contact.text.erb +13 -0
- data/app/views/barkest_core/testsub/_links.html.erb +5 -0
- data/app/views/barkest_core/testsub/page1.html.erb +3 -0
- data/app/views/barkest_core/testsub/page2.html.erb +2 -0
- data/app/views/barkest_core/testsub/page3.html.erb +2 -0
- data/app/views/barkest_core/user_mailer/account_activation.html.erb +7 -0
- data/app/views/barkest_core/user_mailer/account_activation.text.erb +6 -0
- data/app/views/barkest_core/user_mailer/invalid_password_reset.html.erb +3 -0
- data/app/views/barkest_core/user_mailer/invalid_password_reset.text.erb +5 -0
- data/app/views/barkest_core/user_mailer/password_reset.html.erb +8 -0
- data/app/views/barkest_core/user_mailer/password_reset.text.erb +7 -0
- data/app/views/contact/index.html.erb +24 -0
- data/app/views/layouts/_footer_copyright.html.erb +1 -0
- data/app/views/layouts/_menu_admin.html.erb +5 -0
- data/app/views/layouts/_menu_anon.html.erb +0 -0
- data/app/views/layouts/_menu_auth.html.erb +3 -0
- data/app/views/layouts/_menu_footer.html.erb +1 -0
- data/app/views/layouts/_nav_logo.html.erb +1 -0
- data/app/views/layouts/application.html.erb +2 -0
- data/app/views/layouts/barkest_core/_application.html.erb +24 -0
- data/app/views/layouts/barkest_core/_footer.html.erb +18 -0
- data/app/views/layouts/barkest_core/_header.html.erb +38 -0
- data/app/views/layouts/barkest_core/_html_mailer.html.erb +11 -0
- data/app/views/layouts/barkest_core/_menu_account.html.erb +14 -0
- data/app/views/layouts/barkest_core/_menu_sample.html.erb +1 -0
- data/app/views/layouts/barkest_core/_messages.html.erb +4 -0
- data/app/views/layouts/barkest_core/_shim.html.erb +4 -0
- data/app/views/layouts/barkest_core/_subheader.html.erb +1 -0
- data/app/views/layouts/barkest_core/_text_mailer.text.erb +4 -0
- data/app/views/layouts/mailer.html.erb +1 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/log_view/index.html.erb +100 -0
- data/app/views/password_resets/edit.html.erb +20 -0
- data/app/views/password_resets/new.html.erb +14 -0
- data/app/views/sessions/new.html.erb +27 -0
- data/app/views/shared/_error_messages.html.erb +29 -0
- data/app/views/shared/_generic_user_alert.html.erb +4 -0
- data/app/views/status/current.html.erb +34 -0
- data/app/views/status/test.html.erb +50 -0
- data/app/views/system_config/index.html.erb +25 -0
- data/app/views/system_config/show_auth.html.erb +28 -0
- data/app/views/system_config/show_database.html.erb +36 -0
- data/app/views/system_config/show_email.html.erb +21 -0
- data/app/views/system_config/show_self_update.html.erb +13 -0
- data/app/views/system_update/index.html.erb +31 -0
- data/app/views/system_update/new.html.erb +2 -0
- data/app/views/test_access/allow_anon.html.erb +2 -0
- data/app/views/test_access/require_admin.html.erb +2 -0
- data/app/views/test_access/require_group_x.html.erb +2 -0
- data/app/views/test_access/require_user.html.erb +2 -0
- data/app/views/test_report/index.csv.csvrb +23 -0
- data/app/views/test_report/index.html.erb +6 -0
- data/app/views/test_report/index.pdf.prawn +50 -0
- data/app/views/test_report/index.xlsx.axlsx +28 -0
- data/app/views/users/_user.html.erb +57 -0
- data/app/views/users/_user_details.html.erb +15 -0
- data/app/views/users/_user_details_for_list.html.erb +1 -0
- data/app/views/users/_user_form.html.erb +13 -0
- data/app/views/users/disable_confirm.html.erb +19 -0
- data/app/views/users/edit.html.erb +15 -0
- data/app/views/users/index.html.erb +9 -0
- data/app/views/users/new.html.erb +10 -0
- data/app/views/users/show.html.erb +46 -0
- data/bin/rails +12 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20160617172539_create_access_groups.rb +10 -0
- data/db/migrate/20160617172725_create_users.rb +26 -0
- data/db/migrate/20160617172833_create_user_login_histories.rb +12 -0
- data/db/migrate/20160622151720_create_access_group_user_members.rb +9 -0
- data/db/migrate/20160622151925_create_access_group_group_members.rb +9 -0
- data/db/migrate/20160701005706_create_ldap_access_groups.rb +11 -0
- data/db/migrate/20161108155029_create_system_configs.rb +11 -0
- data/db/seeds/barkest_core_01_create_users.rb +42 -0
- data/db/seeds.rb +53 -0
- data/lib/barkest_core/concerns/association_with_defaults.rb +55 -0
- data/lib/barkest_core/concerns/boolean_parser.rb +88 -0
- data/lib/barkest_core/concerns/date_parser.rb +181 -0
- data/lib/barkest_core/concerns/email_tester.rb +55 -0
- data/lib/barkest_core/concerns/encrypted_fields.rb +156 -0
- data/lib/barkest_core/concerns/named_model.rb +73 -0
- data/lib/barkest_core/concerns/number_parser.rb +145 -0
- data/lib/barkest_core/concerns/utc_conversion.rb +60 -0
- data/lib/barkest_core/engine.rb +105 -0
- data/lib/barkest_core/extensions/active_record_extensions.rb +120 -0
- data/lib/barkest_core/extensions/application_configuration_extensions.rb +38 -0
- data/lib/barkest_core/extensions/application_extensions.rb +50 -0
- data/lib/barkest_core/extensions/axlsx_extenstions.rb +157 -0
- data/lib/barkest_core/extensions/fixture_set_extensions.rb +107 -0
- data/lib/barkest_core/extensions/generator_extensions.rb +271 -0
- data/lib/barkest_core/extensions/main_app_extensions.rb +35 -0
- data/lib/barkest_core/extensions/prawn_document_extensions.rb +367 -0
- data/lib/barkest_core/extensions/prawn_table_extensions.rb +131 -0
- data/lib/barkest_core/extensions/router_extensions.rb +106 -0
- data/lib/barkest_core/extensions/simple_formatter_extensions.rb +66 -0
- data/lib/barkest_core/extensions/test_case_extensions.rb +348 -0
- data/lib/barkest_core/extensions/time_extensions.rb +164 -0
- data/lib/barkest_core/handlers/csv_handler.rb +30 -0
- data/lib/barkest_core/version.rb +3 -0
- data/lib/barkest_core.rb +324 -0
- data/lib/generators/barkest/install_generator.rb +102 -0
- data/lib/generators/barkest_core/actions/01_patch_application_controller.rb +55 -0
- data/lib/generators/barkest_core/actions/02_patch_application_mailer.rb +56 -0
- data/lib/generators/barkest_core/actions/03_patch_assets.rb +62 -0
- data/lib/generators/barkest_core/actions/04_patch_layouts.rb +36 -0
- data/lib/generators/barkest_core/actions/05_patch_routes.rb +93 -0
- data/lib/generators/barkest_core/actions/06_patch_seeds.rb +60 -0
- data/lib/generators/barkest_core/actions/07_copy_migrations.rb +51 -0
- data/lib/generators/barkest_core/actions/08_configure_database.rb +52 -0
- data/lib/generators/barkest_core/actions/09_configure_secrets.rb +29 -0
- data/lib/generators/barkest_core/actions/99_patch_gitignore.rb +57 -0
- data/lib/generators/barkest_core/install_generator.rb +17 -0
- data/test/barkest_core_test.rb +83 -0
- data/test/controllers/access_groups_controller_test.rb +53 -0
- data/test/controllers/contact_controller_test.rb +10 -0
- data/test/controllers/sessions_controller_test.rb +10 -0
- data/test/controllers/users_controller_test.rb +10 -0
- data/test/dummy/.gitignore +10 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +14 -0
- data/test/dummy/app/assets/stylesheets/application.css +16 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/mailers/application_mailer.rb +3 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +1 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +1 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/app/views/system_config/show_fake.html.erb +3 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +27 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +47 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +44 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/db_updater_ext.rb +33 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/sys_config_ext.rb +12 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +60 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/schema.rb +95 -0
- data/test/dummy/db/seeds/barkest_core_01_create_users.rb +42 -0
- data/test/dummy/db/seeds.rb +51 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/sql/my_test_view.sql +3 -0
- data/test/fixtures/access_groups.yml +21 -0
- data/test/fixtures/users.yml +71 -0
- data/test/helpers/barkest_core/sessions_helper_test.rb +22 -0
- data/test/integration/access_group_mgmt_test.rb +33 -0
- data/test/integration/access_test.rb +24 -0
- data/test/integration/account_activations_access_test.rb +12 -0
- data/test/integration/contact_test.rb +98 -0
- data/test/integration/extra_partial_test.rb +41 -0
- data/test/integration/log_view_access_test.rb +12 -0
- data/test/integration/password_resets_test.rb +101 -0
- data/test/integration/reports_test.rb +53 -0
- data/test/integration/status_access_test.rb +27 -0
- data/test/integration/system_config_access_test.rb +24 -0
- data/test/integration/system_update_access_test.rb +19 -0
- data/test/integration/users_access_test.rb +34 -0
- data/test/integration/users_edit_test.rb +178 -0
- data/test/integration/users_index_test.rb +62 -0
- data/test/integration/users_login_test.rb +67 -0
- data/test/integration/users_signup_test.rb +54 -0
- data/test/mailers/.keep +0 -0
- data/test/mailers/barkest_core/contact_form_test.rb +28 -0
- data/test/mailers/barkest_core/user_mailer_test.rb +43 -0
- data/test/mailers/previews/barkest_core/contact_form_preview.rb +17 -0
- data/test/mailers/previews/barkest_core/user_mailer_preview.rb +26 -0
- data/test/models/access_group_group_member_test.rb +28 -0
- data/test/models/access_group_test.rb +114 -0
- data/test/models/access_group_user_member_test.rb +28 -0
- data/test/models/barkest_core/auth_config_test.rb +57 -0
- data/test/models/barkest_core/bool_parser_test.rb +28 -0
- data/test/models/barkest_core/contact_message_test.rb +61 -0
- data/test/models/barkest_core/database_config_test.rb +33 -0
- data/test/models/barkest_core/date_parser_test.rb +110 -0
- data/test/models/barkest_core/email_config_test.rb +57 -0
- data/test/models/barkest_core/global_status_test.rb +50 -0
- data/test/models/barkest_core/ms_sql_db_updater_test.rb +115 -0
- data/test/models/barkest_core/ms_sql_definition_test.rb +102 -0
- data/test/models/barkest_core/ms_sql_function_test.rb +131 -0
- data/test/models/barkest_core/number_parser_test.rb +29 -0
- data/test/models/barkest_core/self_update_config_test.rb +29 -0
- data/test/models/barkest_core/user_alert_test.rb +19 -0
- data/test/models/barkest_core/user_manager_test.rb +34 -0
- data/test/models/barkest_core/work_path_test.rb +26 -0
- data/test/models/disable_user_test.rb +27 -0
- data/test/models/generic_time_test.rb +66 -0
- data/test/models/ldap_access_group_test.rb +31 -0
- data/test/models/pdf_table_builder_test.rb +6 -0
- data/test/models/system_config_test.rb +78 -0
- data/test/models/user_login_history_test.rb +37 -0
- data/test/models/user_test.rb +130 -0
- data/test/test_helper.rb +63 -0
- metadata +798 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
|
|
3
|
+
module BarkestCore
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Reads a log line from a JSON log file.
|
|
7
|
+
class LogEntry
|
|
8
|
+
|
|
9
|
+
include ActiveModel::Model
|
|
10
|
+
include ActiveModel::Validations
|
|
11
|
+
include Comparable
|
|
12
|
+
|
|
13
|
+
attr_reader :level, :time, :message, :app_name, :app_version, :process_id
|
|
14
|
+
|
|
15
|
+
validates :level, presence: true
|
|
16
|
+
validates :time, presence: true
|
|
17
|
+
validates :message, presence: true
|
|
18
|
+
|
|
19
|
+
SEVERITY_LIST = %w(DEBUG INFO WARN ERROR FATAL)
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Creates a LogEntry.
|
|
23
|
+
#
|
|
24
|
+
# The args can either be a JSON string or a Hash.
|
|
25
|
+
def initialize(*args)
|
|
26
|
+
args.each do |arg|
|
|
27
|
+
if arg.is_a?(String)
|
|
28
|
+
arg = JSON.parse(arg).symbolize_keys rescue nil
|
|
29
|
+
end
|
|
30
|
+
if arg.is_a?(Hash)
|
|
31
|
+
arg.each do |k,v|
|
|
32
|
+
k = k.to_sym
|
|
33
|
+
v = case k
|
|
34
|
+
when :level
|
|
35
|
+
v.to_sym
|
|
36
|
+
when :time
|
|
37
|
+
Time.parse(v)
|
|
38
|
+
when :process_id
|
|
39
|
+
v.to_i
|
|
40
|
+
else
|
|
41
|
+
v
|
|
42
|
+
end
|
|
43
|
+
instance_variable_set(:"@#{k}", v)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Gets the index in the log file.
|
|
51
|
+
def index
|
|
52
|
+
@index ||= 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Gets the level as a numeric ID.
|
|
57
|
+
def level_id
|
|
58
|
+
@level_id ||= SEVERITY_LIST.index(level.to_s.upcase) || 5
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# :nodoc:
|
|
62
|
+
def inspect
|
|
63
|
+
"#<#{self.class.name} #{level} #{time} [#{app_name} #{app_version} (#{process_id})] #{message.length > 32 ? (message[0...32] + '...') : message}>"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# :nodoc:
|
|
67
|
+
def to_s
|
|
68
|
+
"#{level} #{time} [#{app_name} #{app_version} (#{process_id})] #{message}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# :nodoc:
|
|
72
|
+
def <=>(other)
|
|
73
|
+
return 1 unless other.is_a?(LogEntry)
|
|
74
|
+
if index == other.index
|
|
75
|
+
time <=> other.time
|
|
76
|
+
else
|
|
77
|
+
index <=> other.index
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# Reads a log file consisting of JSON records.
|
|
83
|
+
#
|
|
84
|
+
# If no log file is specified, the default log file is assumed.
|
|
85
|
+
def self.read_log(log_file = nil)
|
|
86
|
+
log_file ||= Rails.root.join('log', "#{Rails.env}.log")
|
|
87
|
+
|
|
88
|
+
ret = []
|
|
89
|
+
|
|
90
|
+
if File.exist?(log_file)
|
|
91
|
+
File.foreach(log_file, "\n").with_index do |line, index|
|
|
92
|
+
line = JSON.parse(line) rescue nil
|
|
93
|
+
ret << LogEntry.new(line.symbolize_keys.merge(index: index)) if line.is_a?(Hash)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
ret
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module BarkestCore
|
|
2
|
+
class LogViewOptions
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
include BarkestCore::DateParser
|
|
5
|
+
|
|
6
|
+
attr_accessor :search, :max_records
|
|
7
|
+
attr_reader :start_time, :end_time
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def min_severity=(value)
|
|
11
|
+
@min_severity = BarkestCore::LogEntry::SEVERITY_LIST.index(value.to_s.upcase)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def min_severity
|
|
15
|
+
return nil unless instance_variable_defined?(:@min_severity)
|
|
16
|
+
return nil unless @min_severity
|
|
17
|
+
BarkestCore::LogEntry::SEVERITY_LIST[@min_severity]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def min_severity_id
|
|
21
|
+
return nil unless instance_variable_defined?(:@min_severity)
|
|
22
|
+
@min_severity
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def start_time=(value)
|
|
26
|
+
@start_time = parse_for_date_column(value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def end_time=(value)
|
|
30
|
+
@end_time = parse_for_date_column(value)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def search_regex
|
|
34
|
+
@search_regex ||=
|
|
35
|
+
if search.blank?
|
|
36
|
+
nil
|
|
37
|
+
else
|
|
38
|
+
/#{search.gsub('[]', '\[\]')}/i rescue nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def keep_log_entry?(log_entry)
|
|
43
|
+
return false if min_severity_id && log_entry.level_id < min_severity_id
|
|
44
|
+
return false if start_time && log_entry.time < start_time
|
|
45
|
+
return false if end_time && log_entry.time > (end_time + 1.day) # include events from the end date.
|
|
46
|
+
return false unless search_regex.nil? || search_regex.match(log_entry.message)
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
module BarkestCore
|
|
2
|
+
##
|
|
3
|
+
# This class can be used to conditionally update a target SQL database with custom code.
|
|
4
|
+
#
|
|
5
|
+
# The updater will hunt through the SQL files found in the +sql_sources+ passed in.
|
|
6
|
+
# For each source, the updater calculates the new version and compares it against the
|
|
7
|
+
# existing version. If the new version is actually newer, the updater will update the
|
|
8
|
+
# object in the database.
|
|
9
|
+
class MsSqlDbDefinition
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# The base error for errors raised by the updater class.
|
|
13
|
+
UpdateError = Class.new(StandardError)
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# The error raised when the provided connection does not provide a user with full control over the database.
|
|
17
|
+
NeedFullAccess = Class.new(UpdateError)
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# The error raised when an object type doesn't match the previous type for the object with the specified name.
|
|
21
|
+
ObjectTypeMismatch = Class.new(UpdateError)
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# The name of the table holding the object versions.
|
|
25
|
+
VERSION_TABLE_NAME = 'zz_barkest__versions'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
attr_reader :table_prefix
|
|
29
|
+
|
|
30
|
+
class Conn < ActiveRecord::Base
|
|
31
|
+
self.abstract_class = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Defines a new updater.
|
|
36
|
+
#
|
|
37
|
+
# Options can include +source_paths+, +before_update+, and +after_update+.
|
|
38
|
+
#
|
|
39
|
+
# The +before_update+ and +after_update+ options define a callback to be run before or after
|
|
40
|
+
# the database update is performed. This can be a string referencing a method or it can be a Proc.
|
|
41
|
+
#
|
|
42
|
+
# MsSqlDbDefinition.new(
|
|
43
|
+
# :before_update => 'MyClass.my_method(db_conn,user)',
|
|
44
|
+
# :after_update => Proc.new do |db_conn, user|
|
|
45
|
+
# ...
|
|
46
|
+
# end
|
|
47
|
+
# )
|
|
48
|
+
#
|
|
49
|
+
# If you use the string option, note that the +db_conn+ and +user+ variables are available. In the example
|
|
50
|
+
# above they are being passed to the method as arguments.
|
|
51
|
+
def initialize(options = {})
|
|
52
|
+
options = {
|
|
53
|
+
table_name_prefix: 'zz_barkest_'
|
|
54
|
+
}.merge(options || {}).symbolize_keys
|
|
55
|
+
|
|
56
|
+
@table_prefix = options[:table_name_prefix].to_s
|
|
57
|
+
valid_regex = /^[a-z][a-z0-9_]*$/im
|
|
58
|
+
raise 'invalid table prefix' unless valid_regex.match(@table_prefix)
|
|
59
|
+
|
|
60
|
+
@sources = [ ]
|
|
61
|
+
@source_paths = [ ]
|
|
62
|
+
@pre_update = options.delete(:before_update) || options.delete(:pre_update)
|
|
63
|
+
@post_update = options.delete(:after_update) || options.delete(:post_update)
|
|
64
|
+
|
|
65
|
+
# and any other paths provided via options.
|
|
66
|
+
if options[:source_paths]
|
|
67
|
+
if options[:source_paths].is_a?(String)
|
|
68
|
+
add_source_path options[:source_paths]
|
|
69
|
+
elsif options[:source_paths].respond_to?(:each)
|
|
70
|
+
options[:source_paths].each do |path|
|
|
71
|
+
add_source_path path.to_s
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
add_source_path options[:source_paths].to_s
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Gets an object's name according to this DB updater.
|
|
82
|
+
def object_name(unprefixed_name)
|
|
83
|
+
name = unprefixed_name.to_s
|
|
84
|
+
return name if name.index(table_prefix) == 0
|
|
85
|
+
"#{table_prefix}#{name}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# Gets all of the source paths that have currently been searched.
|
|
90
|
+
def source_paths
|
|
91
|
+
@source_paths.dup.freeze
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
# Gets all of the sources currently loaded.
|
|
96
|
+
def sources
|
|
97
|
+
@sources.dup.map{|t| t.name_prefix = table_prefix; t}.freeze
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# Adds a source using a specific timestamp.
|
|
102
|
+
#
|
|
103
|
+
# The first line of the SQL should be a comment specifying the timestamp for the source.
|
|
104
|
+
# -- 2016-12-19 15:45
|
|
105
|
+
# -- 2016-12-19
|
|
106
|
+
# -- 201612191545
|
|
107
|
+
# -- 20161219
|
|
108
|
+
#
|
|
109
|
+
# The timestamp will be converted into a 12-digit number, if time is not specified it will be right-padded
|
|
110
|
+
# with zeroes to get to the 12-digit number.
|
|
111
|
+
#
|
|
112
|
+
# The +sql+ should be a valid create/alter table/view/function statement.
|
|
113
|
+
def add_source(sql)
|
|
114
|
+
sql_def = BarkestCore::MsSqlDefinition.new(sql, '')
|
|
115
|
+
sql_def.instance_variable_set(:@source_location, "::#{sql_def.name}::")
|
|
116
|
+
add_sql_def sql_def
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# Adds a MsSqlDefinition object to the sources for this updater.
|
|
122
|
+
#
|
|
123
|
+
# The +definition+ should be a previously created MsSqlDefinition object.
|
|
124
|
+
def add_source_definition(definition)
|
|
125
|
+
add_sql_def definition
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# Adds all SQL files found in the specified directory to the sources for this updater.
|
|
131
|
+
#
|
|
132
|
+
# The +path+ should contain the SQL files. If there are subdirectories, you should
|
|
133
|
+
# include them individually.
|
|
134
|
+
#
|
|
135
|
+
# The source files should specify a timestamp in the first comment.
|
|
136
|
+
# -- 2016-12-19 15:45
|
|
137
|
+
# -- 2016-12-19
|
|
138
|
+
# -- 201612191545
|
|
139
|
+
# -- 20161219
|
|
140
|
+
#
|
|
141
|
+
# The timestamp will be converted into a 12-digit number, if time is not specified it will be right-padded
|
|
142
|
+
# with zeroes to get to the 12-digit number.
|
|
143
|
+
#
|
|
144
|
+
def add_source_path(path)
|
|
145
|
+
raise 'path must be a string' unless path.is_a?(String)
|
|
146
|
+
|
|
147
|
+
path = File.expand_path(path)
|
|
148
|
+
raise 'cannot add root path' if path == '/'
|
|
149
|
+
path = path[0...-1] if path[-1] == '/'
|
|
150
|
+
|
|
151
|
+
unless @source_paths.include?(path)
|
|
152
|
+
@source_paths << path
|
|
153
|
+
|
|
154
|
+
if Dir.exist?(path)
|
|
155
|
+
Dir.glob("#{path}/*.sql").each do |source|
|
|
156
|
+
add_sql_def BarkestCore::MsSqlDefinition.new(File.read(source), source, File.mtime(source))
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
##
|
|
165
|
+
# Performs the database update using the specified configuration.
|
|
166
|
+
#
|
|
167
|
+
# A warning will be logged if the runtime user has full access to the database.
|
|
168
|
+
#
|
|
169
|
+
# An error will be raised if there is the runtime user does not have full access and no update_user is provided,
|
|
170
|
+
# or if an update_user is provided who also does not have full access to the database.
|
|
171
|
+
def update_db(config, options = {})
|
|
172
|
+
|
|
173
|
+
begin
|
|
174
|
+
options ||= {}
|
|
175
|
+
|
|
176
|
+
runtime_user = config[:username]
|
|
177
|
+
|
|
178
|
+
Conn.remove_connection
|
|
179
|
+
Conn.establish_connection config
|
|
180
|
+
|
|
181
|
+
if have_db_control?
|
|
182
|
+
warn "WARNING: Runtime user '#{runtime_user}' has full access to the database. (this is not recommended)" unless Rails.env.test?
|
|
183
|
+
else
|
|
184
|
+
raise NeedFullAccess, 'please provide update_username and update_password for a user with full access to the database' unless config[:update_username]
|
|
185
|
+
|
|
186
|
+
use_config = config.dup
|
|
187
|
+
use_config[:username] = config[:update_username]
|
|
188
|
+
use_config[:password] = config[:update_password]
|
|
189
|
+
|
|
190
|
+
Conn.remove_connection
|
|
191
|
+
Conn.establish_connection use_config
|
|
192
|
+
|
|
193
|
+
raise NeedFullAccess, 'provided update user does not have full access to the database' unless have_db_control?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
unless Conn.object_exists?(VERSION_TABLE_NAME)
|
|
197
|
+
debug 'Creating version tracking table...'
|
|
198
|
+
db_connection.execute <<-EOSQL
|
|
199
|
+
CREATE TABLE [#{VERSION_TABLE_NAME}] (
|
|
200
|
+
[object_name] VARCHAR(120) NOT NULL PRIMARY KEY,
|
|
201
|
+
[object_type] VARCHAR(40) NOT NULL,
|
|
202
|
+
[object_version] VARCHAR(40) NOT NULL,
|
|
203
|
+
[created] DATETIME NOT NULL,
|
|
204
|
+
[updated] DATETIME NOT NULL,
|
|
205
|
+
[created_by] VARCHAR(120),
|
|
206
|
+
[updated_by] VARCHAR(120)
|
|
207
|
+
)
|
|
208
|
+
EOSQL
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
if (proc = (options[:before_update] || options[:pre_update] || @pre_update))
|
|
212
|
+
if proc.is_a?(String)
|
|
213
|
+
code = proc
|
|
214
|
+
proc = Proc.new { |db_conn, user| eval code }
|
|
215
|
+
end
|
|
216
|
+
if proc.respond_to?(:call)
|
|
217
|
+
debug 'Running pre-update code...'
|
|
218
|
+
proc.call db_connection, runtime_user
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
debug 'Processing source list...'
|
|
223
|
+
sources.each do |src|
|
|
224
|
+
src.name_prefix = table_prefix
|
|
225
|
+
|
|
226
|
+
cur_ver = get_version src.prefixed_name
|
|
227
|
+
|
|
228
|
+
if cur_ver
|
|
229
|
+
raise ObjectTypeMismatch, "object type mismatch for #{src.prefixed_name}" unless src.type.upcase == cur_ver['object_type'].upcase
|
|
230
|
+
if cur_ver['object_version'].to_i >= src.version.to_i
|
|
231
|
+
debug " > Preserving #{src.prefixed_name}..."
|
|
232
|
+
next # source
|
|
233
|
+
else
|
|
234
|
+
debug " > Updating #{src.prefixed_name}..."
|
|
235
|
+
if src.is_create?
|
|
236
|
+
db_connection.execute src.drop_sql
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
debug " > Creating #{src.prefixed_name}..."
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
db_connection.execute src.update_sql
|
|
244
|
+
db_connection.execute src.grant_sql(runtime_user)
|
|
245
|
+
set_version src.prefixed_name, src.type, src.version
|
|
246
|
+
|
|
247
|
+
src.name_prefix = ''
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
if (proc = (options[:after_update] || options[:post_update] || @post_update))
|
|
251
|
+
if proc.is_a?(String)
|
|
252
|
+
code = proc
|
|
253
|
+
proc = Proc.new { |db_conn, user| eval code }
|
|
254
|
+
end
|
|
255
|
+
if proc.respond_to?(:call)
|
|
256
|
+
debug 'Running post-update code...'
|
|
257
|
+
proc.call db_connection, runtime_user
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
yield db_connection, runtime_user if block_given?
|
|
262
|
+
|
|
263
|
+
ensure
|
|
264
|
+
Conn.remove_connection
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
true
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
##
|
|
271
|
+
# Registers a DB updater and tells BarkestCore that the named database exists and could use a configuration.
|
|
272
|
+
#
|
|
273
|
+
# The +options+ will be passed to the MsSqlDbDefinition constructor, except for the +extra_params+ key.
|
|
274
|
+
# If this key is provided, it is pulled out and used for the defaults for the database configuration.
|
|
275
|
+
#
|
|
276
|
+
# Ideally this is to provide the +extra_[1|2]_name+, +extra_[1|2]_type+, and +extra_[1|2]_value+ parameters, but
|
|
277
|
+
# you can also use it to provide reasonable defaults for +host+, +database+, or even credentials.
|
|
278
|
+
#
|
|
279
|
+
def self.register(name, options={})
|
|
280
|
+
name = symbolize_name name
|
|
281
|
+
|
|
282
|
+
raise 'already registered' if registered.include?(name)
|
|
283
|
+
|
|
284
|
+
options = (options || {}).symbolize_keys
|
|
285
|
+
|
|
286
|
+
extra_params = options.delete(:extra_params)
|
|
287
|
+
if extra_params.is_a?(Hash)
|
|
288
|
+
repeat = true
|
|
289
|
+
while repeat
|
|
290
|
+
repeat = false
|
|
291
|
+
extra_params.dup.each do |k,v|
|
|
292
|
+
if v.is_a?(Hash)
|
|
293
|
+
extra_params.delete(k)
|
|
294
|
+
v.each do |subk,subv|
|
|
295
|
+
extra_params[:"#{k}_#{subk}"] = subv
|
|
296
|
+
end
|
|
297
|
+
repeat = true
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
options[:table_name_prefix] ||=
|
|
304
|
+
if name.to_s.index('barkest') == 0
|
|
305
|
+
"zz_#{name}_"
|
|
306
|
+
else
|
|
307
|
+
"zz_barkest_#{name}_"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
updater = MsSqlDbDefinition.new(options)
|
|
311
|
+
|
|
312
|
+
registered[name] = updater
|
|
313
|
+
|
|
314
|
+
# Register with DatabaseConfig to enable the config page for this DB.
|
|
315
|
+
DatabaseConfig.register name
|
|
316
|
+
|
|
317
|
+
cfg_def = (extra_params.is_a?(Hash) ? extra_params : {})
|
|
318
|
+
.merge(
|
|
319
|
+
{
|
|
320
|
+
adapter: 'sqlserver',
|
|
321
|
+
pool: 5,
|
|
322
|
+
timeout: 30000,
|
|
323
|
+
port: 1433,
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Register with BarkestCore so that the default configuration is somewhat appropriate.
|
|
328
|
+
BarkestCore.register_db_config_defaults name, cfg_def
|
|
329
|
+
|
|
330
|
+
updater
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
##
|
|
334
|
+
# Gets a DB updater by name.
|
|
335
|
+
def self.[](name)
|
|
336
|
+
name = symbolize_name name
|
|
337
|
+
registered[name]
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
##
|
|
341
|
+
# Gets a list of all the DB updaters currently registered.
|
|
342
|
+
def self.keys
|
|
343
|
+
registered.keys
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
##
|
|
347
|
+
# Iterates through the registered DB updaters.
|
|
348
|
+
#
|
|
349
|
+
# Yields the db_name and the db_updater to the block.
|
|
350
|
+
def self.each
|
|
351
|
+
registered.each do |k,v|
|
|
352
|
+
yield k, v if block_given?
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
private
|
|
357
|
+
|
|
358
|
+
def self.symbolize_name(name)
|
|
359
|
+
name.to_s.underscore.gsub('/', '_').to_sym
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def self.registered
|
|
363
|
+
@registered ||= {}
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def get_version(object_name)
|
|
367
|
+
object_name = object_name.to_s.gsub("'", "''")
|
|
368
|
+
db_connection.exec_query("SELECT [object_name], [object_type], [object_version], [created], [updated], [created_by], [updated_by] FROM [#{VERSION_TABLE_NAME}] WHERE [object_name]='#{object_name}'").first
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def set_version(object_name, object_type, object_version)
|
|
372
|
+
raw_obj_name = object_name
|
|
373
|
+
existing = get_version(raw_obj_name)
|
|
374
|
+
|
|
375
|
+
object_name = object_name.to_s.gsub("'", "''")
|
|
376
|
+
object_type = object_type.to_s.gsub("'", "''")
|
|
377
|
+
object_version = object_version.to_s.gsub("'", "''")
|
|
378
|
+
time = Time.now.strftime('%Y-%m-%d %H:%M:%S').gsub("'", "''")
|
|
379
|
+
app = (Rails && Rails.application) ? Rails.application.class.to_s.gsub("'", "''") : '<UNKNOWN>'
|
|
380
|
+
|
|
381
|
+
if existing
|
|
382
|
+
raise ObjectTypeMismatch, 'object type mismatch' unless existing['object_type'] == object_type
|
|
383
|
+
db_connection.execute "UPDATE [#{VERSION_TABLE_NAME}] SET [object_version]='#{object_version}', [updated]='#{time}', [updated_by]='#{app}' WHERE [object_name]='#{object_name}'"
|
|
384
|
+
else
|
|
385
|
+
db_connection.execute "INSERT INTO [#{VERSION_TABLE_NAME}] ([object_name], [object_type], [object_version], [created], [updated], [created_by], [updated_by]) VALUES ('#{object_name}','#{object_type}','#{object_version}','#{time}','#{time}','#{app}','#{app}')"
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
get_version raw_obj_name
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def db_connection
|
|
392
|
+
Conn.connection
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def have_db_control?
|
|
396
|
+
# user must have CONTROL permission on the database itself.
|
|
397
|
+
# if it does, then we are good to move forward.
|
|
398
|
+
result = db_connection.exec_query('SELECT COUNT(*) AS "one" FROM "fn_my_permissions"(NULL, \'DATABASE\') WHERE "permission_name"=\'CONTROL\'').first
|
|
399
|
+
result && result['one'] == 1
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def debug(s)
|
|
403
|
+
if Rails && Rails.logger && Rails.logger.respond_to?(:debug)
|
|
404
|
+
Rails.logger.debug(s)
|
|
405
|
+
else
|
|
406
|
+
$stdout.puts s
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def warn(s)
|
|
411
|
+
if Rails && Rails.logger && Rails.logger.respond_to?(:warn)
|
|
412
|
+
Rails.logger.warn(s)
|
|
413
|
+
else
|
|
414
|
+
$stderr.puts s
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def add_sql_def(sql_def)
|
|
419
|
+
existing = @sources.find{ |item| item.name == sql_def.name }
|
|
420
|
+
if existing
|
|
421
|
+
if existing == sql_def
|
|
422
|
+
debug "A #{existing.type.downcase} named #{existing.name} is already defined with the same source."
|
|
423
|
+
return nil
|
|
424
|
+
end
|
|
425
|
+
if existing.type != sql_def.type
|
|
426
|
+
raise ObjectTypeMismatch, "Cannot change type of object named #{existing.name} from #{existing.type} to #{sql_def.type}."
|
|
427
|
+
end
|
|
428
|
+
if existing.version.to_i > sql_def.version.to_i
|
|
429
|
+
warn "A #{existing.type.downcase} named #{existing.name} is already defined with newer source."
|
|
430
|
+
return nil
|
|
431
|
+
end
|
|
432
|
+
if sql_def.is_create?
|
|
433
|
+
warn "Removing old definition for #{existing.type.downcase} named #{existing.name}."
|
|
434
|
+
@sources.delete existing
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
@sources << sql_def
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
end
|
|
441
|
+
end
|