defra_ruby_features 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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +8 -0
  3. data/README.md +92 -0
  4. data/Rakefile +40 -0
  5. data/app/controllers/defra_ruby_features/application_controller.rb +7 -0
  6. data/app/controllers/defra_ruby_features/feature_toggles_controller.rb +58 -0
  7. data/app/helpers/defra_ruby_features/application_helper.rb +6 -0
  8. data/app/helpers/delete_link_helper.rb +16 -0
  9. data/app/views/defra_ruby_features/feature_toggles/index.html.erb +48 -0
  10. data/app/views/defra_ruby_features/feature_toggles/new.html.erb +35 -0
  11. data/app/views/shared/_back.html.erb +1 -0
  12. data/config/locales/defra_ruby_features.en.yml +24 -0
  13. data/config/routes.rb +5 -0
  14. data/lib/defra_ruby_features.rb +26 -0
  15. data/lib/defra_ruby_features/configuration.rb +7 -0
  16. data/lib/defra_ruby_features/engine.rb +13 -0
  17. data/lib/defra_ruby_features/version.rb +5 -0
  18. data/lib/tasks/changelog.rake +8 -0
  19. data/spec/dummy/Rakefile +8 -0
  20. data/spec/dummy/app/assets/config/manifest.js +3 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  22. data/spec/dummy/app/channels/application_cable/channel.rb +6 -0
  23. data/spec/dummy/app/channels/application_cable/connection.rb +6 -0
  24. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  25. data/spec/dummy/app/controllers/test_controller.rb +7 -0
  26. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  27. data/spec/dummy/app/javascript/packs/application.js +14 -0
  28. data/spec/dummy/app/jobs/application_job.rb +9 -0
  29. data/spec/dummy/app/models/ability.rb +9 -0
  30. data/spec/dummy/app/models/application_record.rb +5 -0
  31. data/spec/dummy/app/models/feature_toggle.rb +4 -0
  32. data/spec/dummy/app/models/user.rb +8 -0
  33. data/spec/dummy/app/views/layouts/application.html.erb +12 -0
  34. data/spec/dummy/bin/rails +4 -0
  35. data/spec/dummy/bin/rake +4 -0
  36. data/spec/dummy/bin/setup +25 -0
  37. data/spec/dummy/config.ru +7 -0
  38. data/spec/dummy/config/application.rb +29 -0
  39. data/spec/dummy/config/boot.rb +7 -0
  40. data/spec/dummy/config/database.yml +21 -0
  41. data/spec/dummy/config/environment.rb +7 -0
  42. data/spec/dummy/config/environments/development.rb +50 -0
  43. data/spec/dummy/config/environments/test.rb +41 -0
  44. data/spec/dummy/config/initializers/application_controller_renderer.rb +10 -0
  45. data/spec/dummy/config/initializers/assets.rb +14 -0
  46. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  47. data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
  48. data/spec/dummy/config/initializers/cookies_serializer.rb +7 -0
  49. data/spec/dummy/config/initializers/defra_ruby_features.rb +7 -0
  50. data/spec/dummy/config/initializers/devise.rb +311 -0
  51. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  52. data/spec/dummy/config/initializers/inflections.rb +18 -0
  53. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  54. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  55. data/spec/dummy/config/locales/devise.en.yml +65 -0
  56. data/spec/dummy/config/locales/en.yml +33 -0
  57. data/spec/dummy/config/routes.rb +7 -0
  58. data/spec/dummy/db/migrate/20200623125321_create_feature_toggles.rb +12 -0
  59. data/spec/dummy/db/migrate/20200624115316_devise_create_users.rb +44 -0
  60. data/spec/dummy/db/schema.rb +34 -0
  61. data/spec/dummy/db/test.sqlite3 +0 -0
  62. data/spec/dummy/log/test.log +4579 -0
  63. data/spec/dummy/public/404.html +67 -0
  64. data/spec/dummy/public/422.html +67 -0
  65. data/spec/dummy/public/500.html +66 -0
  66. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  67. data/spec/dummy/public/apple-touch-icon.png +0 -0
  68. data/spec/dummy/public/favicon.ico +0 -0
  69. data/spec/dummy/tmp/development_secret.txt +1 -0
  70. data/spec/examples.txt +13 -0
  71. data/spec/factories/feature_toggle.rb +8 -0
  72. data/spec/factories/user.rb +11 -0
  73. data/spec/lib/defra_ruby_mocks/configuration_spec.rb +13 -0
  74. data/spec/rails_helper.rb +54 -0
  75. data/spec/requests/defra_ruby_features/feature_toggles_spec.rb +145 -0
  76. data/spec/spec_helper.rb +83 -0
  77. data/spec/support/database_cleaner.rb +19 -0
  78. data/spec/support/devise.rb +5 -0
  79. data/spec/support/factory_bot.rb +8 -0
  80. data/spec/support/migrations.rb +10 -0
  81. data/spec/support/pry.rb +7 -0
  82. data/spec/support/rails_controller_testing.rb +5 -0
  83. data/spec/support/simplecov.rb +17 -0
  84. metadata +364 -0
@@ -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.
@@ -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.
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyFeatures
4
+ class ApplicationController < ::ApplicationController
5
+
6
+ end
7
+ 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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyFeatures
4
+ module ApplicationHelper
5
+ end
6
+ 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"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ DefraRubyFeatures::Engine.routes.draw do
4
+ resources :feature_toggles, except: %i[show edit], path: "feature-toggles"
5
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyFeatures
4
+ class Configuration
5
+ attr_accessor :feature_toggle_model_name
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration"
4
+
5
+ module DefraRubyFeatures
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace DefraRubyFeatures
8
+
9
+ config.generators do |g|
10
+ g.test_framework :rspec
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyFeatures
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "github_changelog_generator/task"
4
+
5
+ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
6
+ config.user = "DEFRA"
7
+ config.project = "defra-ruby-features"
8
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../stylesheets .css
3
+ //= link defra_ruby_features_manifest.js