vario 0.4.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +61 -0
  4. data/Rakefile +37 -0
  5. data/app/controllers/vario/application_controller.rb +15 -0
  6. data/app/controllers/vario/levels_controller.rb +63 -0
  7. data/app/controllers/vario/settings_controller.rb +35 -0
  8. data/app/helpers/vario/application_helper.rb +13 -0
  9. data/app/jobs/vario/application_job.rb +4 -0
  10. data/app/mailers/vario/application_mailer.rb +6 -0
  11. data/app/models/vario/application_record.rb +5 -0
  12. data/app/models/vario/condition.rb +39 -0
  13. data/app/models/vario/level.rb +94 -0
  14. data/app/models/vario/setting.rb +188 -0
  15. data/app/views/vario/levels/_form.html.slim +53 -0
  16. data/app/views/vario/levels/create.js.erb +4 -0
  17. data/app/views/vario/levels/destroy.js.erb +4 -0
  18. data/app/views/vario/levels/update.js.erb +4 -0
  19. data/app/views/vario/settings/_levels.html.slim +22 -0
  20. data/app/views/vario/settings/_new_level.html.slim +7 -0
  21. data/app/views/vario/settings/_settings.html.slim +30 -0
  22. data/app/views/vario/settings/index.html.slim +28 -0
  23. data/app/views/vario/settings/levels.html.slim +1 -0
  24. data/app/views/vario/settings/show.html.slim +13 -0
  25. data/config/locales/en.yml +6 -0
  26. data/config/locales/nl.yml +6 -0
  27. data/config/routes.rb +10 -0
  28. data/db/migrate/20180628103728_create_vario_settings.rb +20 -0
  29. data/db/migrate/20180706130136_remove_fields_from_settings.rb +7 -0
  30. data/db/migrate/20180706155055_add_keys_to_setting.rb +5 -0
  31. data/db/migrate/20211027040546_unique_levels.rb +8 -0
  32. data/lib/vario/action_view_helpers.rb +31 -0
  33. data/lib/vario/active_record_helpers.rb +23 -0
  34. data/lib/vario/api.rb +148 -0
  35. data/lib/vario/config.rb +112 -0
  36. data/lib/vario/engine.rb +15 -0
  37. data/lib/vario/settable.rb +44 -0
  38. data/lib/vario/settings_reader.rb +35 -0
  39. data/lib/vario/version.rb +5 -0
  40. data/lib/vario.rb +30 -0
  41. metadata +194 -0
@@ -0,0 +1,22 @@
1
+ - context = {} if context.blank?
2
+
3
+ div data-id="#{setting.id}_levels"
4
+ .grid.grid-cols-12.gap-4 data-controller='draggable' data-draggable-handle='.draggable'
5
+ - setting.levels.each do |level|
6
+ .col-span-12 data-draggable-url="#{vario.move_setting_level_path(setting_id: setting.id, id: level.id)}" class= "#{"hidden" unless level.with_context_values?(context)}"
7
+ = render partial: 'vario/levels/form', locals: { setting: setting, level: level, context: context }
8
+
9
+ - function_name = "reload_#{setting.id.gsub('-', '')}"
10
+ javascript:
11
+ function #{{function_name}}() {
12
+ document.querySelector("[data-id='#{setting.id}_levels']").addEventListener('draggable.success', function(e) {
13
+ fetch("#{vario.levels_setting_path(setting)}")
14
+ .then(response => response.text())
15
+ .then(function(html) {
16
+ e.detail.element.outerHTML = html
17
+ #{{function_name}}()
18
+ })
19
+ })
20
+ }
21
+
22
+ #{{function_name}}()
@@ -0,0 +1,7 @@
1
+ - context = {} if context.blank?
2
+ - level = setting.new_level
3
+
4
+ div data-id="#{setting.id}_new_level"
5
+ .grid.grid-cols-12.gap-4
6
+ .col-span-12 class="#{"hidden" if level.conditions_not_for(context).size == 0 && setting.levels.find { |level| level.with_context_values?(context)}.present?}"
7
+ = render partial: 'vario/levels/form', locals: { setting: setting, level: level, label: 'New level', context: context, draggable: false }
@@ -0,0 +1,30 @@
1
+ - ui_card_title = 'Settings' if ui_card_title.blank?
2
+ - context = {} if context.blank?
3
+
4
+ = sts.card title: ui_card_title, icon: 'fad fa-slider' do |card|
5
+ - settings_with_contexts.sort_by { |setting| setting[:setting].name}.each do |setting|
6
+ - card.tab setting[:setting].name, title: setting[:setting].title, padding: true
7
+ .grid.grid-cols-12.gap-4
8
+ .col-span-12.prose
9
+ p= setting[:setting].description
10
+ .col-span-12
11
+ = render partial: 'vario/settings/levels', locals: setting
12
+ .col-span-12
13
+ = render partial: 'vario/settings/new_level', locals: setting
14
+
15
+ javascript:
16
+ $(document).on('ajax:error', function(event) {
17
+ window.alert('An error has occurred, your changes are likely not saved. Please try again.')
18
+ })
19
+
20
+ css:
21
+ .row.level {
22
+ min-height: 32px;
23
+ padding: 4px 0;
24
+ }
25
+ .row.level:nth-of-type(even) {
26
+ background-color: #f9f9f9;
27
+ }
28
+ .row.level:nth-of-type(odd) {
29
+ background-color: #f0f0f0;
30
+ }
@@ -0,0 +1,28 @@
1
+ = sts.card title: @title, icon: 'fad fa-sliders' do |card|
2
+ .grid.grid-cols-12.gap-4
3
+ .col-span-4
4
+
5
+ - @groups.each do |group|
6
+ - card.tab group[:name], padding: true
7
+ table.table
8
+ thead
9
+ th Name
10
+ th Levels
11
+ tbody
12
+ - @settings.for_group(group[:name]).each do |setting|
13
+ tr
14
+ td= link_to(setting.setting_name.humanize, setting_path(setting))
15
+ td= setting.levels.count
16
+
17
+ - if @settings.without_group.present?
18
+ - card.tab :ungrouped, padding: true
19
+ table.table
20
+ thead
21
+ th Name
22
+ th Levels
23
+ tbody
24
+ - @settings.without_group.each do |setting|
25
+ tr
26
+ td= link_to(setting.setting_name.humanize, setting_path(setting))
27
+ td= setting.levels.count
28
+
@@ -0,0 +1 @@
1
+ = render partial: 'vario/settings/levels', locals: { setting: @setting }
@@ -0,0 +1,13 @@
1
+ = sts.card title: @setting.title, icon: 'fad fa-sliders' do |card|
2
+
3
+ - card.action
4
+ = link_to("Back", settings_path(settable: @setting.settable.to_sgid(for: 'Vario').to_s), class: 'button')
5
+
6
+ .grid.grid-cols-12.gap-4
7
+ .col-span-12.prose
8
+ - if @setting.default.present?
9
+ p= "#{@setting.description} (default: #{[*@setting.default].join(', ')})"
10
+ - else
11
+ p= @setting.description
12
+ .col-span-12= render partial: 'vario/settings/levels', locals: { setting: @setting }
13
+ .col-span-12= render partial: 'vario/settings/new_level', locals: { setting: @setting }
@@ -0,0 +1,6 @@
1
+ en:
2
+ vario:
3
+ levels:
4
+ form:
5
+ save: Save
6
+ delete: Delete
@@ -0,0 +1,6 @@
1
+ nl:
2
+ vario:
3
+ levels:
4
+ form:
5
+ save: Opslaan
6
+ delete: Verwijder
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ Vario::Engine.routes.draw do
2
+ resources :settings do
3
+ get :levels, on: :member
4
+ resources :levels do
5
+ member do
6
+ patch :move
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ class CreateVarioSettings < ActiveRecord::Migration[5.2]
2
+ def change
3
+ enable_extension 'plpgsql'
4
+ enable_extension 'uuid-ossp'
5
+ enable_extension 'pgcrypto'
6
+
7
+ create_table :vario_settings, id: :uuid do |t|
8
+ t.references :settable, polymorphic: true, index: true, type: :uuid
9
+
10
+ t.string :name
11
+ t.string :category
12
+ t.string :description
13
+ t.string :keys, default: [], array: true
14
+
15
+ t.json :levels
16
+
17
+ t.timestamps
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ class RemoveFieldsFromSettings < ActiveRecord::Migration[5.2]
2
+ def change
3
+ remove_column :vario_settings, :category
4
+ remove_column :vario_settings, :description
5
+ remove_column :vario_settings, :keys
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class AddKeysToSetting < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :vario_settings, :keys, :string, array: true
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class UniqueLevels < ActiveRecord::Migration[6.1]
2
+ def up
3
+ Vario::Setting.all.each(&:uniq_levels!)
4
+ end
5
+
6
+ def down
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vario
4
+ module ActionViewHelpers
5
+ def render_vario_settings(*args)
6
+ settings_with_contexts = []
7
+
8
+ args = [args] unless args.first.is_a?(Array)
9
+ args.each do |argument_set|
10
+ settable = argument_set.first
11
+
12
+ if argument_set.last.is_a?(Hash)
13
+ setting_names = argument_set[1..-2]
14
+ context = argument_set.last
15
+ else
16
+ setting_names = argument_set[1..-1]
17
+ context = {}
18
+ end
19
+
20
+ Vario.config.pre_create_settings(settable)
21
+ settable.settings.where(name: setting_names).each do |setting|
22
+ settings_with_contexts << { setting: setting, context: context }
23
+ end
24
+ end
25
+
26
+ render partial: 'vario/settings/settings', locals: {
27
+ settings_with_contexts: settings_with_contexts
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vario
4
+ module ActiveRecordHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # provides settings_reader method to all models
9
+ include Vario::SettingsReader
10
+ end
11
+
12
+ class_methods do
13
+ def settable(options = {})
14
+ include Vario::Settable
15
+
16
+ configuration = { name: :settings }
17
+ configuration.update(options) if options.is_a?(Hash)
18
+
19
+ has_many configuration[:name], as: :settable, class_name: 'Vario::Setting'
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/vario/api.rb ADDED
@@ -0,0 +1,148 @@
1
+ # frozen_literal_string: true
2
+
3
+ require 'grape-swagger'
4
+
5
+ module Vario
6
+ class API < Grape::API
7
+ default_format :json
8
+ format :json
9
+
10
+ helpers do
11
+ def declared_params
12
+ declared(params, include_missing: false)
13
+ end
14
+
15
+ def settable
16
+ configuration[:settable]
17
+ end
18
+
19
+ def find_settable(id)
20
+ record = settable.find(id)
21
+ error!("#{settable.name} not found", 404) unless record.present?
22
+ record
23
+ end
24
+
25
+ def settable_settings
26
+ Vario.config.settable_settings[settable.name]
27
+ end
28
+
29
+ def settable_setting(setting)
30
+ settable_settings[setting]
31
+ end
32
+
33
+ def settable_setting_context(setting, context)
34
+ vario_setting = settable_setting(setting)
35
+ context.symbolize_keys!
36
+ vario_setting[:keys].each do |key|
37
+ context[key] ||= nil
38
+ end
39
+ context
40
+ end
41
+ end
42
+
43
+ given configuration[:settable] do
44
+ [*configuration[:include]].each do |m|
45
+ include m
46
+ end
47
+
48
+ Vario.config.settable_settings[configuration[:settable].name].each do |setting_name, setting_data|
49
+ before do
50
+ [*configuration[:before]].each do |method|
51
+ send(method) if method.is_a?(Symbol)
52
+ end
53
+ end if configuration[:before].present?
54
+
55
+ group_name, short_setting_name = setting_name.split('.', 2)
56
+
57
+ next unless Vario.config.show_group?(group_name)
58
+
59
+ route_param :id, type: String do
60
+ namespace configuration[:prefix] do
61
+ namespace group_name do
62
+ desc "Get setting value" do
63
+ detail "Get the setting value for #{setting_name}. Setting description: #{setting_data[:description]}"
64
+ end
65
+ params do
66
+ setting_data[:keys].each do |context_key|
67
+ optional context_key, type: String
68
+ end
69
+ end
70
+ scopes = [configuration[:oauth2].to_sym, "#{configuration[:oath2]}:read".to_sym] if configuration[:oauth2]
71
+ scopes ||= nil
72
+ get "/#{short_setting_name}", scopes: scopes do
73
+ record = find_settable(declared_params[:id])
74
+ context = settable_setting_context(setting_name, declared_params.reject { |key, value| key == 'id' }.to_h )
75
+
76
+ { setting: setting_name, value: record.setting(setting_name, context) }
77
+ end
78
+
79
+ setting_type =
80
+ case setting_data[:type]
81
+ when :array
82
+ Array[String]
83
+ when :boolean
84
+ Boolean
85
+ when :integer
86
+ Integer
87
+ else
88
+ String
89
+ end
90
+
91
+ desc "Add or change setting value" do
92
+ detail "Add or change setting value for #{setting_name}. Setting description: #{setting_data[:description]}"
93
+ end
94
+ params do
95
+ setting_data[:keys].each do |context_key|
96
+ optional context_key, type: String, documentation: { in: 'body' }
97
+ end
98
+ requires :value, type: setting_type, values: setting_data[:collection].present? ? setting_data[:collection].map { |item| item.last.to_s } : nil, documentation: { in: 'body' }
99
+ end
100
+ scopes = [configuration[:oauth2].to_sym, "#{configuration[:oath2]}:write".to_sym] if configuration[:oauth2]
101
+ scopes ||= nil
102
+ route [:post, :put], "/#{short_setting_name}", scopes: scopes do
103
+ record = find_settable(declared_params[:id])
104
+ context = settable_setting_context(setting_name, declared_params.reject { |key, value| %w[id value].include?(key) }.to_h )
105
+ vario_setting = record.settings.find_or_initialize_by(name: setting_name)
106
+ conditions_hash = context.values.compact.blank? ? {} : context
107
+ level = vario_setting.levels.find { |level| conditions_hash == level.conditions_hash }
108
+
109
+ if level.nil?
110
+ level = Level.new(vario_setting, { conditions: context })
111
+ vario_setting.levels.unshift level
112
+ end
113
+
114
+ level.value = declared_params[:value]
115
+ vario_setting.save!
116
+
117
+ { setting: setting_name, value: record.setting(setting_name, context) }
118
+ end
119
+
120
+ desc "Remove setting value" do
121
+ detail "Remove setting value for #{setting_name}. Setting description: #{setting_data[:description]}"
122
+ end
123
+ params do
124
+ setting_data[:keys].each do |context_key|
125
+ optional context_key, type: String
126
+ end
127
+ end
128
+ scopes = [configuration[:oauth2].to_sym, "#{configuration[:oath2]}:write".to_sym] if configuration[:oauth2]
129
+ scopes ||= nil
130
+ delete "/#{short_setting_name}", scopes: scopes do
131
+ record = find_settable(declared_params[:id])
132
+ context = settable_setting_context(setting_name, declared_params.reject { |key, value| key == 'id' }.to_h )
133
+ conditions_hash = context.values.compact.blank? ? {} : context
134
+
135
+ vario_setting = record.settings.find_or_initialize_by(name: setting_name)
136
+ vario_setting.levels.reject! { |level| conditions_hash == level.conditions_hash }
137
+ vario_setting.save!
138
+
139
+ { setting: setting_name, value: record.setting(setting_name, context) }
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
@@ -0,0 +1,112 @@
1
+ module Vario
2
+ class Config
3
+ attr_writer :logger, :current_settable
4
+ attr_reader :keys, :settable_types, :settable_settings
5
+ attr_accessor :base_controller, :admin_layout
6
+
7
+ def initialize
8
+ @logger = Logger.new(STDOUT)
9
+ @logger.level = Logger::INFO
10
+ @keys = {}
11
+ @base_controller = '::ApplicationController'
12
+ @settable_types = {}
13
+ @settable_settings = {}
14
+ @settable_type = nil
15
+ @current_settable = nil
16
+ @admin_layout = 'application'
17
+ @show_group_if = {}
18
+ end
19
+
20
+ # Config setters
21
+
22
+ def key(name, options)
23
+ options.symbolize_keys!
24
+ raise ArgumentError, 'name is required' unless options.keys.include?(:name)
25
+ raise ArgumentError, 'type is required' unless options.keys.include?(:type)
26
+ keys[name.to_s] = options
27
+ end
28
+
29
+ def show_group_if(name, block)
30
+ @show_group_if[name] = block
31
+ end
32
+
33
+ def show_group?(name)
34
+ if @show_group_if[name].is_a?(Proc)
35
+ @show_group_if[name].call
36
+ else
37
+ true
38
+ end
39
+ end
40
+
41
+ def setting(name, options = {})
42
+ settable_type = options.delete(:settable_type) || @settable_type
43
+ options[:type] ||= :string
44
+ options[:keys] ||= []
45
+ options[:default] ||= nil
46
+ options[:description] ||= "Configuration for #{name}, possible on: #{(options[:keys] + ['default']).map(&:to_s).join(', ')}"
47
+ settable_settings[settable_type] ||= {}
48
+ settable_settings[settable_type][name.to_s] = options
49
+ end
50
+
51
+ # If true, raise Vario::UnknownSetting if a unknown setting is retrieved, if false unknown settings return nil
52
+ def raise_on_undefined(value, options = {})
53
+ settable_type = options.delete(:settable_type) || @settable_type
54
+ settable_types[settable_type] ||= {}
55
+ settable_types[settable_type][:raise_on_undefined] = value
56
+ end
57
+
58
+ # If true, a new setting will be saved for settings that do not exist.
59
+ def create_on_request(value, options = {})
60
+ settable_type = options.delete(:settable_type) || @settable_type
61
+ settable_types[settable_type] ||= {}
62
+ settable_types[settable_type][:create_on_request] = value
63
+ settable_types[settable_type][:default_keys] = options[:with_keys]
64
+ end
65
+
66
+ def for_settable_type(settable_type)
67
+ @settable_type = settable_type
68
+ yield self
69
+ @settable_type = nil
70
+ end
71
+
72
+ # Config retrieval
73
+
74
+ # Config: logger [Object].
75
+ def logger
76
+ @logger.is_a?(Proc) ? instance_exec(&@logger) : @logger
77
+ end
78
+
79
+ def current_settable
80
+ @current_settable.is_a?(Proc) ? instance_exec(&@current_settable) : @current_settable
81
+ end
82
+
83
+ def settable_setting(settable_type, name)
84
+ settable_settings.dig(settable_type, name)
85
+ end
86
+
87
+ def key_data_for(key)
88
+ keys[key.to_s] || { name: key, type: :string, description: 'Not configured in initializer' }
89
+ end
90
+
91
+ def create_on_request?(settable_type)
92
+ settable_types.dig(settable_type, :create_on_request)
93
+ end
94
+
95
+ def raise_on_undefined?(settable_type)
96
+ settable_types.dig(settable_type, :raise_on_undefined)
97
+ end
98
+
99
+ def default_keys?(settable_type)
100
+ settable_types.dig(settable_type, :default_keys) || []
101
+ end
102
+
103
+ def pre_create_settings(settable)
104
+ return unless settable_settings[settable.class.name]
105
+
106
+ settable_settings[settable.class.name].keys.each do |key|
107
+ s = settable.settings.find_or_initialize_by(name: key)
108
+ s.save unless s.persisted?
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,15 @@
1
+ module Vario
2
+ class Engine < ::Rails::Engine
3
+ config.autoload_paths << File.expand_path("../../lib", __dir__)
4
+
5
+ isolate_namespace Vario
6
+
7
+ initializer :append_migrations do |app|
8
+ unless app.root.to_s.match root.to_s
9
+ config.paths['db/migrate'].expanded.each do |expanded_path|
10
+ app.config.paths['db/migrate'] << expanded_path
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vario
4
+ module Settable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def setting(name, context = {})
9
+ name = name.to_s
10
+ context.symbolize_keys!
11
+ @vario_setting_cache ||= {}
12
+ @vario_setting_cache[name] ||= settings.find_or_initialize_by(name: name)
13
+ @vario_setting_cache[name]&.value_for(context)
14
+ end
15
+
16
+ def settings_hash(name, context = {})
17
+ name = name.to_s
18
+ context.symbolize_keys!
19
+ raise ArgumentError, 'Cannot request hash with a default' if context.key?(:default)
20
+
21
+ settings.where('name ILIKE ?', "#{name}.%").map do |vario_setting|
22
+ [vario_setting.name.gsub("#{name}.", ''), vario_setting.value_for(context)]
23
+ end.to_h.with_indifferent_access
24
+ end
25
+
26
+ def settings_save_unsaved
27
+ return unless @vario_setting_cache
28
+
29
+ @vario_setting_cache.each do |_key, vario_setting|
30
+ vario_setting.save unless vario_setting.persisted?
31
+ end
32
+ end
33
+
34
+ def settings_prepopulate_cache
35
+ return if @vario_setting_cache
36
+
37
+ @vario_setting_cache ||= {}
38
+ settings.map do |vario_setting|
39
+ @vario_setting_cache[vario_setting.name] ||= vario_setting
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vario
4
+ # Make vario settings accessible as readers on the object
5
+ module SettingsReader
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def settings_reader(klass, settings)
10
+ settings.each do |setting|
11
+ vario_setting = Vario.config.settable_settings[klass.name.to_s][setting]
12
+
13
+ method_name = setting.split('.').last
14
+ method_name += '?' if vario_setting[:type] == :boolean
15
+
16
+ define_method(method_name.to_sym) do
17
+ result = nil
18
+
19
+ if respond_to?(:settings) && self.settings.respond_to?(:dig)
20
+ result = self.settings.dig(*setting.split('.'))
21
+ end
22
+
23
+ if result.nil?
24
+ context = vario_setting[:keys].map { |key| [key, send(key)] }
25
+ result = self.setting(setting, context.to_h) if respond_to?(:settings) && self.class.name == klass.name
26
+ result = send(klass.name.underscore.to_sym)&.setting(setting, context.to_h) if result.nil? && self.respond_to?(klass.name.underscore.to_sym)
27
+ end
28
+
29
+ result
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vario
4
+ VERSION = "0.4.12"
5
+ end
data/lib/vario.rb ADDED
@@ -0,0 +1,30 @@
1
+ require_relative 'vario/engine'
2
+ require_relative 'vario/config'
3
+ require_relative 'vario/settable'
4
+ require_relative 'vario/settings_reader'
5
+ require_relative 'vario/action_view_helpers'
6
+ require_relative 'vario/active_record_helpers'
7
+ require_relative 'vario/api'
8
+
9
+ module Vario
10
+ class Error < StandardError; end
11
+ class UnknownSetting < Error; end
12
+
13
+ class << self
14
+ attr_reader :config
15
+
16
+ def setup
17
+ @config = Vario::Config.new
18
+ yield config
19
+ end
20
+ end
21
+
22
+ # Include helpers
23
+ ActiveSupport.on_load(:active_record) do
24
+ include ActiveRecordHelpers
25
+ end
26
+
27
+ ActiveSupport.on_load(:action_view) do
28
+ include ActionViewHelpers
29
+ end
30
+ end