rails_readonly_injector 0.2.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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