ez-settings 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 (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: []