activerecord-null 0.1.3 → 0.1.4
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 +4 -4
- data/CHANGELOG.md +4 -14
- data/lib/activerecord/null/version.rb +1 -1
- data/lib/activerecord/null.rb +37 -8
- data/test/activerecord/test_lazy_loading.rb +141 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7367e3edc8eb1a5d3a539c08cb3ac8bc18a7334d36725edd397ca121adcd143c
|
|
4
|
+
data.tar.gz: 8e86346fcf08e650daeaacc39a70c213dce6cf63119317742fb0ecd5ffbdc142
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ec01aaa257aebaf1e5f9773e9d9d54431a0ffedf51adba02426b18552dc1bd20e1f64f3a1613a797fe69330c510526383e295650db1720e50b66b67715d6d25
|
|
7
|
+
data.tar.gz: eaf8a7c1df7a95fb2a48f44f519a81f5961d5ab681daa93933e47f80f88a3bfe335061642da59ad14456245363af478e5f62bbc9ecb616a610273b54ec88f287
|
data/CHANGELOG.md
CHANGED
|
@@ -5,24 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.1.
|
|
8
|
+
## [0.1.4] - 2025-11-19
|
|
9
9
|
|
|
10
10
|
### Changed
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
- Implement [] method to access attributes with string/symbol keys (1dc4f95)
|
|
12
|
+
- Only initialize attributes when table exists (dba2ace)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- CodeClimate access on CI runs. (2dbf5bc)
|
|
18
|
-
|
|
19
|
-
## [0.1.3] - 2025-11-18
|
|
14
|
+
## [0.1.4] - 2025-11-19
|
|
20
15
|
|
|
21
16
|
### Changed
|
|
22
17
|
|
|
23
|
-
-
|
|
24
|
-
- Implement [] method to access attributes with string/symbol keys (1dc4f95)
|
|
25
|
-
|
|
26
|
-
### Removed
|
|
27
|
-
|
|
28
|
-
- CodeClimate access on CI runs. (2dbf5bc)
|
|
18
|
+
- Only initialize attributes when table exists (dba2ace)
|
data/lib/activerecord/null.rb
CHANGED
|
@@ -45,7 +45,12 @@ module ActiveRecord
|
|
|
45
45
|
|
|
46
46
|
include Singleton
|
|
47
47
|
|
|
48
|
+
# Store assignments for lazy initialization
|
|
49
|
+
@_null_assignments = assignments
|
|
50
|
+
|
|
48
51
|
class << self
|
|
52
|
+
attr_reader :_null_assignments
|
|
53
|
+
|
|
49
54
|
def method_missing(method, ...)
|
|
50
55
|
mimic_model_class.respond_to?(method) ? mimic_model_class.send(method, ...) : super
|
|
51
56
|
end
|
|
@@ -53,18 +58,42 @@ module ActiveRecord
|
|
|
53
58
|
def respond_to_missing?(method, include_private = false)
|
|
54
59
|
mimic_model_class.respond_to?(method, include_private) || super
|
|
55
60
|
end
|
|
61
|
+
|
|
62
|
+
# Override instance to initialize attributes lazily
|
|
63
|
+
def instance
|
|
64
|
+
initialize_attribute_methods unless @_attributes_initialized
|
|
65
|
+
super
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def initialize_attribute_methods
|
|
71
|
+
# Only initialize if table exists
|
|
72
|
+
return unless mimic_model_class.table_exists?
|
|
73
|
+
|
|
74
|
+
# Define custom assignment methods first
|
|
75
|
+
if _null_assignments.any?
|
|
76
|
+
_null_assignments.each do |attributes, value|
|
|
77
|
+
define_attribute_methods(attributes, value:)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Then define database attributes
|
|
82
|
+
nil_assignments = mimic_model_class.attribute_names
|
|
83
|
+
# Remove custom assignments from database attributes
|
|
84
|
+
if _null_assignments.any?
|
|
85
|
+
_null_assignments.each do |attributes, _|
|
|
86
|
+
nil_assignments -= attributes
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
define_attribute_methods(nil_assignments) if nil_assignments.any?
|
|
90
|
+
|
|
91
|
+
@_attributes_initialized = true
|
|
92
|
+
end
|
|
56
93
|
end
|
|
57
94
|
end
|
|
58
95
|
null_class.class_eval(&) if block_given?
|
|
59
96
|
|
|
60
|
-
nil_assignments = inherit.attribute_names
|
|
61
|
-
if assignments.any?
|
|
62
|
-
assignments.each do |attributes, value|
|
|
63
|
-
nil_assignments -= attributes
|
|
64
|
-
null_class.define_attribute_methods(attributes, value:)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
null_class.define_attribute_methods(nil_assignments)
|
|
68
97
|
inherit.const_set(:Null, null_class)
|
|
69
98
|
|
|
70
99
|
inherit.define_singleton_method(:null) { null_class.instance }
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# Test that Null classes can be defined without database access
|
|
6
|
+
class LazyLoadingTest < Minitest::Spec
|
|
7
|
+
describe "Lazy attribute loading" do
|
|
8
|
+
it "allows Null class definition without database access" do
|
|
9
|
+
# Create a new model class with a fake table_exists? check
|
|
10
|
+
table_available = false
|
|
11
|
+
|
|
12
|
+
test_class = Class.new(ApplicationRecord) do
|
|
13
|
+
def self.name
|
|
14
|
+
"TestModel"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
define_singleton_method(:table_exists?) do
|
|
18
|
+
table_available
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
define_singleton_method(:attribute_names) do
|
|
22
|
+
["id", "name", "email"]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Extend with Null - this should NOT require database
|
|
27
|
+
test_class.extend ActiveRecord::Null
|
|
28
|
+
|
|
29
|
+
# Define the Null class - this should also NOT require database
|
|
30
|
+
# This is the key fix - Null() doesn't call attribute_names
|
|
31
|
+
assert_silent do
|
|
32
|
+
test_class.Null([:name] => "Unknown")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The Null class constant should exist
|
|
36
|
+
assert test_class.const_defined?(:Null)
|
|
37
|
+
|
|
38
|
+
# Now make table available
|
|
39
|
+
table_available = true
|
|
40
|
+
|
|
41
|
+
# When we call .null with table available, attributes get loaded
|
|
42
|
+
null_instance = test_class.null
|
|
43
|
+
assert_instance_of test_class::Null, null_instance
|
|
44
|
+
assert_equal "Unknown", null_instance.name
|
|
45
|
+
assert_nil null_instance.email
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "initializes attributes only once" do
|
|
49
|
+
call_count = 0
|
|
50
|
+
test_class = Class.new(ApplicationRecord) do
|
|
51
|
+
def self.name
|
|
52
|
+
"CountingModel"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
define_singleton_method(:table_exists?) do
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
define_singleton_method(:attribute_names) do
|
|
60
|
+
call_count += 1
|
|
61
|
+
["id", "value"]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
test_class.extend ActiveRecord::Null
|
|
66
|
+
test_class.Null
|
|
67
|
+
|
|
68
|
+
# Calling .null multiple times should only initialize once
|
|
69
|
+
assert_equal 0, call_count
|
|
70
|
+
test_class.null
|
|
71
|
+
assert_equal 1, call_count
|
|
72
|
+
test_class.null
|
|
73
|
+
assert_equal 1, call_count
|
|
74
|
+
test_class.null
|
|
75
|
+
assert_equal 1, call_count
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "handles missing database gracefully" do
|
|
79
|
+
# Create a model that simulates a missing database
|
|
80
|
+
test_class = Class.new(ApplicationRecord) do
|
|
81
|
+
def self.name
|
|
82
|
+
"MissingDbModel"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
define_singleton_method(:table_exists?) do
|
|
86
|
+
false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
define_singleton_method(:attribute_names) do
|
|
90
|
+
raise ActiveRecord::NoDatabaseError, "Database 'test' does not exist"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
test_class.extend ActiveRecord::Null
|
|
95
|
+
|
|
96
|
+
# Should be able to define Null class without database
|
|
97
|
+
assert_silent do
|
|
98
|
+
test_class.Null([:name] => "Unknown")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Accessing .null returns the instance (but no attributes are defined yet)
|
|
102
|
+
null_instance = test_class.null
|
|
103
|
+
assert_instance_of test_class::Null, null_instance
|
|
104
|
+
|
|
105
|
+
# Without a table, no attributes work (not even custom ones)
|
|
106
|
+
# This is fine - in CI, you wouldn't call .null before running migrations
|
|
107
|
+
assert_raises(NoMethodError) { null_instance.name }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "handles missing table gracefully" do
|
|
111
|
+
# Create a model that simulates a missing table
|
|
112
|
+
test_class = Class.new(ApplicationRecord) do
|
|
113
|
+
def self.name
|
|
114
|
+
"MissingTableModel"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
define_singleton_method(:table_exists?) do
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
define_singleton_method(:attribute_names) do
|
|
122
|
+
raise ActiveRecord::StatementInvalid, "Table 'missing_table_models' doesn't exist"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
test_class.extend ActiveRecord::Null
|
|
127
|
+
|
|
128
|
+
# Should be able to define Null class without table
|
|
129
|
+
assert_silent do
|
|
130
|
+
test_class.Null([:status] => "inactive")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Accessing .null returns the instance
|
|
134
|
+
null_instance = test_class.null
|
|
135
|
+
assert_instance_of test_class::Null, null_instance
|
|
136
|
+
|
|
137
|
+
# Without a table, attributes don't work
|
|
138
|
+
assert_raises(NoMethodError) { null_instance.status }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-null
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Gay
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- lib/activerecord/null.rb
|
|
40
40
|
- lib/activerecord/null/mimic.rb
|
|
41
41
|
- lib/activerecord/null/version.rb
|
|
42
|
+
- test/activerecord/test_lazy_loading.rb
|
|
42
43
|
- test/activerecord/test_null.rb
|
|
43
44
|
- test/support/schema.rb
|
|
44
45
|
- test/test_helper.rb
|