models_auditor 0.1.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.
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