ez-settings 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +241 -0
  4. data/Rakefile +28 -0
  5. data/app/assets/config/ez_settings_manifest.js +2 -0
  6. data/app/assets/javascripts/ez/settings/application.js +13 -0
  7. data/app/assets/stylesheets/ez/settings/application.css +15 -0
  8. data/app/cells/ez/settings/base_cell.rb +95 -0
  9. data/app/cells/ez/settings/group/show.slim +17 -0
  10. data/app/cells/ez/settings/group_cell.rb +26 -0
  11. data/app/cells/ez/settings/index/show.slim +18 -0
  12. data/app/cells/ez/settings/index_cell.rb +4 -0
  13. data/app/cells/ez/settings/key/show.slim +4 -0
  14. data/app/cells/ez/settings/key_cell.rb +21 -0
  15. data/app/cells/ez/settings/nav/show.slim +9 -0
  16. data/app/cells/ez/settings/nav_cell.rb +13 -0
  17. data/app/controllers/ez/settings/application_controller.rb +11 -0
  18. data/app/controllers/ez/settings/settings_controller.rb +35 -0
  19. data/app/helpers/ez/settings/application_helper.rb +6 -0
  20. data/app/models/ez/settings/application_record.rb +7 -0
  21. data/config/routes.rb +8 -0
  22. data/lib/ez/settings.rb +10 -0
  23. data/lib/ez/settings/accessors.rb +31 -0
  24. data/lib/ez/settings/backend/file_system.rb +24 -0
  25. data/lib/ez/settings/config.rb +9 -0
  26. data/lib/ez/settings/engine.rb +13 -0
  27. data/lib/ez/settings/interface.rb +65 -0
  28. data/lib/ez/settings/interface/group.rb +44 -0
  29. data/lib/ez/settings/interface/key.rb +24 -0
  30. data/lib/ez/settings/request_dispatcher.rb +19 -0
  31. data/lib/ez/settings/routes.rb +7 -0
  32. data/lib/ez/settings/store.rb +80 -0
  33. data/lib/ez/settings/version.rb +5 -0
  34. data/lib/generators/ez/settings/install_generator.rb +1 -0
  35. metadata +259 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab92996758b0720c782a270a27937d4e62d2f84a
4
+ data.tar.gz: ccad41c32652e4f590a428b7e64268e0605b1ee3
5
+ SHA512:
6
+ metadata.gz: 1a07335a8448a2baf82d96f22c67855aa02c24cb8b2e8a2f9f14fa3a6304cf2b53448aa717670238253efef4aaa2515975787d6dba3cd31db8cfabab5940673d
7
+ data.tar.gz: a61b3c69ff16ebe41297861b902add48fe025da4e95e748610c0e89570244293a6aeb238a7fca05b25df37ff151f3f8eb50e8fbcbdae85c682df42049816e0c2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Volodya Sveredyuk
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # Ez::Settings
2
+ **Ez Settings** (read as "easy settings") - one of the [ez-engines](https://github.com/ez-engines) collection that helps easily add settings interface to your [Rails](http://rubyonrails.org/) application.
3
+
4
+ - Flexible tool with simple DSL
5
+ - Convetion over configuration principles.
6
+ - Depends on [ez-core](https://github.com/ez-engines/ez-core)
7
+
8
+ ## Installation
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'ez-settings'
13
+ ```
14
+
15
+ And then execute:
16
+ ```bash
17
+ $ bundle
18
+ ```
19
+
20
+ Or install it yourself as:
21
+ ```bash
22
+ $ gem install ez-settings
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ ### Initializer
28
+
29
+ Create initializer: `config/initializers/ez_settings.rb` (rails generator would be added in 0.2 version)
30
+
31
+ If you are boring to read all this stuff maybe this video motivates you:
32
+
33
+ [YouTube demonstrates how easy we integrated ez-settings for our pivorak-web-app only by ±50 lines of code](https://www.youtube.com/watch?v=TiX0QDHEaKA&feature=youtu.be)
34
+
35
+ ```ruby
36
+ # By default engine try to inherit from ApplicationController, but you could change this:
37
+ # Ez::Settings.config.base_controller = 'Admin::BaseController'
38
+ #
39
+ # Then you should define settings interfaces (you can create as much as you need)
40
+ # Ierachy is pretty simple: Interface -> Group -> Key
41
+ #
42
+ # Interface DSL allows you to do this very declaratively
43
+ #
44
+ #
45
+ app = Ez::Settings::Interface.define :app do # :app - interface name
46
+ group :general do # :general will be name of the group
47
+ key :app_title, default: -> { 'Main app title' } # :app_title will be as settings key name for store value in :general group for :app interface
48
+ end
49
+
50
+ # And so on...
51
+ group :admin do
52
+ key :app_title, default: -> { 'Admin app title' }
53
+ end
54
+
55
+ # If you want to see all power of the engine, add this showcase:
56
+ group :showcase do
57
+ key :string, default: -> { 'simple string' }
58
+ key :bool, type: :boolean, default: -> { true }
59
+ key :integer, type: :integer, default: -> { 777 }
60
+ key :select, type: :select, default: -> { 'foo' }, collection: %w(foo bar baz)
61
+ key :not_validate, required: false, presence: false
62
+ key :not_for_ui, required: false, ui: false
63
+ end
64
+ # Keys could have:
65
+ # :type (string by default), now ez-settings supports only: string, boolean, integer and select
66
+ # :default value (as callable objects)
67
+ # :required - be or not (all keys are required by default)
68
+ # :ui visible or not (all keys are UI visible by default)
69
+ end
70
+
71
+ # After defining settings interface groups/keys you need to configure it:
72
+ app.configure do |config|
73
+ # Backend adapter to store all settings (in current version only file system)
74
+ config.backend = Ez::Settings::Backend::FileSystem.new(Rails.root.join('config', 'settings.yml'))
75
+
76
+ # Default path for redirect in controller
77
+ config.default_path = '/admin/settings'
78
+
79
+ # Pass your custom css classes through css_map config
80
+ # Defaults would be merged with yours:
81
+ # config.custom_css_map = {
82
+ # nav_label: 'ez-settings-nav-label',
83
+ # nav_menu: 'ez-settings-nav-menu',
84
+ # nav_menu_item: 'ez-settings-nav-menu-item',
85
+ # overview_page_wrapper: 'ez-settings-overview',
86
+ # overview_page_section: 'ez-settings-overview-section',
87
+ # overview_page_section_header: 'ez-settings-overview-section-header',
88
+ # overview_page_section_content: 'ez-settings-overview-section-content',
89
+ # overview_page_section_content_key: 'ez-settings-overview-section-content-key',
90
+ # overview_page_section_content_value: 'ez-settings-overview-section-content-value',
91
+ # group_page_wrapper: 'ez-settings-group-wrapper',
92
+ # group_page_inner_wrapper: 'ez-settings-group-inner-wrapper',
93
+ # group_page_header: 'ez-settings-group-header',
94
+ # group_page_form_wrapper: 'ez-settings-group-form-wrapper',
95
+ # group_page_form_inner: 'ez-settings-group-form-inner',
96
+ # group_page_form_field_row: 'ez-settings-group-form-field-row',
97
+ # group_page_form_string_wrapper: 'ez-settings-group-form-string-wrapper',
98
+ # group_page_form_boolean_wrapper: 'ez-settings-group-form-boolean-wrapper',
99
+ # group_page_form_select_wrapper: 'ez-settings-group-form-select-wrapper',
100
+ # group_page_actions_wrapper: 'ez-settings-group-actions-wrapper',
101
+ # group_page_actions_wrapper: 'ez-settings-group-actions-wrapper',
102
+ # group_page_actions_save_button: 'ez-settings-group-actions-save-btn',
103
+ # group_page_actions_cancel_link: 'ez-settings-group-actions-cancel-link'
104
+ # }
105
+ #
106
+ # Highly recommend inspecting settings page DOM.
107
+ # You can find there a lot of interesting id/class stuff
108
+ #
109
+ # You even can define dynamic map for allows to decide which CSS class could be added
110
+ # `if` must contain callable object that receives controller as a first argument and dynamic element as second one:
111
+ #
112
+ # In this example, you easily could add 'active' CSS class if route end with some fragment:
113
+ # config.dynamic_css_map = {
114
+ # nav_menu_item: {
115
+ # css_class: 'active',
116
+ # if: ->(controller, path_fragment) { controller.request.path.end_with?(path_fragment.to_s) }
117
+ # }
118
+ # }
119
+ end
120
+
121
+ # EzSettings uses Ez::Registry from ez-core lib for storing all knowledges in one place.
122
+ # This place is registry records for :settings_interfaces registry
123
+ #
124
+ # Register `app` variable as settings interface
125
+ Ez::Registry.in(:settings_interfaces, by: PivorakWebApp) do |registry|
126
+ registry.add app
127
+ end
128
+ ```
129
+ ### Routes
130
+ `config/routes.rb`
131
+ ```ruby
132
+ Rails.application.routes.draw do
133
+ # your routes code before
134
+
135
+ # We recommend to hide settings into admin area
136
+ authenticate :user, ->(u) { u.admin? } do
137
+ namespace :admin do
138
+ # :app just interface name as you registred it in Ez::Registry
139
+ ez_settings_for :app
140
+ end
141
+ end
142
+
143
+ # more your routes after
144
+ end
145
+ ```
146
+
147
+ This routes setup allows you to have routes like:
148
+
149
+ `rake routes | grep settings`
150
+ ```
151
+ admin_ez_settings /admin/settings Ez::Settings::Engine {:interface=>:app}
152
+
153
+ root GET / ez/settings/settings#index
154
+ GET /:group(.:format) ez/settings/settings#show
155
+ PUT /:group(.:format) ez/settings/settings#update
156
+ ```
157
+
158
+ In case of example above you could route:
159
+ ```
160
+ GET /admin/settings/ - overview page for all :app interface settings
161
+ GET/PUT: /admin/settings/general - general group settings
162
+ GET/PUT: /admin/settings/admin - admin group settings
163
+ GET/PUT: /admin/settings/showcase - showcase group settings
164
+ ```
165
+
166
+ ### Settings accessors
167
+ In your app you could access to settings values through:
168
+ ```ruby
169
+ Ez::Settings[:all, :general, :app_title]
170
+ Ez::Settings[:all, :admin, :app_title]
171
+ Ez::Settings[:all, :showcase, :select]
172
+ ```
173
+ Just use template: `Ez::Settings['interface', 'group', 'key']`
174
+
175
+ In the case of missing interface/group/key you will receive one of exceptions:
176
+ ```
177
+ NotRegistredInterfaceError
178
+ NotRegistredGroupError
179
+ NotRegistredKeyError
180
+ ```
181
+ Be careful ;)
182
+
183
+ ### I18n
184
+
185
+ By default, all interfaces/groups/keys name would be humanized, but in fact, it tries to get translations from YAML first.
186
+
187
+ If you need, create locale file with this structure:
188
+
189
+ `config/locales/ez-settings.en.yml`
190
+ ```yaml
191
+ en:
192
+ ez_settings:
193
+ label: Ez Settings
194
+ interfaces:
195
+ app:
196
+ label: App Settings
197
+ actions:
198
+ save:
199
+ label: Save Settings
200
+ cancel:
201
+ label: Cancel Settings
202
+ groups:
203
+ general:
204
+ label: General
205
+ admin:
206
+ label: Admin
207
+ showcase:
208
+ label: Showcase
209
+ keys:
210
+ string:
211
+ label: String
212
+ bool:
213
+ label: Bool
214
+ integer:
215
+ label: Integer
216
+ select:
217
+ label: Select
218
+ not_validate:
219
+ label: Not Validate
220
+ not_for_ui:
221
+ label: Not For UI
222
+ ```
223
+
224
+ ## TODO
225
+ This features will be implemented in upcoming 0.2 and 0.3 releases:
226
+ - `rails g ez:settings:install` it creates `config/ez_settings.rb` and `config/locales/ez-settings.en.yml`
227
+ - Scoped settings (`:scope_id`, `:scope_type`)
228
+ - groups as defined methods for interface
229
+ - controller before actions as configured callbacks
230
+ - JSON API endpoints
231
+ - Allow to config endpoints
232
+ - Interface description (and show at UI)
233
+ - Groups description (and show at UI)
234
+ - Database storage as backend
235
+ - Redis storage as backend
236
+
237
+ ## Contributing
238
+ Fork => Fix => MR warmly welcomed!
239
+
240
+ ## License
241
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Ez::Settings'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require "rspec/core/rake_task"
25
+
26
+ RSpec::Core::RakeTask.new(:spec)
27
+
28
+ task :default => :spec
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/ez/settings .js
2
+ //= link_directory ../stylesheets/ez/settings .css
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,95 @@
1
+ module Ez::Settings
2
+ class BaseCell < Cell::ViewModel
3
+ include RequestDispatcher
4
+
5
+ self.view_paths = ["#{Ez::Settings::Engine.root}/app/cells"]
6
+
7
+ delegate :t, to: I18n
8
+ delegate :params, :request, to: :controller
9
+ delegate :dynamic_css_map, to: :'interface.config'
10
+
11
+ SCOPE = 'ez_settings'
12
+ LABEL = 'label'
13
+ INTERFACES = 'interfaces'
14
+ GROUPS = 'groups'
15
+ KEYS = 'keys'
16
+ ACTIONS = 'actions'
17
+ SAVE = 'save'
18
+ CANCEL = 'cancel'
19
+
20
+ def self.form
21
+ include ActionView::Helpers::FormHelper
22
+ include SimpleForm::ActionViewExtensions::FormHelper
23
+ include ActionView::RecordIdentifier
24
+ include ActionView::Helpers::FormOptionsHelper
25
+ end
26
+
27
+ def self.option(name, default: nil)
28
+ define_method name do
29
+ options[name]
30
+ end
31
+ end
32
+
33
+ def css_for(item, dynamic: nil)
34
+ return "ez-settings-defined-#{item}" unless css_map[item]
35
+ return css_map[item] unless dynamic
36
+
37
+ if dynamic_css_map.dig(item, :if)&.call(controller, dynamic)
38
+ dynamic_css_map.dig(item, :css_class) + ' ' + css_map[item]
39
+ else
40
+ css_map[item]
41
+ end
42
+ end
43
+
44
+ def css_map
45
+ {
46
+ nav_label: 'ez-settings-nav-label',
47
+ nav_menu: 'ez-settings-nav-menu',
48
+ nav_menu_item: 'ez-settings-nav-menu-item',
49
+ overview_page_wrapper: 'ez-settings-overview',
50
+ overview_page_section: 'ez-settings-overview-section',
51
+ overview_page_section_header: 'ez-settings-overview-section-header',
52
+ overview_page_section_content: 'ez-settings-overview-section-content',
53
+ overview_page_section_content_key: 'ez-settings-overview-section-content-key',
54
+ overview_page_section_content_value: 'ez-settings-overview-section-content-value',
55
+ group_page_wrapper: 'ez-settings-group-wrapper',
56
+ group_page_inner_wrapper: 'ez-settings-group-inner-wrapper',
57
+ group_page_header: 'ez-settings-group-header',
58
+ group_page_form_wrapper: 'ez-settings-group-form-wrapper',
59
+ group_page_form_inner: 'ez-settings-group-form-inner',
60
+ group_page_form_field_row: 'ez-settings-group-form-field-row',
61
+ group_page_form_string_wrapper: 'ez-settings-group-form-string-wrapper',
62
+ group_page_form_boolean_wrapper: 'ez-settings-group-form-boolean-wrapper',
63
+ group_page_form_select_wrapper: 'ez-settings-group-form-select-wrapper',
64
+ group_page_actions_wrapper: 'ez-settings-group-actions-wrapper',
65
+ group_page_actions_wrapper: 'ez-settings-group-actions-wrapper',
66
+ group_page_actions_save_button: 'ez-settings-group-actions-save-btn',
67
+ group_page_actions_cancel_link: 'ez-settings-group-actions-cancel-link',
68
+ }.merge(interface.config.custom_css_map)
69
+ end
70
+
71
+ def controller
72
+ context[:controller]
73
+ end
74
+
75
+ def group_link(group, options = {})
76
+ link_to i18n_group_label(group),
77
+ group_path(group),
78
+ class: css_for(:nav_menu_item, dynamic: "settings/#{group.name}")
79
+ end
80
+
81
+ def group_path(group)
82
+ "#{interface.config.default_path}/#{group.name}"
83
+ end
84
+
85
+ def i18n_group_label(group)
86
+ t(LABEL, scope: [SCOPE, INTERFACES, group.interface, GROUPS, group.name],
87
+ default: group.name.to_s.humanize)
88
+ end
89
+
90
+ def i18n_key_label(key)
91
+ t(LABEL, scope: [SCOPE, INTERFACES, key.interface, GROUPS, key.group, KEYS, key.name],
92
+ default: key.name.to_s.humanize)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,17 @@
1
+ #ez-settings
2
+ = cell 'ez/settings/nav'
3
+
4
+ div class="#{css_for :group_page_wrapper} ez-settings-group-#{group.name}"
5
+ div class=(css_for :group_page_inner_wrapper)
6
+ div class=(css_for :group_page_header)
7
+ = i18n_group_label(group)
8
+
9
+ div class=(css_for :group_page_form_wrapper)
10
+ = simple_form_for store, as: :settings, url: form_url, method: :put do |f|
11
+ div class=(css_for :group_page_form_inner)
12
+ - ui_keys.each do |key|
13
+ = cell 'ez/settings/key', key, form: f
14
+
15
+ div class=(css_for :group_page_actions_wrapper)
16
+ = save_button(f)
17
+ = cancel_link
@@ -0,0 +1,26 @@
1
+ module Ez::Settings
2
+ class GroupCell < BaseCell
3
+ form
4
+
5
+ property :ui_keys
6
+ option :store
7
+
8
+ def form_url
9
+ "#{interface.config.default_path}/#{model.name}"
10
+ end
11
+
12
+ def save_button(form)
13
+ form.button :submit,
14
+ t(LABEL, scope: [SCOPE, INTERFACES, model.interface, ACTIONS, SAVE],
15
+ default: 'Save'),
16
+ class: css_for(:group_page_actions_save_button)
17
+ end
18
+
19
+ def cancel_link
20
+ link_to t(LABEL, scope: [SCOPE, INTERFACES, model.interface, ACTIONS,CANCEL],
21
+ default: 'Cancel'),
22
+ interface.config.default_path,
23
+ class: css_for(:group_page_actions_cancel_link)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ #ez-settings
2
+ = cell 'ez/settings/nav'
3
+
4
+ div class="#{css_for :overview_page_wrapper}"
5
+ - interface.groups.each do |group|
6
+
7
+ div class="#{css_for :overview_page_section}"
8
+ div class="#{css_for :overview_page_section_header}"
9
+ = group_link(group)
10
+
11
+ - group.ui_keys.each do |key|
12
+ div class="#{css_for :overview_page_section_content}"
13
+
14
+ div class="#{css_for :overview_page_section_content_key}"
15
+ = i18n_key_label(key)
16
+
17
+ div class="#{css_for :overview_page_section_content_value}"
18
+ = group.store(interface.config.backend).send(key.name)
@@ -0,0 +1,4 @@
1
+ module Ez::Settings
2
+ class IndexCell < BaseCell
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ div class=(css_for :group_page_form_field_row)
2
+ div id=(css_for "#{interface.name}-#{model.group}-#{model.name}-wrapper".to_sym)
3
+ div class=(css_for "group_page_form_#{model.type}_wrapper".to_sym)
4
+ = form.input model.name, html_options
@@ -0,0 +1,21 @@
1
+ module Ez::Settings
2
+ class KeyCell < BaseCell
3
+ form
4
+
5
+ option :form
6
+
7
+ private
8
+
9
+ def html_options
10
+ {
11
+ label: i18n_key_label(model),
12
+ as: model.type,
13
+ collection: model.collection,
14
+ include_blank: model.default.present?,
15
+ required: model.required?,
16
+ checked_value: TRUE.to_s,
17
+ unchecked_value: FALSE.to_s
18
+ }.merge(model.options)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ .ez-settings-nav
2
+ div class=(css_for :nav_label)
3
+ = nav_label
4
+
5
+ div class=(css_for :nav_menu)
6
+ = settings_link
7
+
8
+ - interface.groups.each do |group|
9
+ = group_link(group)
@@ -0,0 +1,13 @@
1
+ module Ez::Settings
2
+ class NavCell < BaseCell
3
+ def nav_label
4
+ t(LABEL, scope: SCOPE, default: 'Settings')
5
+ end
6
+
7
+ def settings_link
8
+ link_to nav_label,
9
+ interface.config.default_path,
10
+ class: css_for(:nav_menu_item, dynamic: 'settings/')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Ez
2
+ module Settings
3
+ class ApplicationController < Ez::Settings.config.base_controller.constantize
4
+ protect_from_forgery with: :exception
5
+
6
+ def view(cell_name, *args)
7
+ render html: cell("ez/settings/#{cell_name}", *args), layout: true
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ module Ez
2
+ module Settings
3
+ class SettingsController < Ez::Settings::ApplicationController
4
+ include Ez::Settings::RequestDispatcher
5
+
6
+ def index
7
+ view :index
8
+ end
9
+
10
+ def show
11
+ render_group(group, group.store(backend))
12
+ end
13
+
14
+ def update
15
+ store = group.store(backend).update(params[:settings])
16
+
17
+ if store.valid?
18
+ redirect_to interface.config.default_path
19
+ else
20
+ render_group(group, store)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def render_group(group, store)
27
+ view :group, group, store: store
28
+ end
29
+
30
+ def backend
31
+ interface.config.backend
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ module Ez
2
+ module Settings
3
+ module ApplicationHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Ez
2
+ module Settings
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
6
+ end
7
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ Ez::Settings::Engine.routes.draw do
2
+ scope module: 'ez/settings' do
3
+ root to: 'settings#index'
4
+
5
+ get ':group', to: 'settings#show'
6
+ put ':group', to: 'settings#update'
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ require 'ez/settings/engine'
2
+
3
+ require 'ez/registry'
4
+
5
+ require 'ez/settings/interface'
6
+ require 'ez/settings/accessors'
7
+
8
+ module Ez::Settings
9
+ extend Accessors
10
+ end
@@ -0,0 +1,31 @@
1
+ module Ez::Settings
2
+ NotRegistredInterfaceError = Class.new(StandardError)
3
+ NotRegistredGroupError = Class.new(StandardError)
4
+ NotRegistredKeyError = Class.new(StandardError)
5
+
6
+ module Accessors
7
+ def [](interface_name, group_name, key_name)
8
+ interface = Ez::Registry.data(:settings_interfaces).find do |interface|
9
+ interface.name == interface_name.to_sym
10
+ end
11
+
12
+ unless interface
13
+ raise NotRegistredInterfaceError, "Interface #{interface_name} is not registred!"
14
+ end
15
+
16
+ group = interface.groups.find { |g| g.name == group_name.to_sym }
17
+
18
+ unless group
19
+ raise NotRegistredGroupError, "Group #{group_name} is not registred for #{interface_name} interface"
20
+ end
21
+
22
+ store = Ez::Settings::Store.new(group, interface.config.backend)
23
+
24
+ begin
25
+ store.send(key_name.to_sym)
26
+ rescue NoMethodError
27
+ raise NotRegistredKeyError, "Key #{key_name} is not registred for #{interface_name} interface, #{group_name} group"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require 'yaml'
2
+ require 'active_support/hash_with_indifferent_access'
3
+
4
+ module Ez::Settings
5
+ module Backend
6
+ class FileSystem
7
+ attr_reader :file
8
+
9
+ def initialize(file)
10
+ @file = file
11
+ end
12
+
13
+ def read
14
+ return {} unless File.exist?(file)
15
+
16
+ YAML.load_file(file).deep_symbolize_keys
17
+ end
18
+
19
+ def write(data)
20
+ File.write(file, read.merge(data).deep_stringify_keys.to_yaml)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ require 'ez/configurator'
2
+
3
+ module Ez::Settings
4
+ include Ez::Configurator
5
+
6
+ configure do |config|
7
+ config.base_controller = 'ApplicationController'
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ require 'cells-rails'
2
+ require 'cells-slim'
3
+ require 'simple_form'
4
+
5
+ require 'ez/settings/routes'
6
+ require 'ez/settings/request_dispatcher'
7
+
8
+ module Ez
9
+ module Settings
10
+ class Engine < ::Rails::Engine
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,65 @@
1
+ require_relative 'interface/group'
2
+ require_relative 'interface/key'
3
+ require_relative 'store'
4
+
5
+ require 'ez/configurator'
6
+ require 'ez/settings/backend/file_system'
7
+
8
+ module Ez::Settings
9
+ class Interface
10
+ include Ez::Configurator
11
+
12
+ configure do |config|
13
+ config.base_controller = 'ApplicationController'
14
+ config.default_path = '/settings'
15
+ config.backend = Backend::FileSystem.new('settings.yml')
16
+ config.custom_css_map = {}
17
+ config.dynamic_css_map = {}
18
+ end
19
+
20
+ def self.define(name, &block)
21
+ interface = new(name)
22
+ interface.instance_eval(&block)
23
+ interface
24
+ end
25
+
26
+ delegate :config, :configure, to: :class
27
+
28
+ attr_reader :name, :groups, :store
29
+
30
+ def initialize(name)
31
+ @name = name
32
+ @keys, @groups = [], []
33
+ end
34
+
35
+ def define(&block)
36
+ self.instance_eval(&block)
37
+ end
38
+
39
+ def group(name, &block)
40
+ find_or_initialize_group(name, &block)
41
+ end
42
+
43
+ def keys
44
+ groups.map(&:keys).flatten
45
+ end
46
+
47
+ private
48
+
49
+ def find_or_initialize_group(name, &block)
50
+ existing_group = groups.find { |g| g.name == name }
51
+
52
+ if existing_group
53
+ existing_group.instance_eval(&block)
54
+ else
55
+ add_group(Group.new(name, self.name, &block))
56
+ end
57
+ end
58
+
59
+ def add_group(group)
60
+ @groups << group
61
+
62
+ group
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'key'
2
+ require_relative '../store'
3
+
4
+ module Ez::Settings
5
+ class Interface
6
+ class Group
7
+ OverwriteKeyError = Class.new(StandardError)
8
+
9
+ attr_reader :name, :options, :keys, :store, :interface
10
+
11
+ def initialize(name, interface, options = {}, &block)
12
+ @name = name
13
+ @interface = interface
14
+ @options = options
15
+ @keys = []
16
+
17
+ instance_eval(&block)
18
+ end
19
+
20
+ def key(key_name, params = {})
21
+ prevent_key_rewrite!(key_name)
22
+
23
+ keys << Interface::Key.new(key_name, params.merge(group: name, interface: interface))
24
+ keys
25
+ end
26
+
27
+ def ui_keys
28
+ keys.select(&:ui?)
29
+ end
30
+
31
+ def store(backend)
32
+ Ez::Settings::Store.new(self, backend)
33
+ end
34
+
35
+ private
36
+
37
+ def prevent_key_rewrite!(key_name)
38
+ return unless keys.map(&:name).include?(key_name)
39
+
40
+ raise OverwriteKeyError, "Key #{key_name} already registred in #{name} group"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ module Ez::Settings
2
+ class Interface
3
+ class Key
4
+ attr_reader :name, :group, :interface, :type, :default,
5
+ :ui, :required, :collection, :options
6
+
7
+ def initialize(name, params)
8
+ @name = name
9
+ @group = params.fetch(:group)
10
+ @interface = params.fetch(:interface)
11
+ @type = params.fetch(:type, :string)
12
+ @default = params.fetch(:default, -> {}).call
13
+ @ui = params.fetch(:ui, true)
14
+ @required = params.fetch(:required, true)
15
+ @collection = params.fetch(:collection, [])
16
+ @options = params.fetch(:options, {})
17
+ end
18
+
19
+ # Alias all boolean-like options to predicates methods, please
20
+ alias_method :ui?, :ui
21
+ alias_method :required?, :required
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Ez
2
+ module Settings
3
+ module RequestDispatcher
4
+ # should implement :params
5
+
6
+ def interface
7
+ # TODO: add raise exception in nil
8
+ Ez::Registry.data(:settings_interfaces).find do |interface|
9
+ interface.name == params[:interface].to_sym
10
+ end
11
+ end
12
+
13
+ def group
14
+ # TODO: add raise exception if nil
15
+ interface.groups.find { |g| g.name == params[:group].to_sym }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ class ActionDispatch::Routing::Mapper
2
+ def ez_settings_for(interface)
3
+ defaults interface: interface do
4
+ mount Ez::Settings::Engine, at: '/settings', as: :ez_settings
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,80 @@
1
+ require 'active_model'
2
+
3
+ require 'ez/settings/config'
4
+
5
+ module Ez::Settings
6
+ class Store
7
+ delegate :keys, to: :group
8
+
9
+ attr_reader :group, :errors, :backend
10
+
11
+ def initialize(group, backend)
12
+ @group = group
13
+ @errors = ActiveModel::Errors.new(self)
14
+ @backend = backend
15
+
16
+ define_accessors
17
+
18
+ keys.each { |key| default_or_exists_value(data, key) }
19
+ end
20
+
21
+ def validate
22
+ @errors = ActiveModel::Errors.new(self)
23
+
24
+ group.keys.select(&:required?).each do |key|
25
+ errors.add(key.name, "can't be blank") if self.send(key.name).blank?
26
+ end
27
+ end
28
+
29
+ def valid?
30
+ errors.empty?
31
+ end
32
+
33
+ def invalid?
34
+ !valid?
35
+ end
36
+
37
+ def update(params)
38
+ params.each { |key, value| self.public_send("#{key}=", value) }
39
+
40
+ validate
41
+ return self unless errors.empty?
42
+
43
+ backend.write(schema)
44
+
45
+ self
46
+ end
47
+
48
+ def schema
49
+ {
50
+ group.name => group.keys.map(&:name).each_with_object({}) do |key_name, schema|
51
+ schema[key_name] = send(key_name)
52
+ end
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ def default_or_exists_value(from_data, key)
59
+ value = from_data[key.name].nil? ? key.default : from_data[key.name]
60
+
61
+ public_send("#{key.name}=", value)
62
+ end
63
+
64
+ def data
65
+ @data ||= backend.read[group.name] || {}
66
+ end
67
+
68
+ def define_accessors
69
+ group.keys.map(&:name).each do |name|
70
+ define_singleton_method(name) do
71
+ instance_variable_get("@#{name}")
72
+ end
73
+
74
+ define_singleton_method("#{name}=") do |value|
75
+ instance_variable_set("@#{name}", value)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ module Ez
2
+ module Settings
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ # TODO @VS [0.2]
metadata ADDED
@@ -0,0 +1,259 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ez-settings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Volodya Sveredyuk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ez-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 5.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: cells-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.0.8
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.8
55
+ - !ruby/object:Gem::Dependency
56
+ name: cells-slim
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.5
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.0.5
69
+ - !ruby/object:Gem::Dependency
70
+ name: simple_form
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.5.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.5.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: capybara
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: launchy
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: faker
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: guard-rspec
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ description: Easy settings engine for Rails app.
196
+ email:
197
+ - sveredyuk@gmail.com
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - MIT-LICENSE
203
+ - README.md
204
+ - Rakefile
205
+ - app/assets/config/ez_settings_manifest.js
206
+ - app/assets/javascripts/ez/settings/application.js
207
+ - app/assets/stylesheets/ez/settings/application.css
208
+ - app/cells/ez/settings/base_cell.rb
209
+ - app/cells/ez/settings/group/show.slim
210
+ - app/cells/ez/settings/group_cell.rb
211
+ - app/cells/ez/settings/index/show.slim
212
+ - app/cells/ez/settings/index_cell.rb
213
+ - app/cells/ez/settings/key/show.slim
214
+ - app/cells/ez/settings/key_cell.rb
215
+ - app/cells/ez/settings/nav/show.slim
216
+ - app/cells/ez/settings/nav_cell.rb
217
+ - app/controllers/ez/settings/application_controller.rb
218
+ - app/controllers/ez/settings/settings_controller.rb
219
+ - app/helpers/ez/settings/application_helper.rb
220
+ - app/models/ez/settings/application_record.rb
221
+ - config/routes.rb
222
+ - lib/ez/settings.rb
223
+ - lib/ez/settings/accessors.rb
224
+ - lib/ez/settings/backend/file_system.rb
225
+ - lib/ez/settings/config.rb
226
+ - lib/ez/settings/engine.rb
227
+ - lib/ez/settings/interface.rb
228
+ - lib/ez/settings/interface/group.rb
229
+ - lib/ez/settings/interface/key.rb
230
+ - lib/ez/settings/request_dispatcher.rb
231
+ - lib/ez/settings/routes.rb
232
+ - lib/ez/settings/store.rb
233
+ - lib/ez/settings/version.rb
234
+ - lib/generators/ez/settings/install_generator.rb
235
+ homepage: https://github.com/ez-engines
236
+ licenses:
237
+ - MIT
238
+ metadata: {}
239
+ post_install_message:
240
+ rdoc_options: []
241
+ require_paths:
242
+ - lib
243
+ required_ruby_version: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - ">="
246
+ - !ruby/object:Gem::Version
247
+ version: '0'
248
+ required_rubygems_version: !ruby/object:Gem::Requirement
249
+ requirements:
250
+ - - ">="
251
+ - !ruby/object:Gem::Version
252
+ version: '0'
253
+ requirements: []
254
+ rubyforge_project:
255
+ rubygems_version: 2.5.1
256
+ signing_key:
257
+ specification_version: 4
258
+ summary: Easy settings engine for Rails app.
259
+ test_files: []