lightningff 0.0.2
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +91 -0
- data/Rakefile +18 -0
- data/app/assets/config/lightning_manifest.js +1 -0
- data/app/assets/stylesheets/lightning/application.css +37 -0
- data/app/controllers/lightning/application_controller.rb +6 -0
- data/app/controllers/lightning/feature_opt_ins_controller.rb +32 -0
- data/app/controllers/lightning/features_controller.rb +63 -0
- data/app/helpers/lightning/application_helper.rb +4 -0
- data/app/helpers/lightning/flaggable.rb +7 -0
- data/app/jobs/lightning/application_job.rb +4 -0
- data/app/mailers/lightning/application_mailer.rb +6 -0
- data/app/models/lightning/application_record.rb +5 -0
- data/app/models/lightning/feature.rb +12 -0
- data/app/models/lightning/feature_opt_in.rb +16 -0
- data/app/views/layouts/lightning/application.html.erb +17 -0
- data/app/views/lightning/feature_opt_ins/_feature_opt_in.html.erb +3 -0
- data/app/views/lightning/feature_opt_ins/_form.html.erb +12 -0
- data/app/views/lightning/features/_form.html.erb +37 -0
- data/app/views/lightning/features/edit.html.erb +7 -0
- data/app/views/lightning/features/index.html.erb +31 -0
- data/app/views/lightning/features/new.html.erb +6 -0
- data/app/views/lightning/features/show.html.erb +39 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20210513140212_create_feature_flag_framework.rb +21 -0
- data/lib/lightning.rb +17 -0
- data/lib/lightning/api.rb +56 -0
- data/lib/lightning/engine.rb +13 -0
- data/lib/lightning/errors.rb +9 -0
- data/lib/lightning/version.rb +3 -0
- data/lib/tasks/lightning_tasks.rake +51 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 88d8c73c6bac9eb684d13681f741c8c470388aaa6f704d3f7ea4acbd570d810f
|
4
|
+
data.tar.gz: 9519b22659caf09331f314d99d452040788ddfeb07aaa928e803d7cdb3098673
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fa2041ef7c649e5516f977e03a4a3cc1f1df35a0c735e6263985a99728c0635612ab6bbe0bb1975693ca621d4272b46a72795d351b75ce64311190c31391fb14
|
7
|
+
data.tar.gz: 66852cdcdeb487ced36b21ed29c93e352c99277d8df384a211f12f9afe6214a165a87dbd819ddc939589e01242931056c6a4d4741c9f11ca86e7ea02beadb774
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2021 Ramruthwick Pathireddy
|
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,91 @@
|
|
1
|
+
# ⚡️ Lightning  [](https://badge.fury.io/rb/lightningff)
|
2
|
+
An end-to-end feature flagging system that can be setup in <1 minute.
|
3
|
+
|
4
|
+
Lightning is a rails gem you can install into your Rails application to get both console and UI access to manage feature flags. Lightning saves you time to avoid building an in-house solution.
|
5
|
+
|
6
|
+
## Install
|
7
|
+
|
8
|
+
Add the following link to Gemfile
|
9
|
+
```ruby
|
10
|
+
gem 'lightningff', require: 'lightning'
|
11
|
+
```
|
12
|
+
and run `bundle install`. _Note: You might need to run `bundle update` to resolve any incompatible issues with rails._
|
13
|
+
|
14
|
+
Set up feature flag migrations by running the following lines
|
15
|
+
```bash
|
16
|
+
bin/rails lightning:install:migrations
|
17
|
+
bin/rails db:migrate SCOPE=lightning
|
18
|
+
```
|
19
|
+
|
20
|
+
Create `config/initializers/lightning.rb` and set your flaggable entities
|
21
|
+
```ruby
|
22
|
+
Lightning.flaggable_entities = ["User", "Workspace"]
|
23
|
+
```
|
24
|
+
For each flaggable model, add `include Lightning::Flaggable`.
|
25
|
+
```ruby
|
26
|
+
class User < ApplicationRecord
|
27
|
+
include Lightning::Flaggable
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
To check feature availability for entity: `Lightning::Feature.enabled?(user, <feature_key>)`
|
32
|
+
|
33
|
+
### UI setup
|
34
|
+
|
35
|
+
Mount engine in `routes.rb` file
|
36
|
+
```ruby
|
37
|
+
Rails.application.routes.draw do
|
38
|
+
mount Lightning::Engine => "/lightning"
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
## Rails Console API Usage
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
### Feature Management
|
46
|
+
# Create feature (state is disabled)
|
47
|
+
Lightning.create!('homepage_v2', 'New homepage with better logo')
|
48
|
+
# Get feature by key
|
49
|
+
Lightning.get('homepage_v2')
|
50
|
+
# List all features
|
51
|
+
Lightning.list
|
52
|
+
# Update feature state/description
|
53
|
+
Lightning.update('homepage_v2', {state: 'enabled_per_entity', description: 'Homepage with new nav'})
|
54
|
+
# Delete feature
|
55
|
+
Lightning.delete('homepage_v2')
|
56
|
+
|
57
|
+
### Feature Permissions Management
|
58
|
+
u = User.create(name: 'Dummy user')
|
59
|
+
# Add entity to feature
|
60
|
+
Lightning.enable_entity('homepage_v2', u)
|
61
|
+
# List entities for feature
|
62
|
+
Lightning.entities
|
63
|
+
# Remove entity to feature
|
64
|
+
Lightning.remove_entity('homepage_v2', u)
|
65
|
+
# Check if feature is enabled for entity
|
66
|
+
Lightning.enabled?(u, 'homepage_v2')
|
67
|
+
```
|
68
|
+
|
69
|
+
## Advanced Configuration
|
70
|
+
|
71
|
+
Lightning makes is super easy to configure how data is represented through the UI.
|
72
|
+
|
73
|
+
## Running Tests
|
74
|
+
|
75
|
+
To run the test suite, pull the repo locally and run `rspec spec/`. All tests live in the **spec/** folder.
|
76
|
+
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
You need to have ruby version 2.7.3 installed locally and rails version 6.1.3.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
cd test/dummy
|
84
|
+
bundle install
|
85
|
+
bin/rails db:create
|
86
|
+
bin/rails db:migrate
|
87
|
+
bin/rails server
|
88
|
+
```
|
89
|
+
|
90
|
+
## License
|
91
|
+
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,18 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
4
|
+
load "rails/tasks/engine.rake"
|
5
|
+
|
6
|
+
load "rails/tasks/statistics.rake"
|
7
|
+
|
8
|
+
require "bundler/gem_tasks"
|
9
|
+
|
10
|
+
require "rake/testtask"
|
11
|
+
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = false
|
16
|
+
end
|
17
|
+
|
18
|
+
task default: :test
|
@@ -0,0 +1 @@
|
|
1
|
+
//= link_directory ../stylesheets/lightning .css
|
@@ -0,0 +1,37 @@
|
|
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
|
+
*/
|
16
|
+
|
17
|
+
html {
|
18
|
+
font-size: 14px;
|
19
|
+
}
|
20
|
+
|
21
|
+
.fw-monospace {
|
22
|
+
font-family: monospace;
|
23
|
+
}
|
24
|
+
|
25
|
+
.text-underline-on-hover {
|
26
|
+
text-decoration: none;
|
27
|
+
}
|
28
|
+
|
29
|
+
.text-underline-on-hover:hover {
|
30
|
+
text-decoration: underline;
|
31
|
+
}
|
32
|
+
|
33
|
+
.lightning-logo {
|
34
|
+
font-size: 18px;
|
35
|
+
font-weight: bolder;
|
36
|
+
letter-spacing: -0.5px;
|
37
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_dependency "lightning/application_controller"
|
2
|
+
|
3
|
+
module Lightning
|
4
|
+
class FeatureOptInsController < ApplicationController
|
5
|
+
|
6
|
+
def create
|
7
|
+
@feature = Feature.find(params[:feature_id])
|
8
|
+
@feature_opt_in = @feature.feature_opt_ins.new
|
9
|
+
@feature_opt_in.entity_id = params[:feature_opt_in][:entity_id]
|
10
|
+
@feature_opt_in.entity_type = params[:feature_opt_in][:entity_type]
|
11
|
+
if @feature_opt_in.save
|
12
|
+
flash[:notice] = "Permissions has been created!"
|
13
|
+
else
|
14
|
+
flash[:notice] = "FAILED TO SAVE PERMISSIONS! INVALID ENTITY ID!"
|
15
|
+
end
|
16
|
+
redirect_to @feature
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy
|
20
|
+
@feature = Feature.find(params[:feature_id])
|
21
|
+
@feature_opt_in = @feature.feature_opt_ins.find(params[:id])
|
22
|
+
@feature_opt_in.destroy
|
23
|
+
redirect_to @feature, notice: 'Feature permission was successfully destroyed.'
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def feature_opt_ins_params
|
28
|
+
params.require(:feature_opt_in).permit(:entity_id, :entity_type)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_dependency "lightning/application_controller"
|
2
|
+
|
3
|
+
module Lightning
|
4
|
+
class FeaturesController < ApplicationController
|
5
|
+
before_action :set_feature, only: [:show, :edit, :update, :destroy]
|
6
|
+
|
7
|
+
# GET /features
|
8
|
+
def index
|
9
|
+
@features = Feature.all
|
10
|
+
end
|
11
|
+
#
|
12
|
+
# GET /features/1
|
13
|
+
def show
|
14
|
+
@flaggable_entities = Lightning.flaggable_entities
|
15
|
+
end
|
16
|
+
|
17
|
+
# GET /features/new
|
18
|
+
def new
|
19
|
+
@feature = Feature.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# GET /feature/1/edit
|
23
|
+
def edit
|
24
|
+
end
|
25
|
+
|
26
|
+
# POST /features
|
27
|
+
def create
|
28
|
+
@feature = Feature.new(feature_params)
|
29
|
+
|
30
|
+
if @feature.save
|
31
|
+
redirect_to @feature, notice: 'Feature was successfully created.'
|
32
|
+
else
|
33
|
+
render :new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# PATCH/PUT /features/1
|
38
|
+
def update
|
39
|
+
if @feature.update(feature_params)
|
40
|
+
redirect_to @feature, notice: 'Feature was successfully updated.'
|
41
|
+
else
|
42
|
+
render :edit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# DELETE /features/1
|
47
|
+
def destroy
|
48
|
+
@feature.destroy
|
49
|
+
redirect_to features_url, notice: 'Feature was successfully destroyed.'
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
# Use callbacks to share common setup or constraints between actions.
|
54
|
+
def set_feature
|
55
|
+
@feature = Feature.find(params[:id])
|
56
|
+
end
|
57
|
+
|
58
|
+
# Only allow a list of trusted parameters through.
|
59
|
+
def feature_params
|
60
|
+
params.require(:feature).permit(:key, :description, :state)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Lightning
|
2
|
+
class FeatureOptIn < ApplicationRecord
|
3
|
+
belongs_to :feature
|
4
|
+
|
5
|
+
attr_accessor :entity_id
|
6
|
+
|
7
|
+
belongs_to :entity, polymorphic: true
|
8
|
+
before_validation :set_entity
|
9
|
+
|
10
|
+
private
|
11
|
+
def set_entity
|
12
|
+
self.entity = self.entity_type.constantize.find_by_id(self.entity_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Lightning — Feature Flags</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<%= stylesheet_link_tag "lightning/application", media: "all" %>
|
9
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
|
11
|
+
|
12
|
+
</head>
|
13
|
+
|
14
|
+
<body>
|
15
|
+
<%= yield %>
|
16
|
+
</body>
|
17
|
+
</html>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<h3>Permission Entities</h3>
|
2
|
+
<%= form_with model: [@feature, @feature.feature_opt_ins.build] do |form| %>
|
3
|
+
<p>
|
4
|
+
<%= form.label :entity_type, 'Select Entity Type' %><br>
|
5
|
+
<%= form.select :entity_type, options_for_select(@flaggable_entities.map {|u| [ u.to_s, u.to_s] }) %>
|
6
|
+
</p>
|
7
|
+
<p>
|
8
|
+
<%= form.label :entity_id, 'Entity Id' %><br>
|
9
|
+
<%= form.text_field :entity_id %>
|
10
|
+
</p>
|
11
|
+
<%= form.submit 'Add entity to feature' %>
|
12
|
+
<% end %>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<%= form_with(model: feature) do |form| %>
|
2
|
+
<% if feature.errors.any? %>
|
3
|
+
<div id="error_explanation">
|
4
|
+
<h2><%= pluralize(feature.errors.count, "error") %> prohibited this feature from being saved:</h2>
|
5
|
+
|
6
|
+
<ul>
|
7
|
+
<% feature.errors.each do |error| %>
|
8
|
+
<li><%= error.full_message %></li>
|
9
|
+
<% end %>
|
10
|
+
</ul>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<div class="field">
|
15
|
+
<%= form.label :key %>
|
16
|
+
<%= form.text_field :key %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="field">
|
20
|
+
<%= form.label :description %>
|
21
|
+
<%= form.text_area :description %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="field">
|
25
|
+
<%= form.label :state %>
|
26
|
+
<%= form.select :state, Lightning::Feature.states.keys %>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<!-- <div class="field">-->
|
30
|
+
<%#= form.label :globally_enabled %>
|
31
|
+
<%#= form.text_area :globally_enabled %>
|
32
|
+
<!-- </div>-->
|
33
|
+
|
34
|
+
<div class="actions">
|
35
|
+
<%= form.submit %>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<div class="container">
|
2
|
+
<p id="notice"><%= notice %></p>
|
3
|
+
|
4
|
+
<div class="d-flex justify-content-between align-items-center">
|
5
|
+
<div class="lightning-logo">⚡️ Lightning</div>
|
6
|
+
<%= link_to '+ New Feature', new_feature_path, class: "btn btn-primary" %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<table class="table mt-5">
|
10
|
+
<tbody>
|
11
|
+
<% @features.each do |feature| %>
|
12
|
+
<tr>
|
13
|
+
<td>
|
14
|
+
<% if feature.enabled_per_entity? %>
|
15
|
+
<span class="badge bg-primary"><%= pluralize feature.feature_opt_ins.count, "opt in" %></span>
|
16
|
+
<% elsif feature.enabled_globally? %>
|
17
|
+
<span class="badge bg-success">On</span>
|
18
|
+
<% else %>
|
19
|
+
<span class="badge bg-secondary">Off</span>
|
20
|
+
<% end %>
|
21
|
+
</td>
|
22
|
+
<td>
|
23
|
+
<%= link_to feature.key, feature, class: "fw-monospace fw-bold text-underline-on-hover text-reset" %>
|
24
|
+
</td>
|
25
|
+
<td><%= feature.description %></td>
|
26
|
+
<td><%= feature.created_at.to_formatted_s(:short) %></td>
|
27
|
+
</tr>
|
28
|
+
<% end %>
|
29
|
+
</tbody>
|
30
|
+
</table>
|
31
|
+
</div>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<p id="notice"><%= notice %></p>
|
2
|
+
|
3
|
+
<td><%= link_to 'Edit', edit_feature_path(@feature) %></td>
|
4
|
+
<td><%= button_to 'Destroy', feature_path(@feature), method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<strong>Key:</strong>
|
8
|
+
<%= @feature.key %>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<strong>Description:</strong>
|
13
|
+
<%= @feature.description %>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<p>
|
17
|
+
<strong>State:</strong>
|
18
|
+
<%= @feature.state %>
|
19
|
+
</p>
|
20
|
+
|
21
|
+
|
22
|
+
<p>
|
23
|
+
<strong>Created:</strong>
|
24
|
+
<%= @feature.created_at.to_formatted_s(:long) %>
|
25
|
+
</p>
|
26
|
+
|
27
|
+
<p>
|
28
|
+
<strong>Updated:</strong>
|
29
|
+
<%= @feature.updated_at.to_formatted_s(:long) %>
|
30
|
+
</p>
|
31
|
+
|
32
|
+
|
33
|
+
<h3>Opt In Entities</h3>
|
34
|
+
<%= render @feature.feature_opt_ins %>
|
35
|
+
<%= render "lightning/feature_opt_ins/form" %>
|
36
|
+
<br/>
|
37
|
+
|
38
|
+
<%= link_to 'Edit', edit_feature_path(@feature) %> |
|
39
|
+
<%= link_to 'Back', features_path %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateFeatureFlagFramework < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :lightning_features do |t|
|
4
|
+
t.string :key, index: { unique: true }, null: false
|
5
|
+
t.text :description
|
6
|
+
t.integer :state, default: 0, null: false
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :lightning_feature_opt_ins do |t|
|
12
|
+
t.integer :feature_id, null: false
|
13
|
+
t.integer :entity_id, null: false
|
14
|
+
t.string :entity_type, null: false
|
15
|
+
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
add_index :lightning_feature_opt_ins, [:entity_id, :entity_type]
|
20
|
+
end
|
21
|
+
end
|
data/lib/lightning.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'lightning/version'
|
2
|
+
require 'lightning/engine'
|
3
|
+
require 'lightning/api'
|
4
|
+
require 'lightning/errors'
|
5
|
+
|
6
|
+
module Lightning
|
7
|
+
# Your code goes here...
|
8
|
+
mattr_accessor :flaggable_entities
|
9
|
+
|
10
|
+
def self.flaggable_entities
|
11
|
+
@@flaggable_entities.map { |f| f.constantize }
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
delegate :create!, :get, :list, :update, :delete, :entities, :enable_entity, :remove_entity, :enabled?, to: Api
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lightning
|
4
|
+
module Api
|
5
|
+
def self.create!(key, description = '')
|
6
|
+
Feature.create!(key: key, description: description)
|
7
|
+
rescue StandardError
|
8
|
+
raise ::Lightning::Errors::FailedToCreate, 'Failed to create new feature'
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.get(key)
|
12
|
+
Feature.find_by!(key: key)
|
13
|
+
rescue StandardError
|
14
|
+
raise ::Lightning::Errors::FeatureNotFound, "Feature with key: #{key} not found"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.list
|
18
|
+
Feature.all
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.update(key, attributes)
|
22
|
+
get(key).update(attributes)
|
23
|
+
rescue ArgumentError
|
24
|
+
raise ::Lightning::Errors::InvalidFeatureState, "Failed to update state. State must be one of the following: #{Feature.states.keys} "
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.delete(key)
|
28
|
+
get(key).destroy
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.entities(key)
|
32
|
+
get(key).feature_opt_ins.all.map(&:entity)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.enable_entity(key, entity)
|
36
|
+
permissioned_entity = get(key).feature_opt_ins.new
|
37
|
+
permissioned_entity.entity_id = entity.id
|
38
|
+
permissioned_entity.entity_type = entity.class.to_s
|
39
|
+
permissioned_entity.save!
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.remove_entity(key, entity)
|
43
|
+
get(key).feature_opt_ins.find_by(entity_id: entity.id, entity_type: entity.class.to_s)&.destroy
|
44
|
+
rescue ActiveRecord::RecordNotFound
|
45
|
+
raise ::Lightning::Errors::EntityNotFound, "Could not find entity with id #{entity.id} and type #{entity.class.to_s}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.enabled?(entity, feature_key)
|
49
|
+
joined_table = Feature.left_outer_joins(:feature_opt_ins)
|
50
|
+
joined_table
|
51
|
+
.where(key: feature_key, state: :enabled_per_entity, feature_opt_ins: { entity_id: entity.id, entity_type: entity.class.to_s })
|
52
|
+
.or(joined_table.where(key: feature_key, state: :enabled_globally))
|
53
|
+
.exists?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Lightning
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Lightning
|
4
|
+
|
5
|
+
initializer "lightning.assets.precompile" do |app|
|
6
|
+
app.config.assets.precompile += %w( lightning/application.css )
|
7
|
+
end
|
8
|
+
|
9
|
+
config.generators do |g|
|
10
|
+
g.test_framework :rspec
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
desc "Setup script for the gem"
|
2
|
+
namespace :lightning do
|
3
|
+
task :install do
|
4
|
+
_, *args = ARGV
|
5
|
+
|
6
|
+
|
7
|
+
# Task goes here
|
8
|
+
puts 'Copying migrations from engines...'
|
9
|
+
`bin/rails lightning:install:migrations`
|
10
|
+
|
11
|
+
puts 'Running engine migrations...'
|
12
|
+
`bin/rails db:migrate SCOPE=lightning`
|
13
|
+
|
14
|
+
puts 'Creating initializers script'
|
15
|
+
`echo "Lightning.flaggable_entities = [#{args.map{ |a| '\"' + a + '\"'}.join(", ")}]" > config/initializers/lightning.rb`
|
16
|
+
|
17
|
+
args.each do |model|
|
18
|
+
puts 'Adding taggable to model: ' + model
|
19
|
+
Dir.glob("app/models/**/#{model.underscore}.rb").each do |f|
|
20
|
+
puts f
|
21
|
+
|
22
|
+
# Find the model name and add the line underneath it
|
23
|
+
file = File.open(f)
|
24
|
+
new_file_contents = StringIO.open
|
25
|
+
add_line_here = false
|
26
|
+
file.each do |line|
|
27
|
+
if add_line_here
|
28
|
+
new_file_contents << " include Lightning::Flaggable\n"
|
29
|
+
add_line_here = false
|
30
|
+
end
|
31
|
+
|
32
|
+
if line.include?('class') && line.include?(model) && line.include?(' < ')
|
33
|
+
add_line_here = true
|
34
|
+
end
|
35
|
+
new_file_contents << line
|
36
|
+
end
|
37
|
+
file.close
|
38
|
+
|
39
|
+
# Update file contents with new file contents
|
40
|
+
new_file_contents.seek 0
|
41
|
+
File.open(f, 'wb').write new_file_contents.read
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
puts 'Successfully ran setup script'
|
47
|
+
|
48
|
+
# Don't run the tasks in the arguments
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lightningff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ruthwick Pathireddy
|
8
|
+
- Pranav Singh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-05-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 6.1.3
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 6.1.3.2
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - "~>"
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 6.1.3
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 6.1.3.2
|
34
|
+
description: With Lightning, you can set up an end-to-end highly customizable feature
|
35
|
+
flagging system in <1 minute. It provides console and UI support for feature flag
|
36
|
+
management.
|
37
|
+
email:
|
38
|
+
- ruthwickp@gmail.com
|
39
|
+
- pranav@getcadet.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- MIT-LICENSE
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- app/assets/config/lightning_manifest.js
|
48
|
+
- app/assets/stylesheets/lightning/application.css
|
49
|
+
- app/controllers/lightning/application_controller.rb
|
50
|
+
- app/controllers/lightning/feature_opt_ins_controller.rb
|
51
|
+
- app/controllers/lightning/features_controller.rb
|
52
|
+
- app/helpers/lightning/application_helper.rb
|
53
|
+
- app/helpers/lightning/flaggable.rb
|
54
|
+
- app/jobs/lightning/application_job.rb
|
55
|
+
- app/mailers/lightning/application_mailer.rb
|
56
|
+
- app/models/lightning/application_record.rb
|
57
|
+
- app/models/lightning/feature.rb
|
58
|
+
- app/models/lightning/feature_opt_in.rb
|
59
|
+
- app/views/layouts/lightning/application.html.erb
|
60
|
+
- app/views/lightning/feature_opt_ins/_feature_opt_in.html.erb
|
61
|
+
- app/views/lightning/feature_opt_ins/_form.html.erb
|
62
|
+
- app/views/lightning/features/_form.html.erb
|
63
|
+
- app/views/lightning/features/edit.html.erb
|
64
|
+
- app/views/lightning/features/index.html.erb
|
65
|
+
- app/views/lightning/features/new.html.erb
|
66
|
+
- app/views/lightning/features/show.html.erb
|
67
|
+
- config/routes.rb
|
68
|
+
- db/migrate/20210513140212_create_feature_flag_framework.rb
|
69
|
+
- lib/lightning.rb
|
70
|
+
- lib/lightning/api.rb
|
71
|
+
- lib/lightning/engine.rb
|
72
|
+
- lib/lightning/errors.rb
|
73
|
+
- lib/lightning/version.rb
|
74
|
+
- lib/tasks/lightning_tasks.rake
|
75
|
+
homepage: https://github.com/LightningFF/lightning
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata:
|
79
|
+
homepage_uri: https://github.com/LightningFF/lightning
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.1.6
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Feature flagging for Rails
|
99
|
+
test_files: []
|