strict-loading 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a2e19110075e341793904808b1faf3c6409d44f8bb4ac546810867cafc8ab3ea
4
+ data.tar.gz: d3afb64736eae88dd4c0676b3aff34a6e29139d1cf2bebc384cc274f98f9b565
5
+ SHA512:
6
+ metadata.gz: 5e59b8a982935eecbd347f2525a40d6aaf1c61eafd33060f050a38220d9249bf0e3645f3dd0e9a2302761fc8c092d13f7354b0071faa6d77e77ff77aa0032ee0
7
+ data.tar.gz: 4f04540ffe96ea74815953716a3cd1e2cb01a5633fd8947bc2e8aa764d653155499825ffb1671d90b8bb39da2e846271793d190620ceedc0070bea07df4f8fc9
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 1.0.0
2
+ * Birthday
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "concurrent-ruby", "< 1.3.5"
6
+
7
+ group :development, :test do
8
+ gem "rake"
9
+ end
10
+
11
+ group :development do
12
+ gem "appraisal"
13
+ gem "appraisal-run", "~> 1.0"
14
+ gem "pry-byebug"
15
+ end
16
+
17
+ group :test do
18
+ gem "combustion"
19
+ gem "database_cleaner"
20
+ gem "rspec-rails"
21
+ gem "sqlite3"
22
+ end
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # strict-loading
2
+
3
+ Backport of Rails 6.1's [strict loading](https://guides.rubyonrails.org/active_record_querying.html#strict-loading) feature for earlier versions of Rails.
4
+
5
+ ## Usage
6
+
7
+ Add this line to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "strict-loading"
11
+ ```
12
+
13
+ Then execute:
14
+ ```ruby
15
+ bundle install
16
+ ```
17
+
18
+ This backport is designed to mimic the Rails 6.1 feature in both behavior and API (although it is currently not a complete implementation). Please see the official Rails [documentation](https://guides.rubyonrails.org/active_record_querying.html#strict-loading) for additional usage information.
19
+
20
+ ### Violation Modes
21
+
22
+ Strict loading can report violations in two ways:
23
+
24
+ 1. `:raise`: Violations will raise errors indicating which association on which model was accessed.
25
+ 2. `:log`: Violations use the `ActiveSupport::Notifications` system and log violation messages under the notification name `strict_loading_violation.active_record`.
26
+
27
+ The default violation mode is `:raise`. To set the violation mode, add the following line to application.rb or one of your environments (eg. config/development.rb):
28
+
29
+ ```ruby
30
+ config.active_record.action_on_strict_loading_violation = :log
31
+ ```
32
+
33
+ Setting up a subscriber to listen for violations can be done like so:
34
+
35
+ ```ruby
36
+ ActiveSupport::Notifications.subscribe("strict_loading_violation.active_record") do |name, start, finish, id, payload|
37
+ # payload is a hash of the form { reflection:, owner: } where the reflection is the
38
+ # association, eg. ActiveRecord::Reflection::HasManyReflection, and `owner:` is the
39
+ # ActiveRecord model class.
40
+ end
41
+ ```
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler"
2
+ require "rspec/core/rake_task"
3
+ require "rubygems/package_task"
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ task default: :spec
8
+
9
+ desc "Run specs"
10
+ RSpec::Core::RakeTask.new do |t|
11
+ t.pattern = "./spec/**/*_spec.rb"
12
+ end
@@ -0,0 +1,9 @@
1
+ module StrictLoading
2
+ module AbstractReflection
3
+ def strict_loading_violation_message(owner)
4
+ message = "`#{owner}` is marked for strict_loading."
5
+ message << " The #{polymorphic? ? "polymorphic association" : "#{klass} association"}"
6
+ message << " named `:#{name}` cannot be lazily loaded."
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StrictLoading
4
+ module Association
5
+ private
6
+
7
+ def find_target
8
+ if violates_strict_loading?
9
+ ::ActiveRecord::Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
10
+ end
11
+
12
+ super
13
+ end
14
+
15
+ def violates_strict_loading?
16
+ return unless owner.validation_context.nil?
17
+ owner.strict_loading?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StrictLoading
4
+ module Core
5
+ def self.included(base)
6
+ base.include(InstanceMethods)
7
+ base.extend(ClassMethods)
8
+
9
+ base.delegate :strict_loading, to: :all
10
+ base.mattr_accessor(:action_on_strict_loading_violation, instance_writer: false)
11
+ base.action_on_strict_loading_violation = :raise
12
+ end
13
+
14
+ module InstanceMethods
15
+ def strict_loading!
16
+ @strict_loading = true
17
+ end
18
+
19
+ def strict_loading?
20
+ @strict_loading
21
+ end
22
+
23
+ def strict_loading #(value = true)
24
+ spawn.strict_loading! #(value)
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ delegate :strict_loading, to: :all
30
+
31
+ def strict_loading_violation!(owner:, reflection:)
32
+ case ActiveRecord::Base.action_on_strict_loading_violation
33
+ when :raise
34
+ message = reflection.strict_loading_violation_message(owner)
35
+ raise ActiveRecord::StrictLoadingViolationError.new(message)
36
+ when :log
37
+ name = "strict_loading_violation.active_record"
38
+ ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StrictLoading
4
+ module QueryMethods
5
+ def strict_loading
6
+ spawn.strict_loading!
7
+ end
8
+
9
+ def strict_loading!
10
+ self.strict_loading_value = true
11
+ self
12
+ end
13
+
14
+ def strict_loading_value
15
+ @values.fetch(:strict_loading, nil)
16
+ end
17
+
18
+ def strict_loading_value=(value)
19
+ raise ImmutableRelation if @loaded
20
+ __check_relation
21
+ @values[:strict_loading] = value
22
+ end
23
+
24
+ if ActiveRecord::VERSION::STRING >= "5.0"
25
+ def __check_relation
26
+ assert_mutability!
27
+ end
28
+ else
29
+ def __check_relation
30
+ check_cached_relation
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StrictLoading
4
+ class Railtie < Rails::Railtie
5
+ initializer "strict_loading.initialize" do
6
+ ActiveSupport.on_load(:active_record) do
7
+ ::ActiveRecord::Relation.prepend(::StrictLoading::Relation)
8
+ ::ActiveRecord::Relation.include(::StrictLoading::QueryMethods)
9
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(::StrictLoading::Association)
10
+ ::ActiveRecord::Associations::SingularAssociation.prepend(::StrictLoading::Association)
11
+ ::ActiveRecord::Associations::HasManyThroughAssociation.prepend(::StrictLoading::Association)
12
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(::StrictLoading::Association)
13
+ ::ActiveRecord::Reflection::AbstractReflection.include(::StrictLoading::AbstractReflection)
14
+
15
+ if (action = Rails.application.config.active_record.action_on_strict_loading_violation)
16
+ self.action_on_strict_loading_violation = action
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StrictLoading
4
+ module Relation
5
+ private
6
+
7
+ def exec_queries
8
+ super.tap do |records|
9
+ unless strict_loading_value.nil?
10
+ records.each do |record|
11
+ record.strict_loading!
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StrictLoading
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StrictLoading
4
+ autoload :AbstractReflection, "strict-loading/abstract_reflection"
5
+ autoload :Association, "strict-loading/association"
6
+ autoload :Core, "strict-loading/core"
7
+ autoload :QueryMethods, "strict-loading/query_methods"
8
+ autoload :Relation, "strict-loading/relation"
9
+ end
10
+
11
+ require "active_record"
12
+
13
+ module ActiveRecord
14
+ class StrictLoadingViolationError < ::ActiveRecord::ActiveRecordError; end
15
+
16
+ class Base
17
+ # we can't do this in the railtie's initializer because we need to load it
18
+ # earlier than ActiveRecord's own initializer
19
+ include ::StrictLoading::Core
20
+ end
21
+ end
22
+
23
+ if defined?(Rails)
24
+ require "strict-loading/railtie"
25
+ end
@@ -0,0 +1 @@
1
+ require "strict-loading"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TodoItem < ActiveRecord::Base
4
+ belongs_to :todo_list
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TodoList < ActiveRecord::Base
4
+ has_many :todo_items
5
+ end
@@ -0,0 +1,6 @@
1
+ default: &default
2
+ adapter: sqlite3
3
+ database: db/combustion_test.sqlite
4
+
5
+ test:
6
+ <<: *default
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define do
4
+ create_table :todo_lists do |t|
5
+ t.column :name, :string
6
+ end
7
+
8
+ create_table :todo_items do |t|
9
+ t.column :name, :string
10
+ t.references :todo_list
11
+ end
12
+ end