defra_ruby_features 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +8 -0
- data/README.md +92 -0
- data/Rakefile +40 -0
- data/app/controllers/defra_ruby_features/application_controller.rb +7 -0
- data/app/controllers/defra_ruby_features/feature_toggles_controller.rb +58 -0
- data/app/helpers/defra_ruby_features/application_helper.rb +6 -0
- data/app/helpers/delete_link_helper.rb +16 -0
- data/app/views/defra_ruby_features/feature_toggles/index.html.erb +48 -0
- data/app/views/defra_ruby_features/feature_toggles/new.html.erb +35 -0
- data/app/views/shared/_back.html.erb +1 -0
- data/config/locales/defra_ruby_features.en.yml +24 -0
- data/config/routes.rb +5 -0
- data/lib/defra_ruby_features.rb +26 -0
- data/lib/defra_ruby_features/configuration.rb +7 -0
- data/lib/defra_ruby_features/engine.rb +13 -0
- data/lib/defra_ruby_features/version.rb +5 -0
- data/lib/tasks/changelog.rake +8 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +6 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +6 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/test_controller.rb +7 -0
- data/spec/dummy/app/helpers/application_helper.rb +4 -0
- data/spec/dummy/app/javascript/packs/application.js +14 -0
- data/spec/dummy/app/jobs/application_job.rb +9 -0
- data/spec/dummy/app/models/ability.rb +9 -0
- data/spec/dummy/app/models/application_record.rb +5 -0
- data/spec/dummy/app/models/feature_toggle.rb +4 -0
- data/spec/dummy/app/models/user.rb +8 -0
- data/spec/dummy/app/views/layouts/application.html.erb +12 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +25 -0
- data/spec/dummy/config.ru +7 -0
- data/spec/dummy/config/application.rb +29 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/database.yml +21 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config/environments/development.rb +50 -0
- data/spec/dummy/config/environments/test.rb +41 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +10 -0
- data/spec/dummy/config/initializers/assets.rb +14 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +7 -0
- data/spec/dummy/config/initializers/defra_ruby_features.rb +7 -0
- data/spec/dummy/config/initializers/devise.rb +311 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/dummy/config/initializers/inflections.rb +18 -0
- data/spec/dummy/config/initializers/mime_types.rb +6 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
- data/spec/dummy/config/locales/devise.en.yml +65 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/routes.rb +7 -0
- data/spec/dummy/db/migrate/20200623125321_create_feature_toggles.rb +12 -0
- data/spec/dummy/db/migrate/20200624115316_devise_create_users.rb +44 -0
- data/spec/dummy/db/schema.rb +34 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +4579 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/development_secret.txt +1 -0
- data/spec/examples.txt +13 -0
- data/spec/factories/feature_toggle.rb +8 -0
- data/spec/factories/user.rb +11 -0
- data/spec/lib/defra_ruby_mocks/configuration_spec.rb +13 -0
- data/spec/rails_helper.rb +54 -0
- data/spec/requests/defra_ruby_features/feature_toggles_spec.rb +145 -0
- data/spec/spec_helper.rb +83 -0
- data/spec/support/database_cleaner.rb +19 -0
- data/spec/support/devise.rb +5 -0
- data/spec/support/factory_bot.rb +8 -0
- data/spec/support/migrations.rb +10 -0
- data/spec/support/pry.rb +7 -0
- data/spec/support/rails_controller_testing.rb +5 -0
- data/spec/support/simplecov.rb +17 -0
- metadata +364 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b9b0b518a146ea7a2c3ac516a564d94f9791c5ffd758b03240924bd4178d544c
|
4
|
+
data.tar.gz: e197f19c6013c8225958f51145159bcf27767b54087e28eff5243e335b4393f2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6fe6cee2c6aeab187d325491773d93a4eb2637e006315f973060a3af84dedefe98688825787753f090105a69438832b7a6fd7b6878c78f4defd24e7bad0d562f
|
7
|
+
data.tar.gz: e9a3c942ad37d26e0ff1dc99d4723641ff3f260be201220a8de34abba0fd15fb779c003b9efb1652bab0b83cbc4bc380f7e1aefcf4f87d310bf168dbb540f9e6
|
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
The Open Government Licence (OGL) Version 3
|
2
|
+
|
3
|
+
Copyright (c) 2020 Defra
|
4
|
+
|
5
|
+
This source code is licensed under the Open Government Licence v3.0. To view this
|
6
|
+
licence, visit www.nationalarchives.gov.uk/doc/open-government-licence/version/3
|
7
|
+
or write to the Information Policy Team, The National Archives, Kew, Richmond,
|
8
|
+
Surrey, TW9 4DU.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Defra Ruby Features
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.com/DEFRA/defra-ruby-features.svg?branch=main)](https://travis-ci.com/DEFRA/defra-ruby-features)
|
4
|
+
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=DEFRA_defra-ruby-features&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=DEFRA_defra-ruby-features)
|
5
|
+
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=DEFRA_defra-ruby-features&metric=coverage)](https://sonarcloud.io/dashboard?id=DEFRA_defra-ruby-features)
|
6
|
+
[![security](https://hakiri.io/github/DEFRA/defra-ruby-features/main.svg)](https://hakiri.io/github/DEFRA/defra-ruby-features/main)
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/defra_ruby_features.svg)](https://badge.fury.io/rb/defra_ruby_features)
|
8
|
+
[![Licence](https://img.shields.io/badge/Licence-OGLv3-blue.svg)](http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3)
|
9
|
+
|
10
|
+
A Rails Engine used by the [Ruby services team](https://github.com/DEFRA/ruby-services-team) in their digital services.
|
11
|
+
|
12
|
+
We use it to set, update and delete feature toggles on our rails services.
|
13
|
+
|
14
|
+
When mounted in an app, it will add additional endpoints and views to manage feature toggles.
|
15
|
+
|
16
|
+
Things to note
|
17
|
+
|
18
|
+
- We have gone with an engine rather than an additional service, to simplify management of feature toggles in our environments
|
19
|
+
|
20
|
+
## Prerequisites
|
21
|
+
|
22
|
+
Make sure you already have:
|
23
|
+
|
24
|
+
- Ruby 2.7.1
|
25
|
+
- [Bundler](http://bundler.io/) – for installing Ruby gems
|
26
|
+
|
27
|
+
## Mounting the engine
|
28
|
+
|
29
|
+
Add the engine to your Gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
gem "defra_ruby_features",
|
33
|
+
git: "https://github.com/DEFRA/defra-ruby-features"
|
34
|
+
```
|
35
|
+
|
36
|
+
Install it with `bundle install`.
|
37
|
+
|
38
|
+
Then mount the engine in your routes.rb file:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Rails.application.routes.draw do
|
42
|
+
mount DefraRubyFeatures::Engine => "/defra-ruby-featuress"
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
The engine should now be mounted at `/defra-ruby-features` of your project. You can change `"/defra-ruby-features"` to a different route if you'd prefer it to be elsewhere.
|
47
|
+
|
48
|
+
## Configuration
|
49
|
+
|
50
|
+
For the feature toggles to be persisted, you need to create a model class with a `String` attribute called `key` and a `Boolean` attribute named `active`.
|
51
|
+
|
52
|
+
*This will work with MongoId models as well as ActiveRecord models.*
|
53
|
+
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# config/initializers/defra_ruby_features.rb
|
57
|
+
require "defra_ruby_features"
|
58
|
+
|
59
|
+
DefraRubyFeatures.configure do |config|
|
60
|
+
config.feature_toggle_model_name = "FeatureToggle"
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
## Testing the engine
|
65
|
+
|
66
|
+
The engine is mounted in a dummy Rails 6 app (in /spec/dummy) so we can properly test its behaviour.
|
67
|
+
|
68
|
+
The test suite is written in RSpec.
|
69
|
+
|
70
|
+
To run all the tests, use `bundle exec rspec`.
|
71
|
+
|
72
|
+
## Contributing to this project
|
73
|
+
|
74
|
+
If you have an idea you'd like to contribute please log an issue.
|
75
|
+
|
76
|
+
All contributions should be submitted via a pull request.
|
77
|
+
|
78
|
+
## License
|
79
|
+
|
80
|
+
THIS INFORMATION IS LICENSED UNDER THE CONDITIONS OF THE OPEN GOVERNMENT LICENCE found at:
|
81
|
+
|
82
|
+
<http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3>
|
83
|
+
|
84
|
+
The following attribution statement MUST be cited in your products and applications when using this information.
|
85
|
+
|
86
|
+
> Contains public sector information licensed under the Open Government license v3
|
87
|
+
|
88
|
+
### About the license
|
89
|
+
|
90
|
+
The Open Government Licence (OGL) was developed by the Controller of Her Majesty's Stationery Office (HMSO) to enable information providers in the public sector to license the use and re-use of their information under a common open licence.
|
91
|
+
|
92
|
+
It is designed to encourage use and re-use of information freely and flexibly, with only a few conditions.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "bundler/setup"
|
5
|
+
rescue LoadError
|
6
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
7
|
+
end
|
8
|
+
|
9
|
+
require "rdoc/task"
|
10
|
+
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
12
|
+
rdoc.rdoc_dir = "rdoc"
|
13
|
+
rdoc.title = "DefraRubyFeatures"
|
14
|
+
rdoc.options << "--line-numbers"
|
15
|
+
rdoc.rdoc_files.include("README.md")
|
16
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
load "rails/tasks/statistics.rake"
|
20
|
+
|
21
|
+
Bundler::GemHelper.install_tasks
|
22
|
+
|
23
|
+
Dir[File.join(File.dirname(__FILE__), "lib/tasks/**/*.rake")].each { |f| load f }
|
24
|
+
|
25
|
+
require "bundler/gem_tasks"
|
26
|
+
|
27
|
+
require "rake/testtask"
|
28
|
+
|
29
|
+
# This is wrapped to prevent an error when rake is called in environments where
|
30
|
+
# rspec may not be available, e.g. production. As such we don't need to handle
|
31
|
+
# the error.
|
32
|
+
begin
|
33
|
+
require "rspec/core/rake_task"
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:spec)
|
36
|
+
|
37
|
+
task default: :spec
|
38
|
+
rescue LoadError
|
39
|
+
# no rspec available
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DefraRubyFeatures
|
4
|
+
class FeatureTogglesController < ::DefraRubyFeatures::ApplicationController
|
5
|
+
before_action :authenticate_user!
|
6
|
+
before_action :authorize_user!
|
7
|
+
|
8
|
+
helper DeleteLinkHelper
|
9
|
+
|
10
|
+
def index
|
11
|
+
# MongoId
|
12
|
+
@feature_toggles = model.order(key: "ASC") if model.respond_to?(:order)
|
13
|
+
|
14
|
+
# ActiveRecord
|
15
|
+
@feature_toggles = model.order_by(key: "ASC").all if model.respond_to?(:order_by)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create
|
19
|
+
model.create!(feature_toggle_params)
|
20
|
+
|
21
|
+
redirect_to feature_toggles_path
|
22
|
+
end
|
23
|
+
|
24
|
+
def update
|
25
|
+
@feature_toggle = model.find_by(id: params[:id])
|
26
|
+
|
27
|
+
@feature_toggle.update!(feature_toggle_params)
|
28
|
+
|
29
|
+
redirect_to feature_toggles_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def new
|
33
|
+
@feature_toggle = model.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
@feature_toggle = model.find_by(id: params[:id])
|
38
|
+
|
39
|
+
@feature_toggle.destroy!
|
40
|
+
|
41
|
+
redirect_to feature_toggles_path
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def authorize_user!
|
47
|
+
authorize! :manage, model
|
48
|
+
end
|
49
|
+
|
50
|
+
def model
|
51
|
+
@_model ||= DefraRubyFeatures.configuration.feature_toggle_model_name.constantize
|
52
|
+
end
|
53
|
+
|
54
|
+
def feature_toggle_params
|
55
|
+
params.fetch(:feature_toggle, {}).permit(:key, :active)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Credits: viget.com/articles/delete-in-rails-without-jquery-and-ujs/
|
4
|
+
|
5
|
+
module DeleteLinkHelper
|
6
|
+
def delete_button_to(title, url, options = {})
|
7
|
+
html_options = {
|
8
|
+
class: "delete_button_to",
|
9
|
+
method: "delete"
|
10
|
+
}.merge(options.delete(:html_options) || {})
|
11
|
+
|
12
|
+
form_for :delete, url: url, html: html_options do |f|
|
13
|
+
f.submit title, options
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<div class="text">
|
2
|
+
<h1 class="heading-large"><%= t(".heading") %></h1>
|
3
|
+
|
4
|
+
<table>
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th scope="col"><%= t(".table.key") %></th>
|
8
|
+
<th scope="col"><%= t(".table.status") %></th>
|
9
|
+
<th scope="col"></th>
|
10
|
+
</tr>
|
11
|
+
</thead>
|
12
|
+
|
13
|
+
<tbody>
|
14
|
+
<% @feature_toggles.each do |feature_toggle| %>
|
15
|
+
<tr>
|
16
|
+
<td><%= feature_toggle.key %></th>
|
17
|
+
<td>
|
18
|
+
<span class="status-tag <%= feature_toggle.active ? "status-tag--active" : "status-tag--inactive" %>">
|
19
|
+
<%= feature_toggle.active ? t(".status.enabled") : t(".status.disabled") %>
|
20
|
+
</span>
|
21
|
+
</td>
|
22
|
+
|
23
|
+
<td>
|
24
|
+
<ul class="list">
|
25
|
+
<li>
|
26
|
+
<%= form_for feature_toggle do |f| %>
|
27
|
+
<%= f.hidden_field :active, value: !feature_toggle.active %>
|
28
|
+
<%= f.submit t(".actions.toggle"), class: "button" %>
|
29
|
+
<% end %>
|
30
|
+
</li>
|
31
|
+
<li>
|
32
|
+
<%= delete_button_to t(".actions.delete"), feature_toggle_path(feature_toggle), {
|
33
|
+
data: { confirm: "Are you sure?" },
|
34
|
+
class: "button button-red"
|
35
|
+
} %>
|
36
|
+
|
37
|
+
</li>
|
38
|
+
</ul>
|
39
|
+
</td>
|
40
|
+
</tr>
|
41
|
+
<% end %>
|
42
|
+
</tbody>
|
43
|
+
</table>
|
44
|
+
|
45
|
+
<p>
|
46
|
+
<%= link_to t(".new"), new_feature_toggle_path, class: "button" %>
|
47
|
+
</p>
|
48
|
+
</div>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<div class="grid-row">
|
2
|
+
<div class="column-two-thirds">
|
3
|
+
<%= render("shared/back", back_path: feature_toggles_path) %>
|
4
|
+
|
5
|
+
<%= form_for(@feature_toggle) do |f| %>
|
6
|
+
<h1 class="heading-large">
|
7
|
+
<%= t(".heading") %>
|
8
|
+
</h1>
|
9
|
+
|
10
|
+
<div class="form-group <%= "form-group-error" if @feature_toggle.errors[:key].any? %>">
|
11
|
+
<% if @feature_toggle.errors[:key].any? %>
|
12
|
+
<span class="error-message"><%= @feature_toggle.errors[:key].join(", ") %></span>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<%= f.label :key, t(".fields.key"), class: "form-label" %>
|
16
|
+
<%= f.text_field :key, class: "form-control" %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="form-group <%= "form-group-error" if @feature_toggle.errors[:active].any? %>">
|
20
|
+
<% if @feature_toggle.errors[:key].any? %>
|
21
|
+
<span class="error-message"><%= @feature_toggle.errors[:key].join(", ") %></span>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<div class="multiple-choice">
|
25
|
+
<%= f.check_box :active %>
|
26
|
+
<%= f.label :active, t(".fields.active") %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<div class="form-group">
|
31
|
+
<%= f.submit t(".submit"), class: "button" %>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
34
|
+
</div>
|
35
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= link_to(t(".back_link"), back_path, class: "link-back") %>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
en:
|
2
|
+
defra_ruby_features:
|
3
|
+
feature_toggles:
|
4
|
+
index:
|
5
|
+
heading: "Feature Toggles"
|
6
|
+
new: "New"
|
7
|
+
table:
|
8
|
+
key: "Name"
|
9
|
+
status: "Status"
|
10
|
+
actions:
|
11
|
+
toggle: "Toggle"
|
12
|
+
delete: "Delete"
|
13
|
+
status:
|
14
|
+
enabled: "Enabled"
|
15
|
+
disabled: "Disabled"
|
16
|
+
new:
|
17
|
+
heading: "Add a new Feature Toggle"
|
18
|
+
fields:
|
19
|
+
key: "Name"
|
20
|
+
active: "Active"
|
21
|
+
submit: "Save"
|
22
|
+
shared:
|
23
|
+
back:
|
24
|
+
back_link: "Back"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "defra_ruby_features/engine"
|
4
|
+
|
5
|
+
module DefraRubyFeatures
|
6
|
+
# Enable the ability to configure the gem from its host app, rather than
|
7
|
+
# reading directly from env vars. Derived from
|
8
|
+
# https://robots.thoughtbot.com/mygem-configure-block
|
9
|
+
class << self
|
10
|
+
attr_writer :configuration
|
11
|
+
|
12
|
+
def configuration
|
13
|
+
@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Added for testing. Without we cannot test both a config object with and
|
17
|
+
# with set values in the same rspec session
|
18
|
+
def reset_configuration
|
19
|
+
@configuration = nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configure
|
24
|
+
yield(configuration)
|
25
|
+
end
|
26
|
+
end
|
data/spec/dummy/Rakefile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
4
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
5
|
+
|
6
|
+
require_relative "config/application"
|
7
|
+
|
8
|
+
Rails.application.load_tasks
|