rails_readonly_injector 0.2.0 → 1.2.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 (44) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +24 -0
  4. data/.gitignore +1 -1
  5. data/.rubocop.yml +7 -0
  6. data/.travis.yml +3 -1
  7. data/Appraisals +10 -1
  8. data/CHANGELOG.md +6 -0
  9. data/CODE_OF_CONDUCT.md +0 -0
  10. data/Gemfile +4 -2
  11. data/LICENSE.txt +0 -0
  12. data/README.md +25 -6
  13. data/Rakefile +4 -2
  14. data/bin/console +4 -3
  15. data/cucumber_features/configuration.feature +0 -0
  16. data/cucumber_features/step_definitions/.gitkeep +0 -0
  17. data/cucumber_features/step_definitions/generic_steps.rb +5 -3
  18. data/cucumber_features/step_definitions/user_steps.rb +18 -14
  19. data/cucumber_features/support/.gitkeep +0 -0
  20. data/cucumber_features/user_controller.feature +0 -0
  21. data/cucumber_features/user_controller_readonly.feature +0 -0
  22. data/cucumber_features/user_model.feature +0 -0
  23. data/cucumber_features/user_model_readonly.feature +0 -0
  24. data/development_tasks/support/file_helpers.rb +57 -0
  25. data/development_tasks/support/gem_helpers.rb +63 -0
  26. data/development_tasks/tests.rake +40 -93
  27. data/gemfiles/.bundle/config +0 -0
  28. data/gemfiles/rails_3.gemfile +0 -0
  29. data/gemfiles/rails_4_0.gemfile +0 -0
  30. data/gemfiles/rails_4_1.gemfile +0 -0
  31. data/gemfiles/rails_4_2.gemfile +0 -0
  32. data/gemfiles/rails_5_0.gemfile +0 -0
  33. data/gemfiles/rails_5_1.gemfile +0 -0
  34. data/gemfiles/rails_5_2.gemfile +10 -0
  35. data/lib/rails_readonly_injector.rb +37 -20
  36. data/lib/rails_readonly_injector/configuration.rb +86 -20
  37. data/lib/rails_readonly_injector/version.rb +3 -1
  38. data/rails_readonly_injector.gemspec +16 -14
  39. data/rspec_specs/.gitkeep +0 -0
  40. data/rspec_specs/rails_readonly_injector/configuration_spec.rb +167 -0
  41. data/rspec_specs/{readonly_site_toggle_spec.rb → rails_readonly_injector_spec.rb} +42 -1
  42. data/rspec_specs/support/.gitkeep +0 -0
  43. metadata +36 -18
  44. data/rspec_specs/readonly_site_toggle/configuration_spec.rb +0 -31
@@ -1,130 +1,77 @@
1
- require 'fileutils'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/file_helpers'
4
+ require_relative 'support/gem_helpers'
2
5
 
3
6
  namespace :dev do
4
- RAILS_APP_PATH = File.expand_path('../../tmp/rails_app', __FILE__).freeze
5
- GEM_ROOT_PATH = File.expand_path('../..', __FILE__).freeze
7
+ include GemHelpers
8
+ include FileHelpers
6
9
 
7
- desc "Deploys a test rails application into the #{RAILS_APP_PATH} directory."
10
+ desc 'Deploys a test rails application.'
8
11
  task :deploy_test_app do
9
12
  switch_to_gems_root_path
10
13
 
11
- # Remove the app, if it exists already
12
- system("rm -rf #{RAILS_APP_PATH}")
13
-
14
- puts "Creating a new rails application..."
15
- FileUtils.mkdir_p RAILS_APP_PATH
16
- system("bundle exec rails new #{RAILS_APP_PATH}")
14
+ puts 'Creating a new rails application...'
15
+ generate_rails_application
17
16
 
18
17
  switch_to_rails_app_path
19
18
 
20
- # Read gems defined in this Appraisal,
21
- # so we can write them to the Gemfile rails generated.
22
- # i.e. To 'override'/force a specific version.
23
- gems_defined_in_appraisal = parse_gemfile(ENV['BUNDLE_GEMFILE'])
24
- gems_defined_in_gemfile = parse_gemfile('Gemfile').collect { |l| l.gem_name }
25
-
26
- gems_to_override = gems_defined_in_appraisal.reject { |l| gems_defined_in_gemfile.include? l.gem_name }.collect { |gem| gem.original_line_in_gemfile }
19
+ ensure_gem_versions_defined_in_appraisal_are_used
27
20
 
28
21
  # Add required gems to the gemfile
29
- append_to_file 'Gemfile', gems_to_override.join("\n")
30
- append_to_file 'Gemfile', %{gem 'simplecov', require: false, group: :test\n}
31
- append_to_file 'Gemfile', %{gem "rails_readonly_injector", path: "#{GEM_ROOT_PATH}"\n}
22
+ add_gem 'simplecov', require: false, group: :test
23
+ add_gem 'rails_readonly_injector', path: gems_root_path
32
24
 
33
25
  # Make sure we don't use the gemfile from Appraisal
34
- ENV.delete('BUNDLE_GEMFILE')
35
- ENV.delete('BUNDLE_BIN_PATH')
36
- ENV.delete('RUBYOPT')
26
+ unset_appraisal_environment_variables
37
27
 
38
28
  # Install gems
39
- system("bundle install --binstubs")
29
+ system("bundle install")
40
30
 
41
- puts "Installing Cucumber..."
42
- system("bundle exec rails generate cucumber:install")
43
-
44
- puts "Installing RSpec..."
45
- system("bundle exec rails generate rspec:install")
31
+ puts 'Executing Generators...'
32
+ system('bundle exec rails generate cucumber:install')
33
+ system('bundle exec rails generate rspec:install')
46
34
 
47
35
  # RSpec: Include all files in support/
48
36
  append_to_file 'spec/spec_helper.rb', "Dir.glob('support/**/*.rb').each { |rb| require rb }"
49
37
 
50
- # Set up SimpleCov
51
- append_to_beginning_of_file 'spec/spec_helper.rb', %{
52
- require 'simplecov'
53
- require 'rails_readonly_injector'
54
- }
55
- append_to_beginning_of_file 'features/support/env.rb', "require 'simplecov'"
38
+ install_simplecov("#{gems_root_path}/coverage")
56
39
 
57
- write_file_with_content '.simplecov', %{
58
- SimpleCov.start do
59
- coverage_dir '#{GEM_ROOT_PATH}/coverage'
60
- end
61
- }
62
-
63
40
  # Prepare database migrations, etc
64
- system("bundle exec rails generate scaffold User name:string")
41
+ system('bundle exec rails generate scaffold User name:string')
65
42
 
66
- system("RAILS_ENV=test bundle exec rake db:migrate")
67
- end
68
-
69
- desc "Synchronises tests from `cucumber_features` and `rspec_specs` into the rails application in #{RAILS_APP_PATH}, and runs the tests against the application."
70
- task :run_tests do
71
- switch_to_rails_app_path
72
-
73
- # Set up the Cucumber and RSpec tests
74
- FileUtils.cp_r File.join(GEM_ROOT_PATH, 'cucumber_features', '.'), 'features'
75
- FileUtils.cp_r File.join(GEM_ROOT_PATH, 'rspec_specs', '.'), 'spec'
76
-
77
- exit_code = system('bundle exec cucumber && bundle exec rspec')
78
- exit exit_code
43
+ system('RAILS_ENV=test bundle exec rake db:migrate')
79
44
  end
80
45
 
81
- def parse_gemfile(file_path)
82
- gems = []
83
-
84
- File.open(file_path).readlines.each do |line|
85
- matches = line.match /^\s*gem\s+['|"]/
86
-
87
- next if matches.nil?
46
+ desc 'Synchronises tests from `cucumber_features` and `rspec_specs` into the temporary rails app, and runs them.'
47
+ task run_tests: %i[run_features run_specs]
88
48
 
89
- parts = line.split(',')
90
-
91
- gem_name = parts.first.gsub(/\s*gem\s*|["|']|\n/, '')
92
-
93
- gems << OpenStruct.new({ gem_name: gem_name, original_line_in_gemfile: line })
94
- end
95
-
96
- gems
97
- end
49
+ desc 'Synchronises features from `cucumber_features` into the temporary rails app, and runs them.'
50
+ task :run_features do
51
+ switch_to_rails_app_path
98
52
 
99
- def switch_to_gems_root_path
100
- FileUtils.cd GEM_ROOT_PATH
101
- end
53
+ # Synchronise the cucumber features
54
+ FileUtils.cp_r File.join(gems_root_path, 'cucumber_features'), 'features'
102
55
 
103
- def switch_to_rails_app_path
104
- FileUtils.cd RAILS_APP_PATH
105
- end
56
+ unset_appraisal_environment_variables
106
57
 
107
- def append_to_file(path_to_file, content)
108
- raise 'The specified path is not a file!' unless File.file? path_to_file
109
-
110
- File.open(path_to_file, 'a') do |f|
111
- f.write content
112
- end
58
+ command_executed_successfully = system('bundle exec cucumber')
59
+
60
+ exit 1 unless command_executed_successfully
113
61
  end
114
62
 
115
- def append_to_beginning_of_file(path_to_file, content)
116
- raise 'The specified path is not a file!' unless File.file? path_to_file
63
+ desc 'Synchronise specs from `rspec_specs` into the temporary rails app, and run rspec.'
64
+ task :run_specs do
65
+ switch_to_rails_app_path
117
66
 
118
- existing_content_as_array = File.open(path_to_file, 'r').readlines
67
+ # Synchronise the RSpec specs
68
+ FileUtils.cp_r File.join(gems_root_path, 'rspec_specs'), 'spec'
119
69
 
120
- new_content_arr = [content] + existing_content_as_array
70
+ unset_appraisal_environment_variables
121
71
 
122
- write_file_with_content path_to_file, new_content_arr.join("\n")
72
+ command_executed_successfully = system('bundle exec rspec')
73
+
74
+ exit 1 unless command_executed_successfully
123
75
  end
124
76
 
125
- def write_file_with_content(path_to_file, content)
126
- File.open(path_to_file, 'w') do |f|
127
- f.write content
128
- end
129
- end
130
77
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 5.2.0"
6
+ gem "cucumber-rails", "~> 1.6.0", group: :test, require: false
7
+ gem "rspec-rails", "~> 3.7.2", group: :test
8
+ gem "database_cleaner", "~> 1.0.1"
9
+
10
+ gemspec path: "../"
@@ -1,33 +1,50 @@
1
- require "rails_readonly_injector/version"
2
- require "rails_readonly_injector/configuration"
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_readonly_injector/version'
4
+ require 'rails_readonly_injector/configuration'
3
5
 
4
6
  module RailsReadonlyInjector
7
+ # Applies changes defined in the `config` object
8
+ # and resets `config.dirty?` to false
5
9
  def self.reload!
6
-
7
- Rails.application.eager_load!
8
-
9
- if Rails::VERSION::STRING < '5.0.0'
10
- descendants = ActiveRecord::Base.descendants
11
- else
12
- descendants = ApplicationRecord.descendants
13
- end
14
-
15
- descendants.each do |descendant_class|
16
-
17
- # Ensure excluded classes aren't set to read-only
18
- if config.classes_to_exclude.include? descendant_class
19
- restore_readonly_method(descendant_class)
10
+ config.classes_to_include.each do |klass|
11
+ # Ensure we restore classes that we want to exclude, to their defaults
12
+ # in case they were previously marked as read-only.
13
+ if config.classes_to_exclude.include? klass
14
+ restore_readonly_method(klass)
20
15
  next
21
16
  end
22
17
 
23
- if self.config.read_only
24
- override_readonly_method(descendant_class)
18
+ if config.send(:read_only)
19
+ override_readonly_method(klass)
25
20
  else
26
- restore_readonly_method(descendant_class)
21
+ restore_readonly_method(klass)
27
22
  end
28
23
  end
29
24
 
30
25
  inject_error_handler_into_actioncontroller_base
26
+
27
+ config.send(:reset_dirty_status!)
28
+ end
29
+
30
+ # Returns the currently loaded `config.read_only` value.
31
+ # @return [Boolean] Whether the currently loaded config is set to read-only.
32
+ def self.in_read_only_mode?
33
+ if config.dirty? && config.changed_attributes.key?(:read_only)
34
+ # Return the previously stored value
35
+ config.changed_attributes[:read_only]
36
+ else
37
+ config.send(:read_only)
38
+ end
39
+ end
40
+
41
+ # Sets the desired configuration object, if a block is provided,
42
+ # and then returns the current configuration object.
43
+ # @return [Configuration] The current configuration object.
44
+ def self.config
45
+ yield configuration if block_given?
46
+
47
+ configuration
31
48
  end
32
49
 
33
50
  private
@@ -53,7 +70,7 @@ module RailsReadonlyInjector
53
70
  end
54
71
 
55
72
  def self.inject_error_handler_into_actioncontroller_base
56
- ActionController::Base.class_eval do |klass|
73
+ ActionController::Base.class_eval do
57
74
  rescue_from ActiveRecord::ReadOnlyRecord, with: :rescue_from_readonly_failure
58
75
 
59
76
  protected
@@ -1,44 +1,110 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsReadonlyInjector
2
4
  class Configuration
3
- attr_writer :read_only, :classes_to_exclude
5
+ attr_reader :controller_rescue_action, :classes_to_include, :classes_to_exclude
6
+
7
+ def initialize
8
+ @read_only = false
9
+ @controller_rescue_action = proc {}
10
+ @classes_to_exclude = []
11
+
12
+ @changed_attributes = {}
13
+ end
14
+
15
+ # @return [Array<Class>] An array of classes to include
16
+ # If not specified upon initialisation, it defaults to:
17
+ # ActiveRecord::Base.descendants on Rails < 5.0.0, or
18
+ # ApplicationRecord.descendants on Rails >= 5.0.0
19
+ def classes_to_include
20
+ return @classes_to_include if defined? @classes_to_include
21
+
22
+ Rails.application.eager_load!
23
+
24
+ if Rails::VERSION::STRING < '5.0.0'
25
+ ActiveRecord::Base.descendants
26
+ else
27
+ ApplicationRecord.descendants
28
+ end
29
+ end
30
+
31
+ #######################
32
+ # Setter Methods #
33
+ #######################
4
34
 
5
- def read_only
6
- @read_only || false
35
+ # @param new_value [Boolean] Whether the site should be in read-only mode
36
+ def read_only=(new_value)
37
+ update_instance_variable('@read_only', new_value)
7
38
  end
8
39
 
40
+ # @param action [Lambda, Proc] The action to execute when rescuing from
41
+ # `ActiveRecord::RecordReadOnly` errors, within a controller
9
42
  def controller_rescue_action=(action)
10
43
  raise 'A lambda or proc must be specified' unless action.respond_to? :call
11
44
 
12
- @controller_rescue_action = action
45
+ update_instance_variable('@controller_rescue_action', action)
13
46
  end
14
47
 
15
- def controller_rescue_action
16
- @controller_rescue_action || Proc.new {}
48
+ # @param klasses [Array<Class>] The classes to exclude from being marked as read-only
49
+ def classes_to_exclude=(klasses)
50
+ update_instance_variable('@classes_to_exclude', klasses)
17
51
  end
18
52
 
19
- def classes_to_exclude
20
- @classes_to_exclude || []
53
+ # @param klasses [Array<Class>] The classes to mark as read-only
54
+ def classes_to_include=(klasses)
55
+ update_instance_variable('@classes_to_include', klasses)
21
56
  end
22
- end
23
- private_constant :Configuration
24
57
 
25
- # Sets the specified configuration options, if a block is provided
26
- # @return [Configuration] the current configuration object.
27
- def self.config
28
- yield self.configuration if block_given?
58
+ #####################
59
+ # Instance methods #
60
+ #####################
29
61
 
30
- self.configuration
31
- end
62
+ # @return [Boolean] Whether the configuration
63
+ # has changed since the config was last reloaded
64
+ def dirty?
65
+ !changed_attributes.empty?
66
+ end
32
67
 
33
- def self.reset_configuration!
34
- @config = Configuration.new
68
+ # @return [Hash] A hash of changed attributes
69
+ # and their previous values
70
+ attr_reader :changed_attributes
71
+
72
+ private
73
+
74
+ # Updates the value of the specified instance variable
75
+ # and tracks the attribute's previous value (for `#dirty?`)
76
+ def update_instance_variable(variable_name, new_value)
77
+ old_value = instance_variable_get(variable_name).freeze
78
+
79
+ instance_variable_set(variable_name.to_sym, new_value)
80
+
81
+ unless old_value == new_value
82
+ changed_attributes[variable_name.to_s.delete('@').to_sym] = old_value
83
+ end
84
+ end
85
+
86
+ # Resets the changed attributes hash,
87
+ # so that `#dirty?` returns false
88
+ def reset_dirty_status!
89
+ @changed_attributes = {}
90
+ end
35
91
 
36
- self.reload!
92
+ attr_reader :read_only
37
93
  end
94
+ private_constant :Configuration
38
95
 
39
96
  private
40
97
 
98
+ # @return [Configuration] The current configuration object
41
99
  def self.configuration
42
100
  @config ||= Configuration.new
43
101
  end
44
- end
102
+
103
+ # Resets the current configuration to the defaults
104
+ # and reloads RailsReadonlyInjector
105
+ def self.reset_configuration!
106
+ @config = Configuration.new
107
+
108
+ reload!
109
+ end
110
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsReadonlyInjector
2
- VERSION = "0.2.0"
4
+ VERSION = '1.2.0'
3
5
  end
@@ -1,30 +1,32 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "rails_readonly_injector/version"
5
+ require 'rails_readonly_injector/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "rails_readonly_injector"
8
+ spec.name = 'rails_readonly_injector'
8
9
  spec.version = RailsReadonlyInjector::VERSION
9
- spec.authors = ["Andrew Walter"]
10
- spec.email = ["andrew.walter@burnet.edu.au"]
10
+ spec.authors = ['Andrew Walter']
11
+ spec.email = ['andrew.walter@burnet.edu.au']
11
12
 
12
13
  spec.summary = "Globally toggle 'read-only' mode in a Rails application, on-demand, without having to restart the server."
13
- spec.homepage = "https://www.github.com/xtrasimplicity/rails_readonly_injector"
14
- spec.license = "MIT"
14
+ spec.homepage = 'https://www.github.com/xtrasimplicity/rails_readonly_injector'
15
+ spec.license = 'MIT'
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
18
  f.match(%r{^(test|spec|features)/})
18
19
  end
19
- spec.bindir = "exe"
20
+ spec.bindir = 'exe'
20
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
- spec.require_paths = ["lib"]
22
+ spec.require_paths = ['lib']
22
23
 
23
- spec.required_ruby_version = ">= 2.1.3"
24
+ spec.required_ruby_version = '>= 2.1.3'
25
+
26
+ spec.add_runtime_dependency 'rails', ['>= 3.0', '< 5.3']
24
27
 
25
- spec.add_runtime_dependency "rails", [">= 3.0", "< 5.2"]
26
-
27
- spec.add_development_dependency "bundler", "~> 1.16"
28
- spec.add_development_dependency "rake", "~> 10.0"
29
28
  spec.add_development_dependency 'appraisal', '~> 2.2'
29
+ spec.add_development_dependency 'bundler', '>= 1.16'
30
+ spec.add_development_dependency 'rake', '>= 12.3.3'
31
+ spec.add_development_dependency 'yard', '~> 0.9.12'
30
32
  end