43 |
44 | <%= render(partial: "profiles_modal") %>
45 | <%= render(partial: "guide_modal", locals: { settings: @settings }) %>
46 |
--------------------------------------------------------------------------------
/app/views/sail/settings/update.js.erb:
--------------------------------------------------------------------------------
1 | var notice = document.getElementById("<%= @successful_update ? "success-#{@setting.name}" : "alert-#{@setting.name}" %>");
2 | var submit = document.getElementById("btn-submit-<%= @setting.name %>");
3 |
4 | submit.style.display = "none";
5 | notice.style.display = "inline-block";
6 |
7 | setTimeout(function () {
8 | notice.style.display = "none";
9 | submit.style.display = "inline-block";
10 | }, 1500);
11 |
12 | if ("<%= @successful_update %>" === "true") {
13 | var input = document.getElementById("<%= "input_for_#{@setting.name}" %>");
14 | var submitButton = document.getElementById("<%= "btn-submit-#{@setting.name}" %>");
15 |
16 | if ("<%= @setting.boolean? %>" === "true") {
17 | input.checked = "<%= @setting.value %>" === "true";
18 | initialSettingValues["<%= @setting.name %>"] = "<%= @setting.value %>" === "true";
19 | } else {
20 | input.value = "<%= @setting.date? ? formatted_date(@setting) : @setting.value %>";
21 | initialSettingValues["<%= @setting.name %>"] = "<%= @setting.date? ? formatted_date(@setting) : @setting.value %>";
22 | }
23 |
24 | submitButton.classList.remove("orange");
25 | submitButton.disabled = true;
26 | }
27 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | # This command will automatically be run when you run "rails" with Rails gems
5 | # installed from the root of your application.
6 |
7 | ENGINE_ROOT = File.expand_path("..", __dir__)
8 | ENGINE_PATH = File.expand_path("../lib/sail/engine", __dir__)
9 | APP_PATH = File.expand_path("../spec/dummy/config/application", __dir__)
10 |
11 | # Set up gems listed in the Gemfile.
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
13 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
14 |
15 | require "rails/all"
16 | require "rails/engine/commands"
17 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | sail:
3 | page_title: Sail dashboard
4 | save: SAVE
5 | activate: ACTIVATE
6 | delete: DELETE
7 | search_placeholder: Setting name, group, cast type, stale or recent x
8 | search_tooltip: "When searching for recently updated settings, x is the number of hours since the update (e.g.: recent 2)"
9 | main_app: Main app
10 | no_settings: No settings found
11 | refresh_tooltip: Reset setting value
12 | next_page: Next page
13 | previous_page: Previous page
14 | stale: stale
15 | stale_tooltip: This setting has not been updated in %{days} days. Consider removing it and refactoring the code.
16 | order_button_tooltip: Sort settings in descending order by the selected field.
17 | relevancy_tooltip: The relevancy score is a relative indicator of how critical for the application each setting is.
18 | profiles_tooltip: Edit application profiles
19 | profile_created: Created
20 | profile_updated: Saved
21 | profile_switching: Switching..
22 | profile_deleted: Deleted
23 | profiles: Profiles
24 | new_profile_tooltip: "New profile: save a current snapshot of your settings"
25 | clean_profile_tooltip: Active profile. All changes saved.
26 | dirty_profile_tooltip: Active profile. Recent setting changes have not been saved.
27 | guide: Guide
28 | searching: Searching
29 | by_setting_name_html: By the setting name: will look for partial matches
30 | by_group_html: "By group: will find all settings in the same group (must be exact match)"
31 | by_cast_type_html: "By cast type: will find all settings with the same type (must be exact match)"
32 | by_stale_html: "By stale: will find settings that are stale (haven't been updated recently)"
33 | by_recent_html: "By recent: will find settings updated in the last X hours (e.g.: recent 50)"
34 | click_title: Click a setting's title to view its description.
35 | profiles_can_be_used: Profiles can be used to configure many states of settings. They save the values of all settings in a given moment.
36 | profile_configuring: Configure settings as desired and create a new profile. Activate profiles to change the value of all settings at once.
37 | relevancy_score: Relevancy Score
38 | relevancy_score_explanation: Settings have a number on the top right portion indicating their relevancy score. This metric is calculated based on the relative number of times the setting is used while the application is running. The higher the value the more the setting is used.
39 | available_groups_and_types: Available groups and types
40 | groups_are: "The groups currently used are:"
41 | types_are: "The cast types currently used are:"
42 | how_to_find_settings: How to find settings you are looking for
43 | how_to_profiles: How to organize your settings in profiles
44 | how_to_relevancy_score: What is the relevancy score and how to use it
45 | how_to_groups_and_types: List of available groups and types
46 | profile_errors:
47 | one: 1 error
48 | other: "%{count} errors"
49 | profile_error_tooltip: This is the number of unexpected errors raised for all settings while this profile was active
50 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Sail::Engine.routes.draw do
4 | root "settings#index"
5 |
6 | resources :settings, only: %i[index update show], param: :name do
7 | member do
8 | put "reset"
9 | end
10 | end
11 |
12 | get "settings/switcher/:positive/:negative/:throttled_by" => "settings#switcher"
13 |
14 | resources :profiles, only: %i[create destroy], param: :name do
15 | member do
16 | put "switch"
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/false_class.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Patch for Ruby's FalseClass
4 | class FalseClass
5 | def to_s
6 | Sail::ConstantCollection::FALSE
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/generators/sail/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails/generators/migration"
4 |
5 | module Sail
6 | module Generators
7 | # InstallGenerator
8 | # This is the install generator for Sail
9 | # which creates the necessary migrations
10 | class InstallGenerator < ::Rails::Generators::Base
11 | include Rails::Generators::Migration
12 |
13 | source_root File.expand_path("templates", __dir__)
14 | desc "Create Sail migrations"
15 |
16 | def self.next_migration_number(_path)
17 | if @prev_migration_nr
18 | @prev_migration_nr += 1
19 | else
20 | @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
21 | end
22 |
23 | @prev_migration_nr.to_s
24 | end
25 |
26 | def copy_migrations
27 | migration_template "create_sail_settings.rb",
28 | "db/migrate/create_sail_settings.rb",
29 | migration_version: migration_version
30 |
31 | migration_template "create_sail_profiles.rb",
32 | "db/migrate/create_sail_profiles.rb",
33 | migration_version: migration_version
34 | end
35 |
36 | def create_config_file
37 | template "sail.yml", "config/sail.yml"
38 | end
39 |
40 | def migration_version
41 | "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/generators/sail/install/templates/create_sail_profiles.rb:
--------------------------------------------------------------------------------
1 | class CreateSailProfiles < ActiveRecord::Migration<%= migration_version %>
2 | def change
3 | create_table :sail_entries do |t|
4 | t.string :value, null: false
5 | t.references :setting, index: true
6 | t.references :profile, index: true
7 | t.timestamps
8 | end
9 |
10 | create_table :sail_profiles do |t|
11 | t.string :name, null: false
12 | t.boolean :active, default: false
13 | t.index ["name"], name: "index_sail_profiles_on_name", unique: true
14 | t.timestamps
15 | end
16 |
17 | add_foreign_key(:sail_entries, :sail_settings, column: :setting_id)
18 | add_foreign_key(:sail_entries, :sail_profiles, column: :profile_id)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/generators/sail/install/templates/create_sail_settings.rb:
--------------------------------------------------------------------------------
1 | class CreateSailSettings < ActiveRecord::Migration<%= migration_version %>
2 | def change
3 | create_table :sail_settings do |t|
4 | t.string :name, null: false
5 | t.text :description
6 | t.string :value, null: false
7 | t.string :group
8 | t.integer :cast_type, null: false, limit: 1
9 | t.timestamps
10 | t.index ["name"], name: "index_settings_on_name", unique: true
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/generators/sail/install/templates/sail.yml.tt:
--------------------------------------------------------------------------------
1 | name_of_setting:
2 | description: Describe what the setting does
3 | value: 'true'
4 | cast_type: boolean
5 | group: feature_flags
6 |
--------------------------------------------------------------------------------
/lib/generators/sail/update/templates/add_group_to_sail_settings.rb:
--------------------------------------------------------------------------------
1 | class AddGroupToSailSettings < ActiveRecord::Migration<%= migration_version %>
2 | def change
3 | add_column(:sail_settings, :group, :string)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/generators/sail/update/update_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails/generators/migration"
4 |
5 | module Sail
6 | module Generators
7 | # UpdateGenerator
8 | #
9 | # The UpdateGenerator analyzes the current
10 | # state of the database and helps users
11 | # upgrade to the latest Sail version.
12 | class UpdateGenerator < ::Rails::Generators::Base
13 | include Rails::Generators::Migration
14 |
15 | source_root File.expand_path("templates", __dir__)
16 | desc "Update an application to Sail's latest version"
17 |
18 | def self.next_migration_number(_path)
19 | if @prev_migration_nr
20 | @prev_migration_nr += 1
21 | else
22 | @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
23 | end
24 |
25 | @prev_migration_nr.to_s
26 | end
27 |
28 | def copy_migrations
29 | # Add migration to add group to settings
30 | # if upgrading from Sail <= 1.x.x
31 |
32 | if Sail::Setting.column_names.exclude?("group")
33 | migration_template "add_group_to_sail_settings.rb",
34 | "db/migrate/add_group_to_sail_settings.rb",
35 | migration_version: migration_version
36 | end
37 |
38 | # Add migration to create profiles
39 | # if upgrading from Sail <= 2.x.x
40 |
41 | unless ActiveRecord::Base.connection.table_exists?("sail_profiles")
42 | migration_template "#{__dir__}/../install/templates/create_sail_profiles.rb",
43 | "db/migrate/create_sail_profiles.rb",
44 | migration_version: migration_version
45 | end
46 | end
47 |
48 | def migration_version
49 | "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
50 | end
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/generators/sail/views/views_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Generators
5 | # ViewsGenerator
6 | # Copies the customizable views to the main app
7 | class ViewsGenerator < ::Rails::Generators::Base
8 | source_root File.expand_path("../../../../app/views/sail", __dir__)
9 | desc "Copies customizable views to the parent application."
10 |
11 | def copy_views
12 | copy_file("settings/_setting.html.erb", "app/views/sail/settings/_setting.html.erb")
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/sail.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "sail/engine"
4 | require "true_class"
5 | require "false_class"
6 |
7 | module Sail # :nodoc:
8 | autoload :ConstantCollection, "sail/constant_collection"
9 | autoload :Configuration, "sail/configuration"
10 | autoload :Instrumenter, "sail/instrumenter"
11 | autoload :Types, "sail/types"
12 | autoload :Graphql, "sail/graphql"
13 |
14 | class << self
15 | attr_writer :configuration
16 |
17 | # Gets the value of a setting casted with the
18 | # appropriate type. Can be used with a block.
19 | #
20 | # Response is cached until the setting's value
21 | # is updated or until the time specific in
22 | # the configuration expires.
23 | #
24 | # Examples:
25 | #
26 | # Sail.get("my_setting")
27 | # => true
28 | #
29 | # Sail.get("my_setting") do |setting_value|
30 | # execute_code if setting_value
31 | # end
32 | def get(name, expected_errors: [])
33 | setting_value = Sail::Setting.get(name)
34 |
35 | block_given? ? yield(setting_value) : setting_value
36 | rescue StandardError => e
37 | instrumenter.increment_failure_of(name) unless expected_errors.blank? || expected_errors.include?(e.class)
38 | raise e
39 | end
40 |
41 | # Sets the value of a setting
42 | #
43 | # Updating a setting's value will cause its
44 | # cache to expire.
45 | #
46 | # Passed values are cast to string before
47 | # saving to the database. For instance,
48 | # the statement below will appropriately
49 | # update the setting value to "true".
50 | #
51 | # Sail.set(:boolean_setting, true)
52 | #
53 | def set(name, value)
54 | Sail::Setting.set(name, value)
55 | end
56 |
57 | # Resets the value of a setting
58 | #
59 | # Restores the original value defined
60 | # in config/sail.yml
61 | def reset(name)
62 | Sail::Setting.reset(name)
63 | end
64 |
65 | # Switches between the value of two settings randomly
66 | #
67 | # +throttled_by+: a throttle type setting
68 | # +positive+: a setting to be returned when the throttle returns true
69 | # +negative+: a setting to be returned when the throttle returns false
70 | #
71 | # Based on the +throttled_by+ setting, this method will
72 | # return either the value of +positive+ or +negative+.
73 | #
74 | # If +throttled_by+ returns true, the casted value of +positive+
75 | # is returned. When false, the casted value of +negative+ is returned.
76 | def switcher(positive:, negative:, throttled_by:)
77 | Sail::Setting.switcher(positive: positive, negative: negative, throttled_by: throttled_by)
78 | end
79 |
80 | def configuration
81 | @configuration ||= Configuration.new
82 | end
83 |
84 | def configure
85 | yield(configuration)
86 | end
87 |
88 | def instrumenter
89 | @instrumenter ||= Instrumenter.new
90 | end
91 | end
92 | end
93 |
--------------------------------------------------------------------------------
/lib/sail/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | # Configuration
5 | # This class keeps the configuration
6 | # data for the gem.
7 | # Defaults be found here and can be
8 | # overridden in an initializer, environment
9 | # file or application.rb
10 | class Configuration
11 | attr_accessor :cache_life_span, :array_separator, :dashboard_auth_lambda,
12 | :back_link_path, :enable_search_auto_submit, :days_until_stale,
13 | :enable_logging, :failures_until_reset
14 |
15 | def initialize
16 | @cache_life_span = 6.hours
17 | @array_separator = ";"
18 | @dashboard_auth_lambda = nil
19 | @back_link_path = "root_path"
20 | @enable_search_auto_submit = true
21 | @days_until_stale = 60
22 | @enable_logging = true
23 | @failures_until_reset = 50
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/sail/constant_collection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | # ConstantCollection
5 | #
6 | # This module includes a variety of
7 | # constants that are used multiple times
8 | # in the code to avoid unnecessary allocations.
9 | module ConstantCollection
10 | TRUE = "true"
11 | FALSE = "false"
12 | STRING_BOOLEANS = %w[true false].freeze
13 | BOOLEAN = "boolean"
14 | ON = "on"
15 | BOOLEANS = [true, false].freeze
16 | CONFIG_FILE_PATH = "./config/sail.yml"
17 | STALE = "stale"
18 | RECENT = "recent"
19 | FIELDS_FOR_SORT = %w[name updated_at cast_type group].freeze
20 | SETTINGS_PER_PAGE = 20
21 | INPUT_DATE_FORMAT = "%Y-%m-%dT%H:%m:%S"
22 | MAX_PAGES = 5
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/sail/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | # Engine
5 | # Defines initializers and
6 | # after initialize hooks
7 | class Engine < ::Rails::Engine
8 | require "sprockets/railtie"
9 | isolate_namespace Sail
10 |
11 | config.generators do |g|
12 | g.test_framework :rspec
13 | end
14 |
15 | config.middleware.use ActionDispatch::Flash
16 | config.middleware.use ActionDispatch::Cookies
17 | config.middleware.use ActionDispatch::ContentSecurityPolicy::Middleware if defined?(ActionDispatch::ContentSecurityPolicy)
18 | config.middleware.use Rack::MethodOverride
19 | config.middleware.use Rails::Rack::Logger
20 | config.middleware.use Rack::Head
21 | config.middleware.use Rack::ConditionalGet
22 | config.middleware.use Rack::ETag
23 |
24 | initializer "sail.assets.precompile" do |app|
25 | app.config.assets.precompile += %w[sail/undo.svg sail/sliders-h.svg sail/angle-left.svg
26 | sail/angle-right.svg sail/external-link-alt.svg sail/cog.svg sail/check.svg
27 | sail/times.svg sail/application.css sail/application.js]
28 | end
29 |
30 | initializer "sail" do
31 | unless Sail.configuration.dashboard_auth_lambda.nil?
32 | ActiveSupport::Reloader.to_prepare do
33 | Sail::SettingsController.before_action(Sail.configuration.dashboard_auth_lambda)
34 | end
35 | end
36 | end
37 |
38 | config.after_initialize do
39 | errors = [ActiveRecord::NoDatabaseError]
40 | errors << PG::ConnectionBad if defined?(PG)
41 |
42 | config.middleware.use Rails.application.config.session_store || ActionDispatch::Session::CookieStore
43 |
44 | begin
45 | Sail::Setting.load_defaults unless Rails.env.test?
46 | rescue *errors
47 | warn "Skipping setting creation because database doesn't exist"
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/sail/graphql.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # :nocov:
4 | module Sail
5 | # Graphql
6 | #
7 | # Module to include type definitions
8 | # for GraphQL APIs.
9 | module Graphql
10 | autoload :Mutations, "sail/mutations"
11 |
12 | module Types # :nodoc:
13 | extend ActiveSupport::Concern
14 |
15 | included do
16 | field :sail_get, ::GraphQL::Types::JSON, null: true do
17 | description "Returns the value for a given setting."
18 | argument :name, String, required: true, description: "The setting's name."
19 | end
20 |
21 | field :sail_switcher, ::GraphQL::Types::JSON, null: true do
22 | description "Switches between the positive or negative setting based on the throttle."
23 | argument :positive, String, required: true, description: "The setting's name if the throttle is bigger than the desired amount."
24 | argument :negative, String, required: true, description: "The setting's name if the throttle is smaller than the desired amount."
25 | argument :throttled_by, String, required: true, description: "The throttle setting's name."
26 | end
27 |
28 | def sail_get(name:)
29 | Sail.get(name)
30 | end
31 |
32 | def sail_switcher(positive:, negative:, throttled_by:)
33 | Sail.switcher(
34 | positive: positive,
35 | negative: negative,
36 | throttled_by: throttled_by
37 | )
38 | end
39 | end
40 | end
41 | end
42 | end
43 | # :nocov:
44 |
--------------------------------------------------------------------------------
/lib/sail/instrumenter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | # Instrumenter
5 | #
6 | # Class containing methods to instrument
7 | # setting usage and provide insights to
8 | # dashboard users.
9 | class Instrumenter
10 | USAGES_UNTIL_CACHE_EXPIRE = 500
11 |
12 | # initialize
13 | #
14 | # Declare basic hash containing setting
15 | # statistics
16 | def initialize
17 | @statistics = { settings: {}, profiles: {} }.with_indifferent_access
18 | @number_of_settings = Setting.count
19 | end
20 |
21 | # []
22 | #
23 | # Accessor method for setting statistics to guarantee
24 | # proper initialization of hashes.
25 | def [](name)
26 | @statistics[:settings][name] = { usages: 0, failures: 0 }.with_indifferent_access if @statistics[:settings][name].blank?
27 | @statistics[:settings][name]
28 | end
29 |
30 | # increment_profile_failure_of
31 | #
32 | # Increments the number of failures
33 | # for settings while a profile is active
34 | def increment_profile_failure_of(name)
35 | @statistics[:profiles][name] ||= 0
36 | @statistics[:profiles][name] += 1
37 | end
38 |
39 | # profile
40 | #
41 | # Profile statistics accessor
42 | def profile(name)
43 | @statistics[:profiles][name] ||= 0
44 | end
45 |
46 | # increment_usage
47 | #
48 | # Simply increments the number of
49 | # times a setting has been called
50 | def increment_usage_of(setting_name)
51 | self[setting_name][:usages] += 1
52 | end
53 |
54 | # relative_usage_of
55 | #
56 | # Calculates the relative usage of
57 | # a setting compared to all others
58 | # in percentage
59 | def relative_usage_of(setting_name)
60 | return 0.0 if @statistics[:settings].empty?
61 |
62 | total_usages = @statistics[:settings].sum { |_, entry| entry[:usages] }
63 | return 0.0 if total_usages.zero?
64 |
65 | (100.0 * self[setting_name][:usages]) / total_usages
66 | end
67 |
68 | # increment_failure_of
69 | #
70 | # Counts the number of failed code block executions
71 | # enveloped by a given setting. If the number of failures
72 | # exceeds the amount configured, resets the setting value
73 | def increment_failure_of(setting_name)
74 | self[setting_name][:failures] += 1
75 |
76 | current_profile = Profile.current
77 | increment_profile_failure_of(current_profile.name) if current_profile
78 |
79 | Sail.reset(setting_name) if self[setting_name][:failures] > Sail.configuration.failures_until_reset
80 | end
81 |
82 | def relevancy_of(setting_name)
83 | (relative_usage_of(setting_name) / @number_of_settings).round(1)
84 | end
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/lib/sail/mutations.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # :nocov:
4 | module Sail
5 | module Graphql
6 | module Mutations # :nodoc:
7 | extend ActiveSupport::Concern
8 |
9 | included do
10 | field :sail_set, mutation: SailSet do
11 | description "Set the value for a setting."
12 | argument :name, String, required: true
13 | argument :value, String, required: true
14 | end
15 |
16 | field :sail_profile_switch, mutation: SailProfileSwitch do
17 | description "Switches to the chosen profile."
18 | argument :name, String, required: true
19 | end
20 | end
21 |
22 | class SailSet < ::GraphQL::Schema::Mutation # :nodoc:
23 | argument :name, String, required: true
24 | argument :value, String, required: true
25 |
26 | field :success, Boolean, null: false
27 |
28 | def resolve(name:, value:)
29 | _, success = Sail.set(name, value)
30 | { success: success }
31 | end
32 | end
33 |
34 | class SailProfileSwitch < ::GraphQL::Schema::Mutation # :nodoc:
35 | argument :name, String, required: true
36 |
37 | field :success, Boolean, null: false
38 |
39 | def resolve(name:)
40 | success = Profile.exists?(name: name)
41 | Profile.switch(name)
42 |
43 | { success: success }
44 | end
45 | end
46 | end
47 | end
48 | end
49 | # :nocov:
50 |
--------------------------------------------------------------------------------
/lib/sail/railtie.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | # Load custom rake tasks in main
5 | # application
6 | class Railtie < Rails::Railtie
7 | rake_tasks do
8 | load "tasks/sail_tasks.rake"
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/sail/types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | # Types
5 | #
6 | # This module holds all setting types
7 | # classes
8 | module Types
9 | autoload :Type, "sail/types/type"
10 | autoload :Boolean, "sail/types/boolean"
11 | autoload :Integer, "sail/types/integer"
12 | autoload :AbTest, "sail/types/ab_test"
13 | autoload :Array, "sail/types/array"
14 | autoload :Cron, "sail/types/cron"
15 | autoload :Date, "sail/types/date"
16 | autoload :Float, "sail/types/float"
17 | autoload :Locales, "sail/types/locales"
18 | autoload :ObjModel, "sail/types/obj_model"
19 | autoload :Range, "sail/types/range"
20 | autoload :String, "sail/types/string"
21 | autoload :Throttle, "sail/types/throttle"
22 | autoload :Uri, "sail/types/uri"
23 | autoload :Set, "sail/types/set"
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/sail/types/ab_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # AbTest
6 | #
7 | # The AbTest setting type returns
8 | # true or false randomly (50% chance).
9 | class AbTest < Boolean
10 | def to_value
11 | if @setting.value == Sail::ConstantCollection::TRUE
12 | Sail::ConstantCollection::BOOLEANS.sample
13 | else
14 | false
15 | end
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/sail/types/array.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Array
6 | #
7 | # This type allows defining an
8 | # array using a string and a separator
9 | # (defined in the configuration).
10 | class Array < Type
11 | def to_value
12 | @setting.value.split(Sail.configuration.array_separator)
13 | end
14 |
15 | def from(value)
16 | value.is_a?(::String) ? value : value.join(Sail.configuration.array_separator)
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/sail/types/boolean.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Boolean
6 | #
7 | # The Boolean type simply returns true
8 | # or false depending on what is stored
9 | # in the database.
10 | class Boolean < Type
11 | def to_value
12 | @setting.value == Sail::ConstantCollection::TRUE
13 | end
14 |
15 | def from(value)
16 | if value.is_a?(::String)
17 | check_for_on_or_boolean(value)
18 | elsif value.nil?
19 | Sail::ConstantCollection::FALSE
20 | else
21 | value.to_s
22 | end
23 | end
24 |
25 | private
26 |
27 | def check_for_on_or_boolean(value)
28 | value == Sail::ConstantCollection::ON ? Sail::ConstantCollection::TRUE : value
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/sail/types/cron.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Cron
6 | #
7 | # The Cron type returns true
8 | # if the saved cron string
9 | # matches the current time
10 | # (ignores seconds).
11 | class Cron < Type
12 | def to_value
13 | Fugit::Cron.new(@setting.value).match?(DateTime.now.utc.change(sec: 0))
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/sail/types/date.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Date
6 | #
7 | # The Date type parses the saved
8 | # string into a DateTime object.
9 | class Date < Type
10 | def to_value
11 | DateTime.parse(@setting.value).utc
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/sail/types/float.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Float
6 | #
7 | # The Float type manipulates the
8 | # saved string value into floats.
9 | class Float < Type
10 | def to_value
11 | @setting.value.to_f
12 | end
13 |
14 | def from(value)
15 | value.to_f
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/sail/types/integer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Integer
6 | #
7 | # The Integer type manipulates the
8 | # saved string value into integers.
9 | class Integer < Type
10 | def to_value
11 | @setting.value.to_i
12 | end
13 |
14 | def from(value)
15 | value.to_i
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/sail/types/locales.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Locales
6 | #
7 | # Locales settings will keep an array of locales.
8 | # If the current I18n.locale is included in the array,
9 | # the setting will return true.
10 | class Locales < Array
11 | def to_value
12 | @setting.value
13 | .split(Sail.configuration.array_separator)
14 | .include?(I18n.locale.to_s)
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/sail/types/obj_model.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # ObjModel
6 | #
7 | # The ObjModel type returns the constant
8 | # for a given string saved.
9 | # For example:
10 | #
11 | # If the saved value is +"Post"+,
12 | # it will return +Post+ (actual class).
13 | class ObjModel < Type
14 | def to_value
15 | @setting.value.constantize
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/sail/types/range.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Range
6 | #
7 | # The Range type is similar to an
8 | # integer, but has a minimum value
9 | # of 0 and a maximum value of 99.
10 | class Range < Integer; end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/sail/types/set.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Set
6 | #
7 | # This type allows defining a set
8 | # using a string and a separator
9 | # (defined in the configuration).
10 | class Set < Type
11 | def to_value
12 | ::Set[*@setting.value.split(Sail.configuration.array_separator)]
13 | end
14 |
15 | def from(value)
16 | value.is_a?(::String) ? value : value.join(Sail.configuration.array_separator)
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/sail/types/string.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # String
6 | #
7 | # The String setting type
8 | # is the simplest. It only
9 | # stores a configurable value
10 | # in the database.
11 | class String < Type; end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/sail/types/throttle.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Throttle
6 | #
7 | # The Throttle type returns true +X+%
8 | # of the time (randomly), where +X+ is
9 | # the value saved in the database.
10 | #
11 | # Example:
12 | #
13 | # If the setting value is 30, it will
14 | # return +true+ 30% of the time.
15 | class Throttle < Type
16 | def to_value
17 | 100 * rand <= @setting.value.to_f
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/sail/types/type.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Type
6 | #
7 | # This is the base class all types
8 | # inherit from. It is an abstract class
9 | # not supposed to be instantiated.
10 | class Type
11 | def initialize(setting)
12 | @setting = setting
13 | end
14 |
15 | def to_value
16 | @setting.value.to_s
17 | end
18 |
19 | def from(value)
20 | value
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/sail/types/uri.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | module Types
5 | # Uri
6 | #
7 | # The Uri type returns an URI
8 | # object based on the string
9 | # saved in the database.
10 | class Uri < Type
11 | def to_value
12 | URI(@setting.value)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/sail/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sail
4 | VERSION = "3.6.1"
5 | end
6 |
--------------------------------------------------------------------------------
/lib/tasks/sail_tasks.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | namespace :sail do
4 | desc "Loads default setting configurations from sail.yml"
5 | task load_defaults: :environment do
6 | Sail::Setting.load_defaults(true)
7 | end
8 |
9 | desc "Creates sail.yml using the current state of the database"
10 | task create_config_file: :environment do
11 | Sail::Setting.database_to_file
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/true_class.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Patch for Ruby's TrueClass
4 | class TrueClass
5 | def to_s
6 | Sail::ConstantCollection::TRUE
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/sail.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $LOAD_PATH.push File.expand_path("lib", __dir__)
4 | require "sail/version"
5 |
6 | Gem::Specification.new do |s|
7 | s.name = "sail"
8 | s.version = Sail::VERSION
9 | s.authors = ["Vinicius Stock"].freeze
10 | s.email = ["vinicius.stock@outlook.com"].freeze
11 | s.homepage = "https://github.com/vinistock/sail"
12 | s.summary = "Sail is a lightweight Rails engine that brings an admin panel for managing configuration settings on a live Rails app."
13 | s.description = "Sail is a lightweight Rails engine that brings an admin panel for managing configuration settings on a live Rails app."
14 | s.license = "MIT"
15 |
16 | s.files = Dir["{app,config,lib}/**/*",
17 | "MIT-LICENSE",
18 | "Rakefile",
19 | "README.md"].reject { |path| path.include?("sail.gif") }
20 |
21 | s.required_ruby_version = ">= 2.5.0"
22 |
23 | s.add_dependency "fugit"
24 | s.add_dependency "rails", ">= 5.0.0"
25 | s.metadata = {
26 | "rubygems_mfa_required" => "true"
27 | }
28 | end
29 |
--------------------------------------------------------------------------------
/spec/controllers/sail/profiles_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe Sail::ProfilesController, type: :controller do
4 | routes { Sail::Engine.routes }
5 |
6 | describe "POST create" do
7 | subject(:request) do
8 | post :create, params: { name: "profile" }, format: :js
9 | end
10 |
11 | it "returns ok" do
12 | request
13 | expect(response).to have_http_status(:ok)
14 | end
15 |
16 | it "invokes create_or_update_self from profiles" do
17 | expect(Sail::Profile).to receive(:create_or_update_self).with("profile").and_call_original
18 | request
19 | end
20 |
21 | context "when a profile with the same name exists" do
22 | before do
23 | Sail::Profile.create!(name: :profile)
24 | end
25 |
26 | it "returns ok for an update" do
27 | request
28 | expect(response).to have_http_status(:ok)
29 | end
30 | end
31 | end
32 |
33 | describe "PUT switch" do
34 | subject(:request) do
35 | put :switch, params: { name: "profile" }, format: :js
36 | end
37 |
38 | it "returns ok" do
39 | request
40 | expect(response).to have_http_status(:ok)
41 | end
42 |
43 | it "invokes switch from profiles" do
44 | expect(Sail::Profile).to receive(:switch).with("profile")
45 | request
46 | end
47 | end
48 |
49 | describe "DELETE destroy" do
50 | subject(:request) do
51 | delete :destroy, params: { name: "profile" }, format: :js
52 | end
53 |
54 | let!(:profile) { Sail::Profile.create!(name: :profile) }
55 |
56 | it "returns ok" do
57 | request
58 | expect(response).to have_http_status(:ok)
59 | end
60 |
61 | it "destroys profile" do
62 | expect { request }.to change(Sail::Profile, :count).by(-1)
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/controllers/sail/settings_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe Sail::SettingsController, type: :controller do
4 | routes { Sail::Engine.routes }
5 | before do
6 | Rails.cache.delete("setting_get_setting")
7 | allow(Rails.logger).to receive(:info)
8 | end
9 |
10 | describe "GET index" do
11 | subject { get :index, params: params }
12 |
13 | let(:params) { { page: "1" } }
14 |
15 | before do
16 | Sail::Setting.create!(name: :setting, cast_type: :string, value: :something)
17 | end
18 |
19 | it "queries settings with pagination" do
20 | expect(Sail::Setting).to receive(:paginated).with("1", 20)
21 | subject
22 | expect(response).to have_http_status(:ok)
23 | end
24 |
25 | it "sets eTag in response headers" do
26 | subject
27 | expect(response.headers["ETag"]).to_not be_nil
28 | end
29 |
30 | it "sets the number of pages" do
31 | subject
32 | expect(controller.instance_variable_get(:@number_of_pages)).to eq(1)
33 | end
34 |
35 | context "when passing a query" do
36 | let(:params) { { query: "test" } }
37 |
38 | it "invokes proper scope with query" do
39 | expect(Sail::Setting).to receive(:by_name).with("test").and_call_original
40 | subject
41 | end
42 | end
43 |
44 | context "when passing a field for ordering" do
45 | let(:params) { { order_field: "updated_at" } }
46 | let!(:setting) { Sail::Setting.create!(name: :setting_2, cast_type: :string, value: :something_else, updated_at: 2.days.ago) }
47 |
48 | it "invokes ordered_by properly" do
49 | subject
50 | expect(controller.instance_variable_get(:@settings).last.name).to eq(setting.name)
51 | end
52 | end
53 | end
54 |
55 | describe "PUT update" do
56 | subject { put :update, params: { name: setting.name, value: new_value, cast_type: setting.cast_type }, format: :js }
57 |
58 | let!(:setting) { Sail::Setting.create(name: :setting, cast_type: :string, value: "old value") }
59 | let(:new_value) { "new value" }
60 |
61 | it "updates setting value" do
62 | expect(setting.value).to eq("old value")
63 | subject
64 | expect(response).to have_http_status(:ok)
65 | expect(setting.reload.value).to eq("new value")
66 | end
67 |
68 | it "logs change information" do
69 | expect(Rails.logger).to receive(:info).with(/.* \[Sail\] Update setting='setting' value='new value' author_user_id=1/)
70 | subject
71 | end
72 |
73 | context "when setting is boolean" do
74 | let!(:setting) { Sail::Setting.create(name: :setting, cast_type: :boolean, value: "false") }
75 | let(:new_value) { "on" }
76 |
77 | it "updates setting value" do
78 | expect(setting.value).to eq("false")
79 | subject
80 | expect(response).to have_http_status(:ok)
81 | expect(setting.reload.value).to eq("true")
82 | end
83 | end
84 |
85 | context "when format is JSON" do
86 | subject { put :update, params: { name: setting.name, value: new_value, cast_type: setting.cast_type }, format: :json }
87 |
88 | it "updates setting value" do
89 | expect(setting.value).to eq("old value")
90 | subject
91 | expect(response).to have_http_status(:ok)
92 | expect(setting.reload.value).to eq("new value")
93 | end
94 |
95 | context "when update fails" do
96 | before do
97 | allow(Sail::Setting).to receive(:set).and_return([nil, false])
98 | end
99 |
100 | it "returns http status conflict" do
101 | subject
102 | expect(response).to have_http_status(:conflict)
103 | end
104 |
105 | it "does not log changes" do
106 | expect(Rails.logger).to_not receive(:info).with(/.* \[Sail\] Update setting='setting' value='new value' author_user_id=1/)
107 | subject
108 | end
109 | end
110 | end
111 | end
112 |
113 | describe "GET show" do
114 | subject { get :show, params: params, format: :json }
115 |
116 | let!(:setting) { Sail::Setting.create(name: :setting, cast_type: :string, value: "some value") }
117 | let(:params) { { name: setting.name } }
118 |
119 | it "returns setting value" do
120 | subject
121 | body = JSON.parse(response.body)
122 | expect(body["value"]).to eq(setting.value)
123 | end
124 |
125 | it "returns 200 status" do
126 | subject
127 | expect(response).to have_http_status(:ok)
128 | end
129 |
130 | it "responds in json format" do
131 | subject
132 |
133 | type = Rails::VERSION::MAJOR < 6 ? response.content_type : response.media_type
134 |
135 | expect(type).to eq("application/json")
136 | end
137 | end
138 |
139 | describe "GET switcher" do
140 | subject { get :switcher, params: params, format: :json }
141 |
142 | let!(:throttle) { Sail::Setting.create(name: :throttle, cast_type: :throttle, value: "50.0") }
143 | let(:params) { { positive: :positive, negative: :negative, throttled_by: :throttle } }
144 |
145 | before do
146 | Rails.cache.delete("setting_get_positive")
147 | Rails.cache.delete("setting_get_negative")
148 | Rails.cache.delete("setting_get_throttle")
149 | Sail::Setting.create!(name: :positive, cast_type: :string, value: "I'm the primary!")
150 | Sail::Setting.create!(name: :negative, cast_type: :integer, value: "7")
151 | allow_any_instance_of(Sail::Types::Throttle).to receive(:rand).and_return(random_value)
152 | end
153 |
154 | context "when random value is smaller than throttle" do
155 | let(:random_value) { 0.25 }
156 |
157 | it "returns ok status" do
158 | subject
159 | expect(response).to have_http_status(:ok)
160 | end
161 |
162 | it "returns value of positive setting" do
163 | subject
164 |
165 | body = JSON.parse(response.body)
166 | expect(body["value"]).to eq("I'm the primary!")
167 | end
168 | end
169 |
170 | context "when random value is greater than throttle" do
171 | let(:random_value) { 0.75 }
172 |
173 | it "returns ok status" do
174 | subject
175 | expect(response).to have_http_status(:ok)
176 | end
177 |
178 | it "returns value of negative setting" do
179 | subject
180 |
181 | body = JSON.parse(response.body)
182 | expect(body["value"]).to eq(7)
183 | end
184 | end
185 |
186 | context "when throttle setting is of the wrong type" do
187 | let!(:throttle) { Sail::Setting.create!(name: :throttle, cast_type: :boolean, value: "true") }
188 | let(:random_value) { 0.75 }
189 |
190 | it "returns bad request" do
191 | subject
192 | expect(response).to have_http_status(:bad_request)
193 | end
194 | end
195 |
196 | context "when throttle setting does not exist" do
197 | let!(:throttle) { Sail::Setting.create!(name: :wrong_name, cast_type: :boolean, value: "true") }
198 | let(:random_value) { 0.75 }
199 |
200 | it "returns not found" do
201 | subject
202 | expect(response).to have_http_status(:not_found)
203 | end
204 | end
205 | end
206 |
207 | describe "PUT reset" do
208 | subject { put :reset, params: { name: setting.name }, format: :js }
209 |
210 | let!(:setting) { Sail::Setting.create(name: :setting, cast_type: :string, value: "old value") }
211 | let(:file_contents) { { "setting" => { "value" => "new value" } } }
212 |
213 | before do
214 | allow(File).to receive(:exist?)
215 | allow(Sail::Setting).to receive(:config_file_path).and_return("./config/sail.yml")
216 | allow(File).to receive(:exist?).with("./config/sail.yml").and_return(true)
217 | allow(YAML).to receive(:load_file).with("./config/sail.yml").and_return(file_contents)
218 | end
219 |
220 | it "resets setting value" do
221 | expect(Sail::Setting).to receive(:reset).with("setting").and_call_original
222 | subject
223 | expect(response).to have_http_status(:ok)
224 | expect(setting.reload.value).to eq("new value")
225 | end
226 |
227 | it "logs change information" do
228 | expect(Rails.logger).to receive(:info).with(/.* \[Sail\] Reset setting='setting' value='new value' author_user_id=1/)
229 | subject
230 | end
231 |
232 | context "when update fails" do
233 | before do
234 | allow(Sail::Setting).to receive(:set).and_return([nil, false])
235 | end
236 |
237 | it "does not log changes" do
238 | expect(Rails.logger).to_not receive(:info).with(/.* \[Sail\] Update setting='setting' value='new value' author_user_id=1/)
239 | subject
240 | end
241 | end
242 | end
243 | end
244 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'config/application'
4 | Rails.application.load_tasks
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 |
2 | //= link_tree ../images
3 | //= link_directory ../javascripts .js
4 | //= link_directory ../stylesheets .css
5 | //= link sail_manifest.js
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinistock/sail/65f7bcadd155fff87088d15730356cd5ce33dd17/spec/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js.erb:
--------------------------------------------------------------------------------
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 | <%
14 | require_asset("rails-ujs")
15 | %>
16 | //= require_tree .
17 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/cable.js.erb:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | <% require_asset("action_cable") %>
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinistock/sail/65f7bcadd155fff87088d15730356cd5ce33dd17/spec/dummy/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/spec/dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 | before_action :set_locale
4 |
5 | def index
6 | random_setting = Sail::Setting.pluck(:name)
7 | 5.times { Sail.get(random_setting.sample) }
8 | end
9 |
10 | protected
11 |
12 | def set_locale
13 | I18n.locale = params[:locale] || :en
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinistock/sail/65f7bcadd155fff87088d15730356cd5ce33dd17/spec/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinistock/sail/65f7bcadd155fff87088d15730356cd5ce33dd17/spec/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/namespace/my_model.rb:
--------------------------------------------------------------------------------
1 | class Namespace::MyModel < ApplicationRecord
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/test.rb:
--------------------------------------------------------------------------------
1 | class Test < ApplicationRecord
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/test2.rb:
--------------------------------------------------------------------------------
1 | class Test2 < ApplicationRecord
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/application/index.html.erb:
--------------------------------------------------------------------------------
1 |
Inside dummy app
2 |
3 | <%= link_to("Sail", "/sail?locale=#{params[:locale]}", method: :get) %>
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag 'application', media: 'all' %>
6 | <%= javascript_include_tag 'application', async: true %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | # Install JavaScript dependencies if using Yarn
22 | # system('bin/yarn')
23 |
24 |
25 | # puts "\n== Copying sample files =="
26 | # unless File.exist?('config/database.yml')
27 | # cp 'config/database.yml.sample', 'config/database.yml'
28 | # end
29 |
30 | puts "\n== Preparing database =="
31 | system! 'bin/rails db:setup'
32 |
33 | puts "\n== Removing old logs and tempfiles =="
34 | system! 'bin/rails log:clear tmp:clear'
35 |
36 | puts "\n== Restarting application server =="
37 | system! 'bin/rails restart'
38 | end
39 |
--------------------------------------------------------------------------------
/spec/dummy/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/spec/dummy/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | VENDOR_PATH = File.expand_path('..', __dir__)
3 | Dir.chdir(VENDOR_PATH) do
4 | begin
5 | exec "yarnpkg #{ARGV.join(" ")}"
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'config/environment'
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'boot'
4 |
5 | require 'rails/all'
6 |
7 | Bundler.require(*Rails.groups)
8 | require 'sail'
9 |
10 | module Dummy
11 | class Application < Rails::Application
12 | if Rails::VERSION::MAJOR < 6
13 | config.active_record.sqlite3.represent_boolean_as_integer = true
14 | config.load_defaults 5.0
15 | else
16 | config.load_defaults 6.1
17 | end
18 |
19 | Sail.configure do |config|
20 | config.enable_search_auto_submit = true
21 | end
22 |
23 | I18n.available_locales = %i[en es fr]
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
5 |
6 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
7 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'application'
4 | Rails.application.initialize!
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded on
7 | # every request. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable/disable caching. By default caching is disabled.
18 | if Rails.root.join('tmp/caching-dev.txt').exist?
19 | config.action_controller.perform_caching = true
20 |
21 | config.cache_store = :memory_store
22 |
23 | config.public_file_server.headers = {
24 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
25 | }
26 | else
27 | config.action_controller.perform_caching = false
28 |
29 | config.cache_store = :null_store
30 | end
31 |
32 | # Don't care if the mailer can't send.
33 | config.action_mailer.raise_delivery_errors = false
34 |
35 | config.action_mailer.perform_caching = false
36 |
37 | # Print deprecation notices to the Rails logger.
38 | config.active_support.deprecation = :log
39 |
40 | # Raise an error on page load if there are pending migrations.
41 | config.active_record.migration_error = :page_load
42 |
43 | # Debug mode disables concatenation and preprocessing of assets.
44 | # This option may cause significant delays in view rendering with a large
45 | # number of complex assets.
46 | config.assets.debug = true
47 |
48 | # Suppress logger output for asset requests.
49 | config.assets.quiet = true
50 |
51 | # Raises error for missing translations
52 | # config.action_view.raise_on_missing_translations = true
53 |
54 | # Use an evented file watcher to asynchronously detect changes in source code,
55 | # routes, locales, etc. This feature depends on the listen gem.
56 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
57 | end
58 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
20 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
21 | # `config/secrets.yml.key`.
22 | config.read_encrypted_secrets = true
23 |
24 | # Disable serving static files from the `/public` folder by default since
25 | # Apache or NGINX already handles this.
26 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
27 |
28 | # Compress JavaScripts and CSS.
29 | config.assets.js_compressor = :uglifier
30 | # config.assets.css_compressor = :sass
31 |
32 | # Do not fallback to assets pipeline if a precompiled asset is missed.
33 | config.assets.compile = false
34 |
35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
36 |
37 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
38 | # config.action_controller.asset_host = 'http://assets.example.com'
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Mount Action Cable outside main process or domain
45 | # config.action_cable.mount_path = nil
46 | # config.action_cable.url = 'wss://example.com/cable'
47 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
48 |
49 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
50 | # config.force_ssl = true
51 |
52 | # Use the lowest log level to ensure availability of diagnostic information
53 | # when problems arise.
54 | config.log_level = :debug
55 |
56 | # Prepend all log lines with the following tags.
57 | config.log_tags = [ :request_id ]
58 |
59 | # Use a different cache store in production.
60 | # config.cache_store = :mem_cache_store
61 |
62 | # Use a real queuing backend for Active Job (and separate queues per environment)
63 | # config.active_job.queue_adapter = :resque
64 | # config.active_job.queue_name_prefix = "dummy_#{Rails.env}"
65 | config.action_mailer.perform_caching = false
66 |
67 | # Ignore bad email addresses and do not raise email delivery errors.
68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
69 | # config.action_mailer.raise_delivery_errors = false
70 |
71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
72 | # the I18n.default_locale when a translation cannot be found).
73 | config.i18n.fallbacks = true
74 |
75 | # Send deprecation notices to registered listeners.
76 | config.active_support.deprecation = :notify
77 |
78 | # Use default logging formatter so that PID and timestamp are not suppressed.
79 | config.log_formatter = ::Logger::Formatter.new
80 |
81 | # Use a different logger for distributed setups.
82 | # require 'syslog/logger'
83 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
84 |
85 | if ENV["RAILS_LOG_TO_STDOUT"].present?
86 | logger = ActiveSupport::Logger.new(STDOUT)
87 | logger.formatter = config.log_formatter
88 | config.logger = ActiveSupport::TaggedLogging.new(logger)
89 | end
90 |
91 | # Do not dump schema after migrations.
92 | config.active_record.dump_schema_after_migration = false
93 | end
94 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # The test environment is used exclusively to run your application's
7 | # test suite. You never need to work with it otherwise. Remember that
8 | # your test database is "scratch space" for the test suite and is wiped
9 | # and recreated between test runs. Don't rely on the data there!
10 | config.cache_classes = true
11 |
12 | # Do not eager load code on boot. This avoids loading your whole application
13 | # just for the purpose of running a single test. If you are using a tool that
14 | # preloads Rails for running tests, you may have to set it to true.
15 | config.eager_load = false
16 |
17 | # Configure public file server for tests with Cache-Control for performance.
18 | config.public_file_server.enabled = true
19 | config.public_file_server.headers = {
20 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}"
21 | }
22 |
23 | # Show full error reports and disable caching.
24 | config.consider_all_requests_local = true
25 | config.action_controller.perform_caching = false
26 |
27 | # Raise exceptions instead of rendering exception templates.
28 | config.action_dispatch.show_exceptions = false
29 |
30 | # Disable request forgery protection in test environment.
31 | config.action_controller.allow_forgery_protection = false
32 | config.action_mailer.perform_caching = false
33 |
34 | # Tell Action Mailer not to deliver emails to the real world.
35 | # The :test delivery method accumulates sent emails in the
36 | # ActionMailer::Base.deliveries array.
37 | config.action_mailer.delivery_method = :test
38 |
39 | # Print deprecation notices to the stderr.
40 | config.active_support.deprecation = :stderr
41 |
42 | # Raises error for missing translations
43 | # config.action_view.raise_on_missing_translations = true
44 | end
45 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Version of your assets, change this if you want to expire all your assets.
6 | Rails.application.config.assets.version = '1.0'
7 |
8 | # Add additional assets to the asset load path.
9 | # Rails.application.config.assets.paths << Emoji.images_path
10 | # Add Yarn node_modules folder to the asset load path.
11 | Rails.application.config.assets.paths << Rails.root.join('node_modules')
12 |
13 | # Precompile additional assets.
14 | # application.js, application.css, and all non-JS/CSS in the app/assets
15 | # folder are already added.
16 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | fr:
2 | sail:
3 | page_title: Le dashboard
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Initialize the Rails application.
2 |
3 | # Puma can serve each request in a thread from an internal thread pool.
4 | # The `threads` method setting takes two numbers: a minimum and maximum.
5 | # Any libraries that use thread pools should be configured to match
6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
7 | # and maximum; this matches the default thread size of Active Record.
8 | #
9 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
10 | threads threads_count, threads_count
11 |
12 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
13 | #
14 | port ENV.fetch("PORT") { 3000 }
15 |
16 | # Specifies the `environment` that Puma will run in.
17 | #
18 | environment ENV.fetch("RAILS_ENV") { "development" }
19 |
20 | # Specifies the number of `workers` to boot in clustered mode.
21 | # Workers are forked webserver processes. If using threads and workers together
22 | # the concurrency of the application would be max `threads` * `workers`.
23 | # Workers do not work on JRuby or Windows (both of which do not support
24 | # processes).
25 | #
26 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
27 |
28 | # Use the `preload_app!` method when specifying a `workers` number.
29 | # This directive tells Puma to first boot the application and load code
30 | # before forking the application. This takes advantage of Copy On Write
31 | # process behavior so workers use less memory. If you use this option
32 | # you need to make sure to reconnect any threads in the `on_worker_boot`
33 | # block.
34 | #
35 | # preload_app!
36 |
37 | # If you are preloading your application and using Active Record, it's
38 | # recommended that you close any connections to the database before workers
39 | # are forked to prevent connection leakage.
40 | #
41 | # before_fork do
42 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
43 | # end
44 |
45 | # The code in the `on_worker_boot` will be called if you are using
46 | # clustered mode by specifying a number of `workers`. After each worker
47 | # process is booted, this block will be run. If you are using the `preload_app!`
48 | # option, you will want to use this block to reconnect to any threads
49 | # or connections that may have been created at application boot, as Ruby
50 | # cannot share connections between processes.
51 | #
52 | # on_worker_boot do
53 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
54 | # end
55 | #
56 |
57 | # Allow puma to be restarted by `rails restart` command.
58 | plugin :tmp_restart
59 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | # Initialize the Rails application.
2 |
3 | Rails.application.routes.draw do
4 | root "application#index"
5 | mount Sail::Engine => '/sail'
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/config/sail.yml:
--------------------------------------------------------------------------------
1 | number_of_parallel_jobs:
2 | description: Maximum number of parallel jobs
3 | value: 2
4 | cast_type: integer
5 | group: jobs
6 | enable_awesome_feature:
7 | description: Enable the awesome feature!
8 | value: 'true'
9 | cast_type: boolean
10 | group: feature_flags
11 | partner_url:
12 | description: URL to connect for partner service
13 | value: 'https://mypartner.com'
14 | cast_type: uri
15 | group: app
16 | promotion_expire_date:
17 | description: Expiration date for winter promotion
18 | value: '2019-01-15T9:00'
19 | cast_type: date
20 | group: app
21 | admins:
22 | description: List of system admins
23 | value: John;Ted;Mark
24 | cast_type: array
25 | group: adm
26 | article_model:
27 | description: Article model to be used for posts
28 | value: 'Post'
29 | cast_type: obj_model
30 | group: app
31 | new_design_percentage:
32 | description: Percentage of users that can see the new design
33 | value: '25'
34 | cast_type: throttle
35 | group: feature_flags
36 | lucky_one_cadence:
37 | description: Cron definition for gitfting lucky users
38 | value: '6 10 2 1 *'
39 | cast_type: cron
40 | group: app
41 | max_survey_answers:
42 | description: Maximum answers per user for a survey
43 | value: 3
44 | cast_type: integer
45 | group: app
46 | ab_tester:
47 | description: General purpose ab test setting
48 | value: 'false'
49 | cast_type: ab_test
50 | group: feature_flags
51 | scaler:
52 | description: A scaler
53 | value: 33
54 | cast_type: range
55 | group: tuners
56 |
--------------------------------------------------------------------------------
/spec/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | # Shared secrets are available across all environments.
14 |
15 | # shared:
16 | # api_key: a1B2c3D4e5F6
17 |
18 | # Environmental secrets are only available for that specific environment.
19 |
20 | development:
21 | secret_key_base: a66204353250ec209f37abe12a780e7f5767401475f85fecbdcda515ce0916d25569c33669c3169d5f962605ac61d33aa2e3090464f5255b1d761b83fb09eb41
22 |
23 | test:
24 | secret_key_base: 632ff2eeb0f1d907c4794f48bee1f6d631a3d5df46b71397385e1b614c24955f4bd89d6d9bd7cf6eba819576884f95f5b1e3188b6ee1a945109860145ecfcfdf
25 |
26 | # Do not keep production secrets in the unencrypted secrets file.
27 | # Instead, either read values from the environment.
28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets
29 | # and move the `production:` environment over there.
30 |
31 | production:
32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
33 |
--------------------------------------------------------------------------------
/spec/dummy/config/spring.rb:
--------------------------------------------------------------------------------
1 | # Initialize the Rails application.
2 |
3 | %w(
4 | .ruby-version
5 | .rbenv-vars
6 | tmp/restart.txt
7 | tmp/caching-dev.txt
8 | ).each { |path| Spring.watch(path) }
9 |
--------------------------------------------------------------------------------
/spec/dummy/db/development.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinistock/sail/65f7bcadd155fff87088d15730356cd5ce33dd17/spec/dummy/db/development.sqlite3
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20171026214947_create_tables_for_models.rb:
--------------------------------------------------------------------------------
1 | class CreateTablesForModels < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :tests do |t|
4 | t.string :name
5 | t.integer :value
6 | t.boolean :real
7 | t.text :content
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20180905005346_create_sail_setting.rb:
--------------------------------------------------------------------------------
1 | class CreateSailSetting < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :sail_settings do |t|
4 | t.string :name, null: false
5 | t.text :description
6 | t.string :value, null: false
7 | t.integer :cast_type, null: false, limit: 2
8 | t.timestamps
9 | t.index ["name"], name: "index_settings_on_name", unique: true
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20181220171659_add_group_to_settings.rb:
--------------------------------------------------------------------------------
1 | class AddGroupToSettings < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column(:sail_settings, :group, :string)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20190207182505_create_sail_profiles.rb:
--------------------------------------------------------------------------------
1 | class CreateSailProfiles < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :sail_entries do |t|
4 | t.string :value, null: false
5 | t.references :setting, index: true
6 | t.references :profile, index: true
7 | t.timestamps
8 | end
9 |
10 | create_table :sail_profiles do |t|
11 | t.string :name, null: false
12 | t.index ["name"], name: "index_sail_profiles_on_name", unique: true
13 | t.timestamps
14 | end
15 |
16 | add_foreign_key(:sail_entries, :sail_settings, column: :setting_id)
17 | add_foreign_key(:sail_entries, :sail_profiles, column: :profile_id)
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20190221151558_add_active_to_profiles.rb:
--------------------------------------------------------------------------------
1 | class AddActiveToProfiles < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column(:sail_profiles, :active, :boolean, default: false)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20190606160450_remove_tests.rb:
--------------------------------------------------------------------------------
1 | class RemoveTests < ActiveRecord::Migration[5.2]
2 | def change
3 | drop_table :tests
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 2019_06_06_160450) do
14 |
15 | create_table "sail_entries", force: :cascade do |t|
16 | t.string "value", null: false
17 | t.integer "setting_id"
18 | t.integer "profile_id"
19 | t.datetime "created_at", null: false
20 | t.datetime "updated_at", null: false
21 | t.index ["profile_id"], name: "index_sail_entries_on_profile_id"
22 | t.index ["setting_id"], name: "index_sail_entries_on_setting_id"
23 | end
24 |
25 | create_table "sail_profiles", force: :cascade do |t|
26 | t.string "name", null: false
27 | t.datetime "created_at", null: false
28 | t.datetime "updated_at", null: false
29 | t.boolean "active", default: false
30 | t.index ["name"], name: "index_sail_profiles_on_name", unique: true
31 | end
32 |
33 | create_table "sail_settings", force: :cascade do |t|
34 | t.string "name", null: false
35 | t.text "description"
36 | t.string "value", null: false
37 | t.integer "cast_type", limit: 2, null: false
38 | t.datetime "created_at", null: false
39 | t.datetime "updated_at", null: false
40 | t.string "group"
41 | t.index ["name"], name: "index_settings_on_name", unique: true
42 | end
43 |
44 | end
45 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinistock/sail/65f7bcadd155fff87088d15730356cd5ce33dd17/spec/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinistock/sail/65f7bcadd155fff87088d15730356cd5ce33dd17/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dummy",
3 | "private": true,
4 | "dependencies": {}
5 | }
6 |
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.