preflex 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/controllers/concerns/preflex/set_current_context.rb +13 -0
- data/app/controllers/preflex/application_controller.rb +4 -0
- data/app/controllers/preflex/preferences_controller.rb +16 -0
- data/app/helpers/preflex/preferences_helper.rb +78 -0
- data/app/models/preflex/application_record.rb +5 -0
- data/app/models/preflex/current.rb +6 -0
- data/app/models/preflex/preference.rb +73 -0
- data/app/models/preflex/preference_cache.rb +20 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20240211162939_create_preflex_preferences.rb +11 -0
- data/lib/preflex/engine.rb +12 -0
- data/lib/preflex/version.rb +3 -0
- data/lib/preflex.rb +15 -0
- data/lib/tasks/preflex_tasks.rake +4 -0
- data/lib/template/install.rb +47 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b7bfaa76370b12382b4e911acf713b5e29432f0989301039c3b42ed96a2efe4d
|
4
|
+
data.tar.gz: 9d76ddac8cb8753d2f86dac792168337fe8e69c2062c087feafde3d16859f0ab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b21919439815819dd2f77c46d36c234a6534d009addfba13a37736a2ea683b92b18d135e6f07c7b739f7d0cbbd0fd8d99559c625ae7a236a7fa9c4d11e94f919
|
7
|
+
data.tar.gz: 471c5d55662b128ee78d715eac741f8fb8186cd0d1e95f16da05869e4df1274aa977b00341122b8151d092edd4005534c0d03c4833ef9e139de20bb4d403f7aa
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Owais
|
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,28 @@
|
|
1
|
+
# Preflex
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem "preflex"
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install preflex
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Preflex
|
2
|
+
class PreferencesController < ApplicationController
|
3
|
+
# POST /preferences
|
4
|
+
# Params:
|
5
|
+
# klass
|
6
|
+
# name
|
7
|
+
# value
|
8
|
+
def update
|
9
|
+
klass = params[:klass].constantize
|
10
|
+
raise "Expected #{params[:klass]} to be a subclass of Preflex::Preference" unless klass < Preflex::Preference
|
11
|
+
|
12
|
+
klass.set(params[:name], params[:value])
|
13
|
+
head :ok
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Preflex
|
2
|
+
module PreferencesHelper
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# PREFLEX_PREFERENCE_JS = File.read(Preferences::Preflex::Engine.root.join("app", "static", "preflex_preference.js"))
|
6
|
+
def script_tag(*preference_klasses)
|
7
|
+
return ''.html_safe if preference_klasses.empty?
|
8
|
+
|
9
|
+
base = <<~JS
|
10
|
+
const CSRF_TOKEN = "#{Preflex::Current.controller_instance.send(:form_authenticity_token)}"
|
11
|
+
const UPDATE_PATH = "#{Preflex::Engine.routes.url_helpers.preferences_path}"
|
12
|
+
class PreflexPreference {
|
13
|
+
constructor(klass, data) {
|
14
|
+
this.klass = klass
|
15
|
+
this.localStorageKey = `PreflexPreference-${klass}`
|
16
|
+
|
17
|
+
this.data = data
|
18
|
+
this.dataLocal = JSON.parse(localStorage.getItem(this.localStorageKey) || '{}')
|
19
|
+
}
|
20
|
+
|
21
|
+
get(name) {
|
22
|
+
this.ensurePreferenceExists(name)
|
23
|
+
|
24
|
+
const fromServer = this.data[name]
|
25
|
+
const fromServerUpdatedAt = this.data[`${name}_updated_at_epoch`] || 0
|
26
|
+
|
27
|
+
const fromLocal = this.dataLocal[name]
|
28
|
+
const fromLocalUpdatedAt = this.dataLocal[`${name}_updated_at_epoch`] || 0
|
29
|
+
|
30
|
+
if(fromLocalUpdatedAt > fromServerUpdatedAt) {
|
31
|
+
this.updateOnServer(name, fromLocal)
|
32
|
+
return fromLocal
|
33
|
+
}
|
34
|
+
|
35
|
+
return fromServer
|
36
|
+
}
|
37
|
+
|
38
|
+
set(name, value) {
|
39
|
+
this.ensurePreferenceExists(name)
|
40
|
+
|
41
|
+
this.dataLocal[name] = value
|
42
|
+
this.dataLocal[`${name}_updated_at_epoch`] = Date.now()
|
43
|
+
|
44
|
+
localStorage.setItem(this.localStorageKey, JSON.stringify(this.dataLocal))
|
45
|
+
this.updateOnServer(name, value)
|
46
|
+
document.dispatchEvent(new CustomEvent('preflex:preference-updated', { detail: { name, value } }))
|
47
|
+
}
|
48
|
+
|
49
|
+
updateOnServer(name, value) {
|
50
|
+
fetch(UPDATE_PATH, {
|
51
|
+
method: 'POST',
|
52
|
+
headers: {
|
53
|
+
"Content-Type": "application/json",
|
54
|
+
"X-CSRF-TOKEN": CSRF_TOKEN
|
55
|
+
},
|
56
|
+
body: JSON.stringify({ klass: this.klass, name, value })
|
57
|
+
})
|
58
|
+
}
|
59
|
+
|
60
|
+
ensurePreferenceExists(name) {
|
61
|
+
if(!this.data.hasOwnProperty(name)) {
|
62
|
+
throw new Error(`Preference ${name} was not defined.`)
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
JS
|
67
|
+
|
68
|
+
js = [base]
|
69
|
+
js += preference_klasses.map do |klass|
|
70
|
+
raise 'Expected #{klass} to be a sub-class of Preflex::Preference' unless klass < Preflex::Preference
|
71
|
+
|
72
|
+
"window['#{klass.name}'] = new PreflexPreference('#{klass.name}', #{klass.current.data_for_js});"
|
73
|
+
end
|
74
|
+
|
75
|
+
"<script>#{js.join("\n")}</script>".html_safe
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Preflex
|
2
|
+
class Preference < ApplicationRecord
|
3
|
+
self.store_attribute_unset_values_fallback_to_default = true
|
4
|
+
|
5
|
+
store :data, coder: JSON
|
6
|
+
|
7
|
+
def get(name)
|
8
|
+
name = name.to_sym
|
9
|
+
self.class.ensure_preference_exists(name)
|
10
|
+
|
11
|
+
send(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(name, value)
|
15
|
+
name = name.to_sym
|
16
|
+
self.class.ensure_preference_exists(name)
|
17
|
+
|
18
|
+
send("#{name}=", value)
|
19
|
+
send("#{name}_updated_at_epoch=", (Time.current.to_f * 1000).round)
|
20
|
+
save!
|
21
|
+
end
|
22
|
+
|
23
|
+
def data_for_js
|
24
|
+
data.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.preference(name, type, default: nil, private: false)
|
28
|
+
name = name.to_sym
|
29
|
+
|
30
|
+
@preferences ||= Set.new
|
31
|
+
@preferences.add(name)
|
32
|
+
|
33
|
+
store_attribute(:data, name, type, default: default)
|
34
|
+
store_attribute(:data, "#{name}_updated_at_epoch".to_sym, :integer, default: 0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.current_owner(controller_instance)
|
38
|
+
raise '
|
39
|
+
Please define a class method called owner that returns the owner of this preference.
|
40
|
+
You can use `controller_instance` to refer to things like current_user/etc.
|
41
|
+
You can return either:
|
42
|
+
an ActiveRecord object persisted in the DB
|
43
|
+
or any object that responds to `id` - returning a unique id
|
44
|
+
or a string that uniquely identifies the owner
|
45
|
+
Example:
|
46
|
+
def self.current_owner(controller_instance)
|
47
|
+
controller_instance.current_user
|
48
|
+
end
|
49
|
+
'
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.for(owner)
|
53
|
+
owner = "#{owner.class.name}-#{owner.id}" if owner.respond_to?(:id)
|
54
|
+
PreferenceCache.for(self, owner.to_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.current
|
58
|
+
self.for(current_owner(Preflex::Current.controller_instance))
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.get(name)
|
62
|
+
current.get(name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.set(name, value)
|
66
|
+
current.set(name, value)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.ensure_preference_exists(name)
|
70
|
+
raise "Preference #{name} was not defined. Make sure you define it (e.g. `preference :#{name}, :integer, default: 10`)" unless @preferences&.include?(name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Preflex
|
2
|
+
class PreferenceCache
|
3
|
+
def initialize
|
4
|
+
@cache = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def for(klass, owner)
|
8
|
+
@cache[klass] ||= {}
|
9
|
+
@cache[klass][owner] ||= klass.find_or_initialize_by(owner: owner)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.for(klass, owner)
|
13
|
+
self.current.for(klass, owner)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.current
|
17
|
+
Preflex::Current.preference_cache ||= new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreatePreflexPreferences < ActiveRecord::Migration[7.1]
|
2
|
+
def change
|
3
|
+
create_table :preflex_preferences do |t|
|
4
|
+
t.string :type
|
5
|
+
t.string :owner, limit: 500
|
6
|
+
t.text :data, limit: 16.megabytes - 1
|
7
|
+
t.index ['type', 'owner']
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'store_attribute'
|
2
|
+
|
3
|
+
module Preflex
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace Preflex
|
6
|
+
|
7
|
+
config.to_prepare do
|
8
|
+
Preflex.base_controller_class.include(Preflex::SetCurrentContext)
|
9
|
+
Preflex.base_controller_class_for_update.include(Preflex::SetCurrentContext) unless Preflex.base_controller_class_for_update < Preflex.base_controller_class
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/preflex.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "preflex/version"
|
2
|
+
require "preflex/engine"
|
3
|
+
|
4
|
+
module Preflex
|
5
|
+
mattr_accessor :base_controller_class
|
6
|
+
mattr_accessor :base_controller_class_for_update
|
7
|
+
|
8
|
+
def self.base_controller_class
|
9
|
+
(@@base_controller_class || '::ApplicationController').constantize
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.base_controller_class_for_update
|
13
|
+
@@base_controller_class_for_update&.constantize || self.base_controller_class
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
say "🗄️ Running migration to create a table to store preferences..."
|
2
|
+
rails_command("preflex:install:migrations")
|
3
|
+
rails_command("db:migrate")
|
4
|
+
|
5
|
+
say "🛠️ Setting up routes and a dummy initializer file"
|
6
|
+
route "mount Preflex::Engine => '/preflex'"
|
7
|
+
initializer 'preflex.rb', <<~RUBY
|
8
|
+
# If you have a custom base controller, uncomment the below line and set it here.
|
9
|
+
#Preflex.base_controller_class = '::ApplicationController'
|
10
|
+
|
11
|
+
# If you want to make it so that the controller that handles requests to update preferences(from the client-side) inherits from a different base controller, uncomment the below line and set it here.
|
12
|
+
#Preflex.base_controller_class_for_update = '::ApplicationController'
|
13
|
+
RUBY
|
14
|
+
|
15
|
+
create_example_preference = !no?("✨ Do you want to set up an example preference class? (Y/n)")
|
16
|
+
|
17
|
+
file_name = nil
|
18
|
+
if create_example_preference
|
19
|
+
name = ask("✨ What do you want to call this class? (E.g UserPreference, FeatureFlag, CustomerSettings, etc.)").presence || 'CustomerSettings'
|
20
|
+
file_name = "app/models/#{name.underscore}.rb"
|
21
|
+
file file_name, <<~RUBY
|
22
|
+
class #{name} < Preflex::Preference
|
23
|
+
preference :autoplay, :boolean, default: true
|
24
|
+
preference :volume, :integer, default: 75
|
25
|
+
preference :title, :string, default: 'Mr.'
|
26
|
+
preference :favorite_colors, :json, default: ["red", "blue"]
|
27
|
+
|
28
|
+
def self.current_owner(controller_instance)
|
29
|
+
# You'd want to modify this to return the correct owner (whatever that is for you - can be an account/user/session/customer/etc - whatever you call it)
|
30
|
+
controller_instance.current_user
|
31
|
+
end
|
32
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
|
36
|
+
say ""
|
37
|
+
say ""
|
38
|
+
say ""
|
39
|
+
say "✅ ✅ ✅ All done ✅ ✅ ✅"
|
40
|
+
say ""
|
41
|
+
say "You might want to take a look at #{file_name} and update it's `current_owner` method definition." if create_example_preference
|
42
|
+
say "" if create_example_preference
|
43
|
+
say "You can also change the base controller that preflex uses if warranted. Take a look at config/initializers/preflex.rb"
|
44
|
+
say ""
|
45
|
+
say "If you'd like to easily read and write preferences from the client side, just add `Preflex::PreferencesHelper.script_tag(*AllThePreferenceClassesYouHave)` (e.g `Preflex::PreferencesHelper.script_tag(UserPreference, CustomerSettings)`) to the head tag in your layout file (e.g app/views/layouts/application.html.erb) and read the docs at https://github.com/owaiswiz/preflex"
|
46
|
+
|
47
|
+
def run_bundle; end;
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: preflex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Owais
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: store_attribute
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.0
|
41
|
+
description: 'A simple but powerful Rails engine for storing preferences, feature
|
42
|
+
flags, etc. With support for reading/writing values client-side! '
|
43
|
+
email:
|
44
|
+
- owaiswiz@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- MIT-LICENSE
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- app/controllers/concerns/preflex/set_current_context.rb
|
53
|
+
- app/controllers/preflex/application_controller.rb
|
54
|
+
- app/controllers/preflex/preferences_controller.rb
|
55
|
+
- app/helpers/preflex/preferences_helper.rb
|
56
|
+
- app/models/preflex/application_record.rb
|
57
|
+
- app/models/preflex/current.rb
|
58
|
+
- app/models/preflex/preference.rb
|
59
|
+
- app/models/preflex/preference_cache.rb
|
60
|
+
- config/routes.rb
|
61
|
+
- db/migrate/20240211162939_create_preflex_preferences.rb
|
62
|
+
- lib/preflex.rb
|
63
|
+
- lib/preflex/engine.rb
|
64
|
+
- lib/preflex/version.rb
|
65
|
+
- lib/tasks/preflex_tasks.rake
|
66
|
+
- lib/template/install.rb
|
67
|
+
homepage: https://github.com/owaiswiz/preflex
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata:
|
71
|
+
homepage_uri: https://github.com/owaiswiz/preflex
|
72
|
+
source_code_uri: https://github.com/owaiswiz/preflex
|
73
|
+
changelog_uri: https://github.com/owaiswiz/preflex/releases
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubygems_version: 3.4.10
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: A simple but powerful Rails engine for storing preferences, feature flags,
|
93
|
+
etc. With support for reading/writing values client-side!
|
94
|
+
test_files: []
|