activerecord-null 0.1.3 → 0.1.5
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 +16 -12
- data/README.md +75 -0
- data/lib/activerecord/null/mimic.rb +1 -1
- data/lib/activerecord/null/version.rb +1 -1
- data/lib/activerecord/null.rb +153 -25
- data/test/activerecord/test_lazy_loading.rb +141 -0
- data/test/activerecord/test_void.rb +245 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a56f2bfbaf5d498a54f2698e96dc1d460b6a9036ed1c6946e54bcbf724a782c9
|
|
4
|
+
data.tar.gz: 4575ab2f7ffabae11b05bf8429acb57c0810840fd7e996902691f4758d63ce81
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f737ae853ffbdf31b4ef323e313f9a844f7b549d2bb3a7aa8d5547af94bfb0779b5d2f099a47b11f6ff4b3a48f163e842d68e8c3efb8b52e519a08ae30dc620
|
|
7
|
+
data.tar.gz: e0af61bf86c190fb5e5ccf05eb7d8ec13f0cbb51e59b48e36eb3e0be1b29f3925ea69e778e391c2b0b700d7e1e053ab801db4840fe4e1ae91a130772b98c3132
|
data/CHANGELOG.md
CHANGED
|
@@ -5,24 +5,28 @@ 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.5] - 2025-11-19
|
|
9
9
|
|
|
10
|
-
###
|
|
10
|
+
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
12
|
+
- Void() method for defining non-singleton null objects (0e89194)
|
|
13
|
+
- Model.void(attributes) for creating instances with attribute overrides (0e89194)
|
|
14
14
|
|
|
15
|
-
###
|
|
15
|
+
### Changed
|
|
16
16
|
|
|
17
|
-
-
|
|
17
|
+
- Mimic module now delegates table_name to parent model (c6f3956)
|
|
18
|
+
- Extracted create_null_class, setup_singleton_attributes, setup_instance_attributes helpers (b0a77f8)
|
|
19
|
+
- Pass class_name to the Null() or Void() methods with an alternative class name. (d3284c4)
|
|
18
20
|
|
|
19
|
-
## [0.1.
|
|
21
|
+
## [0.1.5] - 2025-11-19
|
|
20
22
|
|
|
21
|
-
###
|
|
23
|
+
### Added
|
|
22
24
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
+
- Void() method for defining non-singleton null objects (0e89194)
|
|
26
|
+
- Model.void(attributes) for creating instances with attribute overrides (0e89194)
|
|
25
27
|
|
|
26
|
-
###
|
|
28
|
+
### Changed
|
|
27
29
|
|
|
28
|
-
-
|
|
30
|
+
- Mimic module now delegates table_name to parent model (c6f3956)
|
|
31
|
+
- Extracted create_null_class, setup_singleton_attributes, setup_instance_attributes helpers (b0a77f8)
|
|
32
|
+
- Pass class_name to the Null() or Void() methods with an alternative class name. (d3284c4)
|
data/README.md
CHANGED
|
@@ -68,6 +68,81 @@ class User < ApplicationRecord
|
|
|
68
68
|
end
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
Customize the null class name:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
class User < ApplicationRecord
|
|
75
|
+
Null(class_name: "Guest")
|
|
76
|
+
class << self
|
|
77
|
+
alias_method :null, :guest
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
User.guest # returns a User::Guest instance
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Void Objects
|
|
85
|
+
|
|
86
|
+
While `Null` objects are singletons (one instance per model), `Void` objects are instantiable null objects that allow creating multiple instances with different attribute values.
|
|
87
|
+
|
|
88
|
+
Define a void object for the model:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
class Product < ApplicationRecord
|
|
92
|
+
Void([:name] => "Unknown Product") do
|
|
93
|
+
def display_name
|
|
94
|
+
"Product: #{name}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Create instances with custom attributes:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
product1 = Product.void(name: "Widget")
|
|
104
|
+
product2 = Product.void(name: "Gadget")
|
|
105
|
+
|
|
106
|
+
product1.name # => "Widget"
|
|
107
|
+
product2.name # => "Gadget"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Each call to `.void` returns a new instance:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
Product.void.object_id != Product.void.object_id # => true
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Instance attributes override defaults:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
product = Product.void(name: "Custom")
|
|
120
|
+
product.name # => "Custom" (overrides default "Unknown Product")
|
|
121
|
+
|
|
122
|
+
default_product = Product.void
|
|
123
|
+
default_product.name # => "Unknown Product" (uses default)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Void objects support the same features as Null objects:
|
|
127
|
+
- Callable defaults (lambdas/procs)
|
|
128
|
+
- Custom methods via block syntax
|
|
129
|
+
- Association handling
|
|
130
|
+
- All ActiveRecord query methods (`null?`, `persisted?`, etc.)
|
|
131
|
+
- Custom class names via `class_name:` parameter
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
class Product < ApplicationRecord
|
|
135
|
+
Void(class_name: "Placeholder")
|
|
136
|
+
class << self
|
|
137
|
+
alias_method :void, :placeholder
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
Product.placeholder # returns a Product::Placeholder instance
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Use `Null` when you need a single shared null object instance. Use `Void` when you need multiple null object instances with different attribute values.
|
|
145
|
+
|
|
71
146
|
## Development
|
|
72
147
|
|
|
73
148
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/activerecord/null.rb
CHANGED
|
@@ -12,6 +12,129 @@ module ActiveRecord
|
|
|
12
12
|
# extend ActiveRecord::Null
|
|
13
13
|
# end
|
|
14
14
|
module Null
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Shared method to create Null or Void classes
|
|
18
|
+
def create_null_class(inherit, assignments, singleton:)
|
|
19
|
+
null_class = Class.new do
|
|
20
|
+
include ::ActiveRecord::Null::Mimic
|
|
21
|
+
|
|
22
|
+
mimics inherit
|
|
23
|
+
|
|
24
|
+
# Store assignments
|
|
25
|
+
instance_variable_set(:@_assignments, assignments)
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
attr_reader :_assignments
|
|
29
|
+
|
|
30
|
+
def method_missing(method, ...)
|
|
31
|
+
mimic_model_class.respond_to?(method) ? mimic_model_class.send(method, ...) : super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def respond_to_missing?(method, include_private = false)
|
|
35
|
+
mimic_model_class.respond_to?(method, include_private) || super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if singleton
|
|
41
|
+
null_class.include(Singleton)
|
|
42
|
+
setup_singleton_attributes(null_class)
|
|
43
|
+
else
|
|
44
|
+
setup_instance_attributes(null_class)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
null_class
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def setup_singleton_attributes(null_class)
|
|
51
|
+
null_class.class_eval do
|
|
52
|
+
class << self
|
|
53
|
+
# Override instance to initialize attributes lazily
|
|
54
|
+
def instance
|
|
55
|
+
initialize_attribute_methods unless @_attributes_initialized
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def initialize_attribute_methods
|
|
62
|
+
return unless mimic_model_class.table_exists?
|
|
63
|
+
|
|
64
|
+
if _assignments.any?
|
|
65
|
+
_assignments.each do |attributes, value|
|
|
66
|
+
define_attribute_methods(attributes, value:)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
nil_assignments = mimic_model_class.attribute_names
|
|
71
|
+
if _assignments.any?
|
|
72
|
+
_assignments.each do |attributes, _|
|
|
73
|
+
nil_assignments -= attributes
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
define_attribute_methods(nil_assignments) if nil_assignments.any?
|
|
77
|
+
|
|
78
|
+
@_attributes_initialized = true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def setup_instance_attributes(null_class)
|
|
85
|
+
null_class.class_eval do
|
|
86
|
+
def initialize(attributes = {})
|
|
87
|
+
@_instance_attributes = attributes
|
|
88
|
+
initialize_attribute_methods
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def initialize_attribute_methods
|
|
94
|
+
return unless self.class.mimic_model_class.table_exists?
|
|
95
|
+
|
|
96
|
+
assignments = self.class._assignments
|
|
97
|
+
|
|
98
|
+
if assignments.any?
|
|
99
|
+
assignments.each do |attributes, default_value|
|
|
100
|
+
attributes.each do |attr|
|
|
101
|
+
attr_sym = attr.to_sym
|
|
102
|
+
next if respond_to?(attr_sym)
|
|
103
|
+
|
|
104
|
+
define_singleton_method(attr_sym) do
|
|
105
|
+
if @_instance_attributes.key?(attr_sym)
|
|
106
|
+
@_instance_attributes[attr_sym]
|
|
107
|
+
elsif default_value.is_a?(Proc)
|
|
108
|
+
instance_exec(&default_value)
|
|
109
|
+
else
|
|
110
|
+
default_value
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
nil_assignments = self.class.mimic_model_class.attribute_names
|
|
118
|
+
if assignments.any?
|
|
119
|
+
assignments.each do |attributes, _|
|
|
120
|
+
nil_assignments -= attributes.map(&:to_s)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
nil_assignments.each do |attr|
|
|
125
|
+
attr_sym = attr.to_sym
|
|
126
|
+
next if respond_to?(attr_sym)
|
|
127
|
+
|
|
128
|
+
define_singleton_method(attr_sym) do
|
|
129
|
+
@_instance_attributes.key?(attr_sym) ? @_instance_attributes[attr_sym] : nil
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
public
|
|
137
|
+
|
|
15
138
|
# Define a Null class for the given class.
|
|
16
139
|
#
|
|
17
140
|
# @example
|
|
@@ -33,41 +156,46 @@ module ActiveRecord
|
|
|
33
156
|
#
|
|
34
157
|
# @param inherit [Class] The class from which the Null object inherits attributes
|
|
35
158
|
# @param assignments [Array] The attributes to assign to the null object
|
|
36
|
-
def Null(inherit = self,
|
|
159
|
+
def Null(inherit = self, class_name: :Null, **assignments, &)
|
|
37
160
|
if inherit.is_a?(Hash)
|
|
38
161
|
assignments = inherit
|
|
39
162
|
inherit = self
|
|
40
163
|
end
|
|
41
|
-
null_class = Class.new do
|
|
42
|
-
include ::ActiveRecord::Null::Mimic
|
|
43
|
-
|
|
44
|
-
mimics inherit
|
|
45
164
|
|
|
46
|
-
|
|
165
|
+
null_class = create_null_class(inherit, assignments, singleton: true)
|
|
166
|
+
null_class.class_eval(&) if block_given?
|
|
47
167
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
168
|
+
inherit.const_set(class_name, null_class)
|
|
169
|
+
inherit.define_singleton_method(:null) { null_class.instance }
|
|
170
|
+
end
|
|
52
171
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
172
|
+
# Define a Void class for the given class.
|
|
173
|
+
# Unlike Null, Void objects are not singletons and can be instantiated
|
|
174
|
+
# multiple times with different attribute values.
|
|
175
|
+
#
|
|
176
|
+
# @example
|
|
177
|
+
# class Product < ApplicationRecord
|
|
178
|
+
# Void do
|
|
179
|
+
# def display_name = "Product: #{name}"
|
|
180
|
+
# end
|
|
181
|
+
# end
|
|
182
|
+
#
|
|
183
|
+
# product1 = Product.void(name: "Widget")
|
|
184
|
+
# product2 = Product.void(name: "Gadget")
|
|
185
|
+
#
|
|
186
|
+
# @param inherit [Class] The class from which the Void object inherits attributes
|
|
187
|
+
# @param assignments [Hash] The default attributes to assign to void objects
|
|
188
|
+
def Void(inherit = self, class_name: :Void, **assignments, &)
|
|
189
|
+
if inherit.is_a?(Hash)
|
|
190
|
+
assignments = inherit
|
|
191
|
+
inherit = self
|
|
57
192
|
end
|
|
58
|
-
null_class.class_eval(&) if block_given?
|
|
59
193
|
|
|
60
|
-
|
|
61
|
-
if
|
|
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
|
-
inherit.const_set(:Null, null_class)
|
|
194
|
+
void_class = create_null_class(inherit, assignments, singleton: false)
|
|
195
|
+
void_class.class_eval(&) if block_given?
|
|
69
196
|
|
|
70
|
-
inherit.
|
|
197
|
+
inherit.const_set(class_name, void_class)
|
|
198
|
+
inherit.define_singleton_method(:void) { |attributes = {}| void_class.new(attributes) }
|
|
71
199
|
end
|
|
72
200
|
|
|
73
201
|
def self.extended(base)
|
|
@@ -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
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class ApplicationRecord < ActiveRecord::Base
|
|
6
|
+
primary_abstract_class
|
|
7
|
+
extend ActiveRecord::Null
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Product < ApplicationRecord
|
|
11
|
+
self.table_name = "businesses" # Reuse existing table
|
|
12
|
+
|
|
13
|
+
Void([:name] => "Unknown Product") do
|
|
14
|
+
def display_name
|
|
15
|
+
"Product: #{name}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Comment < ApplicationRecord
|
|
21
|
+
self.table_name = "posts" # Reuse existing table
|
|
22
|
+
|
|
23
|
+
Void()
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class ActiveRecord::TestVoid < Minitest::Spec
|
|
27
|
+
describe "Void class definition" do
|
|
28
|
+
it "creates Void constant on model" do
|
|
29
|
+
assert Product.const_defined?(:Void)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "Void class includes Mimic" do
|
|
33
|
+
assert Product::Void.include?(ActiveRecord::Null::Mimic)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "Void class does not include Singleton" do
|
|
37
|
+
refute Product::Void.include?(Singleton)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "defines .void method on model" do
|
|
41
|
+
assert Product.respond_to?(:void)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "Void can be defined without arguments" do
|
|
45
|
+
assert Comment.const_defined?(:Void)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe ".void instantiation" do
|
|
50
|
+
it "returns new instance each time" do
|
|
51
|
+
void1 = Product.void
|
|
52
|
+
void2 = Product.void
|
|
53
|
+
|
|
54
|
+
assert_instance_of Product::Void, void1
|
|
55
|
+
assert_instance_of Product::Void, void2
|
|
56
|
+
refute_equal void1.object_id, void2.object_id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "creates instance with no arguments" do
|
|
60
|
+
void_obj = Product.void
|
|
61
|
+
|
|
62
|
+
assert_instance_of Product::Void, void_obj
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "creates instance with empty hash" do
|
|
66
|
+
void_obj = Product.void({})
|
|
67
|
+
|
|
68
|
+
assert_instance_of Product::Void, void_obj
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "attribute handling" do
|
|
73
|
+
it "returns default value from hash syntax" do
|
|
74
|
+
void_obj = Product.void
|
|
75
|
+
|
|
76
|
+
assert_equal "Unknown Product", void_obj.name
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "allows instance attributes to override defaults" do
|
|
80
|
+
void_obj = Product.void(name: "Custom Product")
|
|
81
|
+
|
|
82
|
+
assert_equal "Custom Product", void_obj.name
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "database attributes default to nil" do
|
|
86
|
+
# Assuming businesses table doesn't have a 'price' column
|
|
87
|
+
void_obj = Product.void
|
|
88
|
+
|
|
89
|
+
# Name has a default, so it should return that
|
|
90
|
+
assert_equal "Unknown Product", void_obj.name
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "supports multiple instances with different attributes" do
|
|
94
|
+
void1 = Product.void(name: "Product A")
|
|
95
|
+
void2 = Product.void(name: "Product B")
|
|
96
|
+
|
|
97
|
+
assert_equal "Product A", void1.name
|
|
98
|
+
assert_equal "Product B", void2.name
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "instance attributes don't affect class defaults" do
|
|
102
|
+
void1 = Product.void(name: "Modified")
|
|
103
|
+
void2 = Product.void
|
|
104
|
+
|
|
105
|
+
assert_equal "Modified", void1.name
|
|
106
|
+
assert_equal "Unknown Product", void2.name
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "custom methods from block" do
|
|
111
|
+
it "block-defined methods are accessible" do
|
|
112
|
+
void_obj = Product.void
|
|
113
|
+
|
|
114
|
+
assert_respond_to void_obj, :display_name
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "block methods can access attributes" do
|
|
118
|
+
void_obj = Product.void(name: "Special")
|
|
119
|
+
|
|
120
|
+
assert_equal "Product: Special", void_obj.display_name
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "block methods work with defaults" do
|
|
124
|
+
void_obj = Product.void
|
|
125
|
+
|
|
126
|
+
assert_equal "Product: Unknown Product", void_obj.display_name
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "type checking" do
|
|
131
|
+
it "is_a?(Model) returns true" do
|
|
132
|
+
void_obj = Product.void
|
|
133
|
+
|
|
134
|
+
assert void_obj.is_a?(Product)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "is_a?(Model::Void) returns true" do
|
|
138
|
+
void_obj = Product.void
|
|
139
|
+
|
|
140
|
+
assert void_obj.is_a?(Product::Void)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "null? returns true" do
|
|
144
|
+
void_obj = Product.void
|
|
145
|
+
|
|
146
|
+
assert void_obj.null?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "persisted? returns false" do
|
|
150
|
+
void_obj = Product.void
|
|
151
|
+
|
|
152
|
+
refute void_obj.persisted?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "new_record? returns false" do
|
|
156
|
+
void_obj = Product.void
|
|
157
|
+
|
|
158
|
+
refute void_obj.new_record?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "destroyed? returns false" do
|
|
162
|
+
void_obj = Product.void
|
|
163
|
+
|
|
164
|
+
refute void_obj.destroyed?
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe "callable attribute values" do
|
|
169
|
+
it "supports callable defaults" do
|
|
170
|
+
test_class = Class.new(ApplicationRecord) do
|
|
171
|
+
def self.name
|
|
172
|
+
"CallableTest"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
self.table_name = "users"
|
|
176
|
+
|
|
177
|
+
Void([:name] => -> { "Computed Name" })
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
void_obj = test_class.void
|
|
181
|
+
|
|
182
|
+
assert_equal "Computed Name", void_obj.name
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it "callable defaults can access instance context" do
|
|
186
|
+
test_class = Class.new(ApplicationRecord) do
|
|
187
|
+
def self.name
|
|
188
|
+
"ContextTest"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
self.table_name = "users"
|
|
192
|
+
|
|
193
|
+
Void([:name] => -> { "Hello" })
|
|
194
|
+
|
|
195
|
+
def greeting
|
|
196
|
+
"Welcome"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
void_obj = test_class.void
|
|
201
|
+
|
|
202
|
+
assert_equal "Hello", void_obj.name
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe "integration with Null" do
|
|
207
|
+
it "Null and Void can coexist on same model" do
|
|
208
|
+
# Define both on a test class
|
|
209
|
+
test_class = Class.new(ApplicationRecord) do
|
|
210
|
+
def self.name
|
|
211
|
+
"TestModel"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
self.table_name = "users"
|
|
215
|
+
|
|
216
|
+
Null([:name] => "Null Default")
|
|
217
|
+
Void([:name] => "Void Default")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
null_obj = test_class.null
|
|
221
|
+
void_obj = test_class.void
|
|
222
|
+
|
|
223
|
+
assert_equal "Null Default", null_obj.name
|
|
224
|
+
assert_equal "Void Default", void_obj.name
|
|
225
|
+
assert_equal null_obj.object_id, test_class.null.object_id # Singleton
|
|
226
|
+
refute_equal void_obj.object_id, test_class.void.object_id # Not singleton
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
describe "model integration" do
|
|
231
|
+
it "respects table_name from parent model" do
|
|
232
|
+
assert_equal Product.table_name, Product::Void.table_name
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "respects primary_key from parent model" do
|
|
236
|
+
assert_equal "id", Product::Void.primary_key
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it "has mimic_model_class reference" do
|
|
240
|
+
void_obj = Product.void
|
|
241
|
+
|
|
242
|
+
assert_equal Product, void_obj.mimic_model_class
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
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.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Gay
|
|
@@ -39,7 +39,9 @@ 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
|
|
44
|
+
- test/activerecord/test_void.rb
|
|
43
45
|
- test/support/schema.rb
|
|
44
46
|
- test/test_helper.rb
|
|
45
47
|
homepage: https://github.com/SOFware/activerecord-null
|