models_auditor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/models_auditor/application.js +13 -0
  5. data/app/assets/stylesheets/models_auditor/application.css +64 -0
  6. data/app/controllers/models_auditor/audit_base_controller.rb +5 -0
  7. data/app/controllers/models_auditor/audit_controller.rb +121 -0
  8. data/app/helpers/models_auditor/application_helper.rb +4 -0
  9. data/app/models/models_auditor/audit_record.rb +17 -0
  10. data/app/models/models_auditor/audit_request.rb +12 -0
  11. data/app/views/layouts/models_auditor/application.html.erb +12 -0
  12. data/app/views/models_auditor/audit/_record.html.erb +39 -0
  13. data/app/views/models_auditor/audit/_request.html.erb +25 -0
  14. data/app/views/models_auditor/audit/index.html.erb +3 -0
  15. data/config/config_option_descriptions.yml +23 -0
  16. data/config/routes.rb +5 -0
  17. data/lib/generators/models_auditor/db_config/USAGE +7 -0
  18. data/lib/generators/models_auditor/db_config/db_config_generator.rb +23 -0
  19. data/lib/generators/models_auditor/install/USAGE +8 -0
  20. data/lib/generators/models_auditor/install/install_generator.rb +25 -0
  21. data/lib/generators/models_auditor/install/templates/initializer.rb.erb +13 -0
  22. data/lib/generators/models_auditor/migrations/USAGE +8 -0
  23. data/lib/generators/models_auditor/migrations/migrations_generator.rb +15 -0
  24. data/lib/generators/models_auditor/migrations/templates/create_audit_records.rb.erb +18 -0
  25. data/lib/generators/models_auditor/migrations/templates/create_audit_requests.rb.erb +12 -0
  26. data/lib/generators/models_auditor/migrations_helper.rb +24 -0
  27. data/lib/models_auditor.rb +37 -0
  28. data/lib/models_auditor/audit.rb +202 -0
  29. data/lib/models_auditor/config.rb +118 -0
  30. data/lib/models_auditor/controller.rb +67 -0
  31. data/lib/models_auditor/engine.rb +5 -0
  32. data/lib/models_auditor/version.rb +3 -0
  33. data/lib/tasks/models_auditor_tasks.rake +75 -0
  34. data/test/dummy/README.rdoc +28 -0
  35. data/test/dummy/Rakefile +6 -0
  36. data/test/dummy/app/assets/javascripts/application.js +13 -0
  37. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  40. data/test/dummy/bin/bundle +3 -0
  41. data/test/dummy/bin/rails +4 -0
  42. data/test/dummy/bin/rake +4 -0
  43. data/test/dummy/bin/setup +29 -0
  44. data/test/dummy/config.ru +4 -0
  45. data/test/dummy/config/application.rb +26 -0
  46. data/test/dummy/config/boot.rb +5 -0
  47. data/test/dummy/config/database.yml +25 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +41 -0
  50. data/test/dummy/config/environments/production.rb +79 -0
  51. data/test/dummy/config/environments/test.rb +42 -0
  52. data/test/dummy/config/initializers/assets.rb +11 -0
  53. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  54. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  55. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  56. data/test/dummy/config/initializers/inflections.rb +16 -0
  57. data/test/dummy/config/initializers/mime_types.rb +4 -0
  58. data/test/dummy/config/initializers/session_store.rb +3 -0
  59. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  60. data/test/dummy/config/locales/en.yml +23 -0
  61. data/test/dummy/config/routes.rb +4 -0
  62. data/test/dummy/config/secrets.yml +22 -0
  63. data/test/dummy/public/404.html +67 -0
  64. data/test/dummy/public/422.html +67 -0
  65. data/test/dummy/public/500.html +66 -0
  66. data/test/dummy/public/favicon.ico +0 -0
  67. data/test/integration/navigation_test.rb +8 -0
  68. data/test/models_auditor_test.rb +7 -0
  69. data/test/test_helper.rb +21 -0
  70. metadata +249 -0
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+ module ModelsAuditor
3
+ class DbConfigGenerator < Rails::Generators::Base
4
+ def add_db_config
5
+ if (nmsps = ModelsAuditor.config.connection_namespace).present?
6
+ inject_into_file 'config/database.yml', before: /\z/ do
7
+ "\n#{nmsps}_development: &#{nmsps}_development\n" +
8
+ " adapter: postgresql\n" +
9
+ " encoding: unicode\n" +
10
+ " database: audit_database\n" +
11
+ " pool: 5\n" +
12
+ " host: localhost\n" +
13
+ " username: audit_user\n" +
14
+ " password: \n" +
15
+ "#{nmsps}_production: *#{nmsps}_development\n" +
16
+ "#{nmsps}_staging: *#{nmsps}_development\n" +
17
+ "#{nmsps}_test: *#{nmsps}_development\n"
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Create a ModelsAuditor initializer'
3
+
4
+ Example:
5
+ rails generate models_auditor:install
6
+
7
+ This will create:
8
+ config/initializers/models_auditor.rb
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+ module ModelsAuditor
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ def descriptions
7
+ @descriptions ||= YAML.load_file(File.expand_path('../../../../../config/config_option_descriptions.yml', __FILE__))
8
+ end
9
+
10
+ def copy_initializer
11
+ template 'initializer.rb.erb', 'config/initializers/models_auditor.rb'
12
+ end
13
+
14
+ # def mount_routes
15
+ # [
16
+ # 'Rails.application.routes.draw do',
17
+ # 'Application.routes.draw do'
18
+ # ].each do |after_str|
19
+ # inject_into_file 'config/routes.rb', :after => after_str do
20
+ # "\n mount GlobalStore::Engine, at: '/global_store'\n"
21
+ # end
22
+ # end
23
+ # end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # Настройки аудитора моделей
2
+
3
+ ModelsAuditor.configure do |config|
4
+ <%
5
+ (ModelsAuditor::Config::CONFIG_OPTIONS).each do |option_sym|
6
+ descr = descriptions[option_sym.to_s].try(:split, "\n").try(:join, "\n # ")
7
+ concat " # #{descr.force_encoding('ASCII-8BIT')}\n" if descr.present?
8
+ concat " # config.#{option_sym} = "
9
+ concat (ModelsAuditor.config.default[option_sym].try(:[], :config) || 'nil').force_encoding('ASCII-8BIT')
10
+ concat "\n\n"
11
+ end
12
+ %>
13
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Create migrations for your audit database
3
+
4
+ Example:
5
+ rails generate models_auditor:migrations
6
+
7
+ This will create:
8
+ db/<audit_migrate_dir>/create_audit_records.rb
@@ -0,0 +1,15 @@
1
+ require 'rails/generators/base'
2
+ require 'generators/models_auditor/migrations_helper'
3
+
4
+ module ModelsAuditor
5
+ class MigrationsGenerator < Rails::Generators::Base
6
+ include MigrationsHelper
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def create_migration_file
10
+ @migration_postfix = SecureRandom.hex
11
+ copy_migration 'create_audit_records', "create_audit_records_n#{@migration_postfix}"
12
+ copy_migration 'create_audit_requests', "create_audit_requests_n#{@migration_postfix}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ class CreateAuditRecordsN<%= @migration_postfix %> < ActiveRecord::Migration
2
+ def change
3
+ <% audit_tbn = ModelsAuditor.config.audit_records_table_name %>
4
+ create_table :<%= audit_tbn %>, comment: 'Журнал изменений данных в моделях' do |t|
5
+ t.integer :request_id, null: true, unsigned: true, comment: 'зафиксированные изменения'
6
+ t.integer :action, null: false, unsigned: true, comment: 'действие'
7
+ t.json :content, null: false, default: {}, comment: 'зафиксированные изменения'
8
+ t.string :object_type, null: false, comment: 'класс логируемого объекта'
9
+ t.integer :object_id, null: false, unsigned: true, comment: 'id логируемого объекта'
10
+ t.json :bridge, null: true, comment: 'данные внешних ключей связующей таблицы'
11
+ t.datetime :created_at, null: false, comment: 'дата и время зафиксированных изменений'
12
+ end
13
+ add_index :<%= audit_tbn %>, :id
14
+ add_index :<%= audit_tbn %>, :request_id
15
+ add_index :<%= audit_tbn %>, :created_at
16
+ add_index :<%= audit_tbn %>, [:object_id, :object_type]
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ class CreateAuditRequestsN<%= @migration_postfix %> < ActiveRecord::Migration
2
+ def change
3
+ <% audit_tbn = ModelsAuditor.config.audit_requests_table_name %>
4
+ create_table :<%= audit_tbn %>, comment: 'Журнал изменений данных в моделях' do |t|
5
+ t.integer :user_id, null: true, unsigned: true, comment: 'id ответственного'
6
+ t.json :request_info, null: false, default: {}, comment: 'Информация о запросе'
7
+ t.datetime :created_at, null: false, comment: 'дата и время запроса'
8
+ end
9
+ add_index :<%= audit_tbn %>, :id
10
+ add_index :<%= audit_tbn %>, :created_at
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ module ModelsAuditor
2
+ module MigrationsHelper
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Rails::Generators::Migration
7
+
8
+ def self.next_migration_number(dirname)
9
+ next_migration_number = current_migration_number(dirname) + 1
10
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
11
+ end
12
+ end
13
+
14
+ def copy_migration(filename, destination)
15
+ migrations_dir = File.join('db', ModelsAuditor.config.audit_migrations_dir)
16
+
17
+ if self.class.migration_exists?(migrations_dir, "#{destination}.rb")
18
+ say_status('skipped', "Migration #{destination}.rb already exists in #{migrations_dir}")
19
+ else
20
+ migration_template "#{filename}.rb.erb", File.join(migrations_dir, "#{destination}.rb")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ require 'request_store'
2
+ require 'models_auditor/engine'
3
+ require 'models_auditor/config'
4
+ require 'models_auditor/audit'
5
+ require 'models_auditor/controller'
6
+
7
+ module ModelsAuditor
8
+ module_function
9
+ def log_error(*args)
10
+ if (logger = ModelsAuditor.config.logger)
11
+ logger.error(*args)
12
+ end
13
+ puts *args
14
+ end
15
+
16
+ def log_info(*args)
17
+ if (logger = ModelsAuditor.config.logger)
18
+ logger.info(*args)
19
+ end
20
+ puts *args
21
+ end
22
+
23
+ def log_warn(*args)
24
+ if (logger = ModelsAuditor.config.logger)
25
+ logger.warn(*args)
26
+ end
27
+ puts *args
28
+ end
29
+
30
+ def store
31
+ RequestStore.store[:models_auditor_store] ||= {}
32
+ end
33
+ end
34
+
35
+ ActiveSupport.on_load(:active_record) do
36
+ include ModelsAuditor::Audit
37
+ end
@@ -0,0 +1,202 @@
1
+ module ModelsAuditor
2
+ module Audit
3
+ # Сбор данных через метод #as_json
4
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_JSON, only: [:title, :subtitle, :published_at]
5
+ AUDIT_MODE_JSON = 1
6
+ # Сбор данных через сериалайзер
7
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_SERIALIZER, serializer: AuditPostSerializer
8
+ AUDIT_MODE_SERIALIZER = 2
9
+ # Сбор данных через назначенный метод
10
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_SERIALIZER, method: :logged_data
11
+ AUDIT_MODE_METHOD = 3
12
+ # Сбор данных через #previous_changes
13
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_CHANGES_ONLY
14
+ AUDIT_MODE_CHANGES_ONLY = 4
15
+
16
+ AUDIT_SNAPSHOT_MODES = [AUDIT_MODE_JSON, AUDIT_MODE_SERIALIZER, AUDIT_MODE_METHOD]
17
+ AUDIT_CHANGES_MODES = [AUDIT_MODE_CHANGES_ONLY]
18
+
19
+ def self.included(base)
20
+ base.send :extend, ClassMethods
21
+ end
22
+
23
+ module InstanceMethods
24
+ def do_audit_init_snapshot
25
+ return unless ModelsAuditor.config.audit_enabled
26
+ mode = self.class.instance_variable_get(:@audit_mode)
27
+ return unless self.class.instance_variable_get(:@audit_enabled) && AUDIT_SNAPSHOT_MODES.include?(mode)
28
+ ma_store_initial_state(ModelsAuditor.store)
29
+ end
30
+
31
+ def do_audit_process
32
+ return unless ModelsAuditor.config.audit_enabled
33
+ return unless self.class.instance_variable_get(:@audit_enabled)
34
+ mode = self.class.instance_variable_get(:@audit_mode)
35
+ options = self.class.instance_variable_get(:@audit_settings) || {}
36
+ store = ModelsAuditor.store
37
+
38
+ initial_data = ma_get_initial_state(store)
39
+ current_data = ma_auditor_get_data
40
+
41
+ action =
42
+ case
43
+ when transaction_include_any_action?([:create])
44
+ ModelsAuditor::AuditRecord::ACTION_CREATE
45
+ when transaction_include_any_action?([:update])
46
+ ModelsAuditor::AuditRecord::ACTION_UPDATE
47
+ when transaction_include_any_action?([:destroy])
48
+ ModelsAuditor::AuditRecord::ACTION_DESTROY
49
+ end
50
+
51
+ bridge =
52
+ if options[:bridge]
53
+ options[:bridge].each_with_object({}) { |(model_name, key), o| o[model_name] = {key => __send__(key)} }
54
+ end
55
+
56
+ Thread.new do
57
+ begin
58
+ log_anyway = !ModelsAuditor.config.audit_request_changes_only
59
+ if (request = store[:audit_request]) || log_anyway
60
+ body =
61
+ case
62
+ when AUDIT_SNAPSHOT_MODES.include?(mode)
63
+ ma_eliminate_not_changed_keys(initial_data, current_data)
64
+ when AUDIT_CHANGES_MODES.include?(mode)
65
+ current_data
66
+ else
67
+ raise ArgumentError.new('Incorrect value of argument audit_type')
68
+ end
69
+
70
+ if request.try(:new_record?) && !request.save
71
+ ModelsAuditor.log_error("Couldn't save request record")
72
+ ModelsAuditor.log_error(request.errors.full_messages)
73
+ return
74
+ end
75
+ record =
76
+ ModelsAuditor::AuditRecord.new(
77
+ request: request,
78
+ auditable: self,
79
+ content: body,
80
+ action: action,
81
+ bridge: bridge
82
+ )
83
+ unless record.save
84
+ ModelsAuditor.log_error("Couldn't logged changes of #{self.class.name} id: #{self.try(:id)}")
85
+ ModelsAuditor.log_error(record.errors.full_messages)
86
+ end
87
+ end
88
+ rescue Exception => e
89
+ ModelsAuditor.log_error("Couldn't logged changes of #{self.class.name} id: #{self.try(:id)}")
90
+ ModelsAuditor.log_error(e.message)
91
+ ModelsAuditor.log_error(e.backtrace.take(100).join("\n"))
92
+ end
93
+ # TODO To remove the #join call from the thread block after debugging
94
+ end.join
95
+ end
96
+
97
+ private
98
+
99
+ # Сравнивает два хэша, оставляя только отличающиеся по значению ключи
100
+ # @return [Hash] filtered result with different attributes only
101
+ def ma_eliminate_not_changed_keys(old_hash, new_hash)
102
+ case
103
+ # Равны или оба nil
104
+ when old_hash == new_hash
105
+ {}
106
+ # Один из них nil
107
+ when (old_hash && new_hash).nil?
108
+ (old_hash || new_hash).keys.each_with_object({}) do |key, o|
109
+ if (was = old_hash.try(:[], key)) != (now = new_hash.try(:[], key))
110
+ o[key] = [was, now]
111
+ end
112
+ end
113
+ else # Оба не nil
114
+ (old_hash.keys | new_hash.keys).each_with_object({}) do |key, o|
115
+ if (was = old_hash[key]) != (now = new_hash[key])
116
+ o[key] = [was, now]
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ # Запоминает сериализованные данные для аудита
123
+ # Вызывать данный метод следует в коллбэке after_initialize
124
+ # Или в любом другом месте до изменения значений аттрибутов
125
+ def ma_store_initial_state(store)
126
+ store[:initial_states] ||= {}
127
+ states_of_mclass = (store[:initial_states][self.class.name] ||= {})
128
+ states_of_mclass[self.id] ||= ma_auditor_get_data
129
+ end
130
+
131
+ # Получает сериализованные данные для аудита подготовленные при инициализации сущности
132
+ # @return [Hash|nil] Начальные данные
133
+ def ma_get_initial_state(store)
134
+ store[:initial_states].try(:[], self.class.name).try(:[], self.id)
135
+ end
136
+
137
+ # Получает сериализованные данные для аудита
138
+ def ma_auditor_get_data
139
+ options = self.class.instance_variable_get(:@audit_settings) || {}
140
+ audit_params = options[:params]
141
+ mode = self.class.instance_variable_get(:@audit_mode)
142
+ case mode
143
+ when AUDIT_MODE_JSON
144
+ self.as_json(audit_params)
145
+ when AUDIT_MODE_SERIALIZER
146
+ if (serializer = options[:serializer]).blank?
147
+ raise ArgumentError.new('Required option :serializer for AUDIT_MODE_SERIALIZER was not passed')
148
+ end
149
+ serializer.new(self, audit_params || {}).as_json
150
+ when AUDIT_MODE_METHOD
151
+ if (method = options[:serializer]).blank?
152
+ raise ArgumentError.new('Required option :method for AUDIT_MODE_METHOD was not passed')
153
+ end
154
+ unless self.respond_to?(method)
155
+ raise ArgumentError.new("Passed method '#{method}' is undefined")
156
+ end
157
+ self.__send__(method)
158
+ when AUDIT_MODE_CHANGES_ONLY
159
+ self.previous_changes
160
+ else
161
+ raise ArgumentError.new('Incorrect value of argument audit_type')
162
+ end
163
+ end
164
+ end
165
+
166
+
167
+ module ClassMethods
168
+ # Активирует аудит изменений данных модели
169
+ # @param [Integer] audit_mode Способ логирования
170
+ # возможные значения: AUDIT_MODE_JSON | AUDIT_MODE_SERIALIZER | AUDIT_MODE_METHOD | AUDIT_MODE_CHANGES_ONLY
171
+ # AUDIT_MODE_JSON - Сериализация путем вызова метода as_json
172
+ # AUDIT_MODE_SERIALIZER - Сериализация через использование сериалайзера, указанного в опции :serializer
173
+ # AUDIT_MODE_METHOD - Сериализация данных формируемых в методе, указанном в опции :method
174
+ # AUDIT_MODE_CHANGES_ONLY - Сериализация данных модели, которые были изменены
175
+ # @param [Hash] options Настройки логирования
176
+ # @option options [params] Параметры сериализации данных.
177
+ # Для AUDIT_MODE_JSON значение передается в метод #as_json
178
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_JSON, only: [:title, :subtitle, :published_at]
179
+ # Для AUDIT_MODE_SERIALIZER значение передается в сериалайзер в качестве опций
180
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_SERIALIZER, serializer: AuditPostSerializer
181
+ # Для AUDIT_MODE_METHOD значение игнорируется
182
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_SERIALIZER, method: :logged_data
183
+ # Для AUDIT_MODE_CHANGES_ONLY значение игнорируется
184
+ # @example enable_audit ModelsAuditor::Audit::AUDIT_MODE_CHANGES_ONLY
185
+ def enable_audit(audit_mode, options = {})
186
+ @audit_enabled = true
187
+ @audit_mode = audit_mode
188
+ @audit_settings = options
189
+ # Lazily include the instance methods so we don't clutter up
190
+ # any more ActiveRecord models than we have to.
191
+ send :include, InstanceMethods
192
+ after_initialize :do_audit_init_snapshot
193
+ after_commit :do_audit_process
194
+ end
195
+
196
+ # Дезактивирует аудит изменений данных модели
197
+ def disable_audit
198
+ @audit_enabled = false
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,118 @@
1
+ module ModelsAuditor
2
+ class Config
3
+ CONFIG_OPTIONS = %i(
4
+ audit_enabled
5
+ connection_namespace
6
+ audit_records_table_name
7
+ audit_requests_table_name
8
+ audit_migrations_dir
9
+ audit_request_changes_only
10
+ logger
11
+ records_per_page
12
+ fake_total_count
13
+ audit_controller_base
14
+ respond_to_json_enabled
15
+ respond_to_html_enabled
16
+ json_response_data_key
17
+ json_response_meta_key
18
+ )
19
+
20
+ def initialize
21
+ @indexed_relations = []
22
+ end
23
+
24
+ def default
25
+ @default ||= {
26
+ audit_enabled: {
27
+ config: 'true',
28
+ val: true
29
+ },
30
+ connection_namespace: {
31
+ config: "'audit'",
32
+ val: 'audit'
33
+ },
34
+ audit_records_table_name: {
35
+ config: "'audit_records'",
36
+ val: 'audit_records'
37
+ },
38
+ audit_requests_table_name: {
39
+ config: "'audit_requests'",
40
+ val: 'audit_requests'
41
+ },
42
+ audit_migrations_dir: {
43
+ config: "'audit_migrate'",
44
+ val: 'audit_migrate'
45
+ },
46
+ logger: {
47
+ config: "Logger.new(Rails.root.join('log', 'models_auditor.log'))",
48
+ val: Logger.new(Rails.root.join('log', 'models_auditor.log'))
49
+ },
50
+ audit_request_changes_only: {
51
+ config: 'true',
52
+ val: true
53
+ },
54
+ records_per_page: {
55
+ config: '10',
56
+ val: 10
57
+ },
58
+ fake_total_count: {
59
+ config: 'true',
60
+ val: true
61
+ },
62
+ audit_controller_base: {
63
+ config: "'ModelsAuditor::AuditBaseController'",
64
+ val: 'ModelsAuditor::AuditBaseController'
65
+ },
66
+ respond_to_json_enabled: {
67
+ config: 'true',
68
+ val: true
69
+ },
70
+ respond_to_html_enabled: {
71
+ config: 'false',
72
+ val: false
73
+ },
74
+ json_response_data_key: {
75
+ config: "'entries'",
76
+ val: 'entries'
77
+ },
78
+ json_response_meta_key: {
79
+ config: "'meta'",
80
+ val: 'meta'
81
+ },
82
+ }
83
+ end
84
+
85
+ def method_missing(method_sym, *args)
86
+ method_name = method_sym.to_s
87
+ option_name = method_name.tr('=', '')
88
+ super if CONFIG_OPTIONS.exclude?(option_name.to_sym)
89
+ if method_name =~ /^.*=$/
90
+ raise ArgumentError.new('Incorrect number of arguments') if args.size != 1
91
+ instance_variable_set("@#{option_name}", args[0]) unless ModelsAuditor.configured?
92
+ else
93
+ var_name = "@#{option_name}"
94
+ instance_variable_defined?(var_name) ?
95
+ instance_variable_get(var_name) :
96
+ default[option_name.to_sym].try(:[], :val)
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ module_function
103
+
104
+ def configure(&block)
105
+ Rails.application.config.after_initialize do
106
+ block.call(config)
107
+ @configured = true
108
+ end
109
+ end
110
+
111
+ def configured?
112
+ @configured
113
+ end
114
+
115
+ def config
116
+ @config ||= Config.new
117
+ end
118
+ end