active_model_persistence 0.3.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +76 -0
- data/lib/active_model_persistence/index.rb +17 -4
- data/lib/active_model_persistence/indexable.rb +12 -8
- data/lib/active_model_persistence/persistence.rb +15 -34
- data/lib/active_model_persistence/primary_key.rb +22 -24
- data/lib/active_model_persistence/primary_key_index.rb +1 -1
- data/lib/active_model_persistence/version.rb +1 -1
- data/lib/active_model_persistence.rb +43 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d34c4046feea446188de7f1381ed024799bd8e545a50d9abf1e22f06ee0e06ee
|
4
|
+
data.tar.gz: 585c06a05af51efb5123efb0c98b618f9747955f9040852ea08835c6f962fdc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 846582f45f58f215e20ba67fb843898d3091c1af816d851689027d94c71c9973d4b5fc3d3b2798d4fd875a22d5a2fc7a5c2c3150f465cdd4aa29fee11e964f2c
|
7
|
+
data.tar.gz: 0ad54fb60bae3d74fceacb8aa94a860bbb399219ccd791ef2376e56b794ec52e706c01eb91bb0e1ff4fb4d4d93af6f72c187d591f657f176ba69f84f15dfa387
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,14 @@
|
|
7
7
|
|
8
8
|
The full change log is stored on [this project's GitHub releases page](https://github.com/jcouball/active_model_persistence/releases).
|
9
9
|
|
10
|
+
## v0.4.0
|
11
|
+
|
12
|
+
* e491e22 Set the version file back to the correct version (#17)
|
13
|
+
* 5774462 Improve the ObjectNotValidError message (#15)
|
14
|
+
* cadde51 Add inheritance support (#14)
|
15
|
+
|
16
|
+
See https://github.com/ruby-git/ruby-git/releases/tag/v0.4.0
|
17
|
+
|
10
18
|
## v0.3.1
|
11
19
|
|
12
20
|
* d904b30 Fix spelling of UniqueConstraintError (#12)
|
data/README.md
CHANGED
@@ -65,6 +65,82 @@ Employee.find_by_manager_id('boss') #=> [e1, e2]
|
|
65
65
|
# etc.
|
66
66
|
```
|
67
67
|
|
68
|
+
## Inheritance
|
69
|
+
|
70
|
+
ActiveModelPersistence supports inheritance in models. Include
|
71
|
+
`ActiveModelPersistence::Persistence` only in the base class.
|
72
|
+
|
73
|
+
Here is an example with a `User` base class and two derived clases `Employee` and `Member`.
|
74
|
+
Each derived class adds different attributes and indexes.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'active_model_persistence'
|
78
|
+
|
79
|
+
class User
|
80
|
+
include ActiveModelPersistence::Persistence
|
81
|
+
|
82
|
+
attribute :id, :integer
|
83
|
+
attribute :name, :string
|
84
|
+
end
|
85
|
+
|
86
|
+
class Employee < User
|
87
|
+
attribute :manager_id, :integer
|
88
|
+
|
89
|
+
index :manager_id, unique: false
|
90
|
+
end
|
91
|
+
|
92
|
+
class Member < User
|
93
|
+
attribute :joined_on, :date, default: Date.today
|
94
|
+
|
95
|
+
index :joined_on, unique: false
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
As an example, say we have one instance of each class:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
user = User.create!(id: 1, name: 'User James')
|
103
|
+
employee = Employee.create!(id: 2, name: 'Employee Bob', manager_id: nil)
|
104
|
+
member = Member.create!(id: 3, name: 'Member Mary', joined_on: Date.parse('2022-01-01'))
|
105
|
+
```
|
106
|
+
|
107
|
+
The primary key is shared by all objects of these classes so it must be unique for
|
108
|
+
all objects created from these classes. For instance:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
require 'rspec-expectations'
|
112
|
+
include RSpec::Matchers
|
113
|
+
|
114
|
+
# Creating a Member with the same primary key as a User will fail
|
115
|
+
expect { Member.create!(id: 1, name: 'Member Jason') }.to(
|
116
|
+
raise_error(ActiveModelPersistence::UniqueConstraintError)
|
117
|
+
)
|
118
|
+
```
|
119
|
+
|
120
|
+
Calling a find method such as `find` or `find_by_*` on a class will only return
|
121
|
+
objects that are a kind of that class.
|
122
|
+
|
123
|
+
That means calling a find method on `User` will return users, employees, or members:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
expect(User.find(1)).to eq(user)
|
127
|
+
expect(User.find(2)).to eq(employee)
|
128
|
+
expect(User.find(3)).to eq(member)
|
129
|
+
```
|
130
|
+
|
131
|
+
While calling a find method on `Employee` will only return employees:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
expect(Employee.find(1)).to be_nil
|
135
|
+
expect(Employee.find(2)).to eq(employee)
|
136
|
+
expect(Employee.find(3)).to be_nil
|
137
|
+
```
|
138
|
+
|
139
|
+
`all`, `count`, `delete_all`, `destroy_all` and other methods similarly limit what
|
140
|
+
objects are acted upon based on what class they are called on.
|
141
|
+
|
142
|
+
## API Documentation
|
143
|
+
|
68
144
|
See [the full API documentation](https://jcouball.github.io/active_record_persistence/) for more details.
|
69
145
|
|
70
146
|
## Installation
|
@@ -15,6 +15,18 @@ module ActiveModelPersistence
|
|
15
15
|
#
|
16
16
|
attr_reader :name
|
17
17
|
|
18
|
+
# Identifies the base class this index applies to
|
19
|
+
#
|
20
|
+
# Objects that are not of this class or one of its subclasses will not be added to the index.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# i = Index.new(name: 'id', base_class: self, key_source: :id, unique: true)
|
24
|
+
# i.base_class == self #=> true
|
25
|
+
#
|
26
|
+
# @return [Class] the class this index applies to
|
27
|
+
#
|
28
|
+
attr_reader :base_class
|
29
|
+
|
18
30
|
# Defines how the object's key value is calculated
|
19
31
|
#
|
20
32
|
# If a proc is provided, it will be called with the object as an argument to get the key value.
|
@@ -22,7 +34,7 @@ module ActiveModelPersistence
|
|
22
34
|
# If a symbol is provided, it will identify the method to call on the object to get the key value.
|
23
35
|
#
|
24
36
|
# @example
|
25
|
-
# i = Index.new(name: 'id', key_value_source: :id, unique: true)
|
37
|
+
# i = Index.new(name: 'id', base_class: self, key_value_source: :id, unique: true)
|
26
38
|
# i.key_value_source # => :id
|
27
39
|
#
|
28
40
|
# @return [Symbol, Proc] the method name or proc used to calculate the index key
|
@@ -37,7 +49,7 @@ module ActiveModelPersistence
|
|
37
49
|
# when trying to add the second object.
|
38
50
|
#
|
39
51
|
# @example
|
40
|
-
# i = Index.new(name: 'id', key_value_source: :id, unique: true)
|
52
|
+
# i = Index.new(name: 'id', base_class: self, key_value_source: :id, unique: true)
|
41
53
|
# i.unique? # => true
|
42
54
|
#
|
43
55
|
# @return [Boolean] true if the index is unique
|
@@ -74,8 +86,9 @@ module ActiveModelPersistence
|
|
74
86
|
# @param key_value_source [Symbol, Proc] the attribute name or proc used to calculate the index key
|
75
87
|
# @param unique [Boolean] when true the index will only allow one object per key
|
76
88
|
#
|
77
|
-
def initialize(name:, key_value_source: nil, unique: false)
|
89
|
+
def initialize(name:, base_class:, key_value_source: nil, unique: false)
|
78
90
|
@name = name.to_s
|
91
|
+
@base_class = base_class
|
79
92
|
@key_value_source = determine_key_value_source(name, key_value_source)
|
80
93
|
@unique = unique
|
81
94
|
@key_to_objects_map = {}
|
@@ -228,7 +241,7 @@ module ActiveModelPersistence
|
|
228
241
|
# @api private
|
229
242
|
#
|
230
243
|
def remove_object_from_index(object, key)
|
231
|
-
key_to_objects_map[key].delete_if { |o| o.
|
244
|
+
key_to_objects_map[key].delete_if { |o| o.primary_key_value == object.primary_key_value }
|
232
245
|
key_to_objects_map.delete(key) if key_to_objects_map[key].empty?
|
233
246
|
object.clear_index_key(name)
|
234
247
|
end
|
@@ -60,6 +60,7 @@ module ActiveModelPersistence
|
|
60
60
|
# make these class methods on that class.
|
61
61
|
#
|
62
62
|
module ClassMethods
|
63
|
+
# @!attribute [r] indexes
|
63
64
|
# Returns a hash of indexes for the model keyed by name
|
64
65
|
#
|
65
66
|
# @example
|
@@ -67,9 +68,7 @@ module ActiveModelPersistence
|
|
67
68
|
#
|
68
69
|
# @return [Hash<String, ActiveModelPersistence::Index>] the indexes defined by the model
|
69
70
|
#
|
70
|
-
|
71
|
-
@indexes ||= {}
|
72
|
-
end
|
71
|
+
# @api public
|
73
72
|
|
74
73
|
# Adds an index to the model
|
75
74
|
#
|
@@ -98,9 +97,7 @@ module ActiveModelPersistence
|
|
98
97
|
indexes[index_name.to_sym] = index
|
99
98
|
|
100
99
|
singleton_class.define_method("find_by_#{index_name}") do |key|
|
101
|
-
index.objects(key).
|
102
|
-
objects.each { |o| o.instance_variable_set(:@previously_new_record, false) }
|
103
|
-
end
|
100
|
+
index.objects(key).select { |object| object.is_a?(self) }
|
104
101
|
end
|
105
102
|
end
|
106
103
|
|
@@ -125,7 +122,9 @@ module ActiveModelPersistence
|
|
125
122
|
# @return [void]
|
126
123
|
#
|
127
124
|
def update_indexes(object)
|
128
|
-
indexes.each_value
|
125
|
+
indexes.each_value do |index|
|
126
|
+
index.add_or_update(object) if object.is_a?(index.base_class)
|
127
|
+
end
|
129
128
|
end
|
130
129
|
|
131
130
|
# Removes the given object from all defined indexes
|
@@ -144,7 +143,9 @@ module ActiveModelPersistence
|
|
144
143
|
# @return [void]
|
145
144
|
#
|
146
145
|
def remove_from_indexes(object)
|
147
|
-
indexes.each_value
|
146
|
+
indexes.each_value do |index|
|
147
|
+
index.remove(object) if object.is_a?(index.base_class)
|
148
|
+
end
|
148
149
|
end
|
149
150
|
|
150
151
|
private
|
@@ -158,12 +159,15 @@ module ActiveModelPersistence
|
|
158
159
|
def default_index_options(index_name)
|
159
160
|
{
|
160
161
|
name: index_name.to_sym,
|
162
|
+
base_class: self,
|
161
163
|
unique: false
|
162
164
|
}
|
163
165
|
end
|
164
166
|
end
|
165
167
|
|
166
168
|
included do
|
169
|
+
cattr_reader :indexes, instance_accessor: false, default: {}
|
170
|
+
|
167
171
|
# Adds the object to the indexes defined by the model
|
168
172
|
#
|
169
173
|
# @example
|
@@ -158,7 +158,7 @@ module ActiveModelPersistence
|
|
158
158
|
# @return [Array<Object>] the model objects in the object store
|
159
159
|
#
|
160
160
|
def all
|
161
|
-
object_array.each
|
161
|
+
object_array.select { |object| object.is_a?(self) }.each
|
162
162
|
end
|
163
163
|
|
164
164
|
# The number of model objects saved in the object store
|
@@ -174,7 +174,7 @@ module ActiveModelPersistence
|
|
174
174
|
# @return [Integer] the number of model objects in the object store
|
175
175
|
#
|
176
176
|
def count
|
177
|
-
object_array.size
|
177
|
+
object_array.select { |object| object.is_a?(self) }.size
|
178
178
|
end
|
179
179
|
|
180
180
|
alias size count
|
@@ -196,30 +196,11 @@ module ActiveModelPersistence
|
|
196
196
|
# @return [void]
|
197
197
|
#
|
198
198
|
def destroy_all
|
199
|
-
object_array.
|
199
|
+
objects_to_destroy = object_array.select { |object| object.is_a?(self) }
|
200
|
+
objects_to_destroy.each(&:destroy)
|
200
201
|
end
|
201
202
|
|
202
|
-
|
203
|
-
#
|
204
|
-
# Each saved model object's `#destroy` method is NOT called.
|
205
|
-
#
|
206
|
-
# @example
|
207
|
-
# array_of_attributes = [
|
208
|
-
# { id: 1, name: 'James' },
|
209
|
-
# { id: 2, name: 'Frank' }
|
210
|
-
# ]
|
211
|
-
# ModelExample.create(array_of_attributes)
|
212
|
-
# ModelExample.all.count #=> 2
|
213
|
-
# ModelExample.destroy_all
|
214
|
-
# ModelExample.all.count #=> 0
|
215
|
-
#
|
216
|
-
# @return [void]
|
217
|
-
#
|
218
|
-
def delete_all
|
219
|
-
@object_array = []
|
220
|
-
indexes.values.each(&:remove_all)
|
221
|
-
nil
|
222
|
-
end
|
203
|
+
alias delete_all destroy_all
|
223
204
|
|
224
205
|
# private
|
225
206
|
|
@@ -229,13 +210,12 @@ module ActiveModelPersistence
|
|
229
210
|
#
|
230
211
|
# @api private
|
231
212
|
#
|
232
|
-
def object_array
|
233
|
-
@object_array ||= []
|
234
|
-
end
|
235
213
|
end
|
236
214
|
|
237
215
|
# rubocop:disable Metrics/BlockLength
|
238
216
|
included do
|
217
|
+
cattr_reader :object_array, instance_accessor: false, default: []
|
218
|
+
|
239
219
|
# Returns true if this object hasn't been saved or destroyed yet
|
240
220
|
#
|
241
221
|
# @example
|
@@ -344,7 +324,7 @@ module ActiveModelPersistence
|
|
344
324
|
#
|
345
325
|
def save!(**_options, &block)
|
346
326
|
raise ObjectDestroyedError if destroyed?
|
347
|
-
raise ObjectNotValidError unless valid?
|
327
|
+
raise ObjectNotValidError, self unless valid?
|
348
328
|
|
349
329
|
new_record? ? _create(&block) : _update(&block)
|
350
330
|
update_indexes
|
@@ -371,7 +351,7 @@ module ActiveModelPersistence
|
|
371
351
|
def destroy
|
372
352
|
if persisted?
|
373
353
|
remove_from_indexes
|
374
|
-
self.class.object_array.delete_if { |o| o.
|
354
|
+
self.class.object_array.delete_if { |o| o.primary_key_value == primary_key_value }
|
375
355
|
end
|
376
356
|
@new_record = false
|
377
357
|
@destroyed = true
|
@@ -429,13 +409,14 @@ module ActiveModelPersistence
|
|
429
409
|
# Creates a record with values matching those of the instance attributes
|
430
410
|
# and returns its id.
|
431
411
|
#
|
432
|
-
# @return [Object] the
|
412
|
+
# @return [Object] the primary_key_value of the created object
|
433
413
|
#
|
434
414
|
# @api private
|
435
415
|
#
|
436
416
|
def _create
|
437
|
-
return false unless
|
438
|
-
|
417
|
+
return false unless primary_key_value?
|
418
|
+
|
419
|
+
raise UniqueConstraintError if primary_key_index.include?(primary_key_value)
|
439
420
|
|
440
421
|
self.class.object_array << self
|
441
422
|
|
@@ -443,7 +424,7 @@ module ActiveModelPersistence
|
|
443
424
|
|
444
425
|
yield(self) if block_given?
|
445
426
|
|
446
|
-
|
427
|
+
primary_key_value
|
447
428
|
end
|
448
429
|
|
449
430
|
# Updates an object that is already in the object store
|
@@ -453,7 +434,7 @@ module ActiveModelPersistence
|
|
453
434
|
# @api private
|
454
435
|
#
|
455
436
|
def _update
|
456
|
-
raise RecordNotFound unless primary_key_index.include?(
|
437
|
+
raise RecordNotFound unless primary_key_index.include?(primary_key_value)
|
457
438
|
|
458
439
|
yield(self) if block_given?
|
459
440
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_support/core_ext/module'
|
4
|
+
|
3
5
|
module ActiveModelPersistence
|
4
6
|
# Exposes the `primary_key` accessor to read or write the primary key attribute value
|
5
7
|
#
|
@@ -42,7 +44,8 @@ module ActiveModelPersistence
|
|
42
44
|
# make these class methods on that class.
|
43
45
|
#
|
44
46
|
module ClassMethods
|
45
|
-
#
|
47
|
+
# @!attribute [rw] primary_key
|
48
|
+
# Identifies the attribute that the `primary_key_value` accessor maps to
|
46
49
|
#
|
47
50
|
# The primary key is 'id' by default.
|
48
51
|
#
|
@@ -54,33 +57,28 @@ module ActiveModelPersistence
|
|
54
57
|
# end
|
55
58
|
# Employee.primary_key #=> :username
|
56
59
|
#
|
57
|
-
# @return [
|
60
|
+
# @return [String] the attribute that the `primary_key` accessor is an alias for
|
58
61
|
#
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
+
# @api public
|
63
|
+
end
|
62
64
|
|
63
|
-
|
65
|
+
included do
|
66
|
+
# @!attribute [r] primary_key
|
67
|
+
# Identifies the attribute that the `primary_key_value` accessor maps to
|
68
|
+
#
|
69
|
+
# The primary key is 'id' by default.
|
64
70
|
#
|
65
71
|
# @example
|
66
72
|
# class Employee
|
67
73
|
# include ActiveModelPersistence::PrimaryKey
|
68
74
|
# attribute :username, :string
|
69
|
-
# primary_key = :username
|
75
|
+
# self.primary_key = :username
|
70
76
|
# end
|
71
|
-
#
|
72
|
-
# e.primary_key #=> 'couballj'
|
73
|
-
#
|
74
|
-
# @param attribute [Symbol] the attribute to use for the primary key
|
75
|
-
#
|
76
|
-
# @return [void]
|
77
|
+
# Employee.primary_key #=> :username
|
77
78
|
#
|
78
|
-
|
79
|
-
|
80
|
-
end
|
81
|
-
end
|
79
|
+
# @return [String] the attribute that the `primary_key` accessor is an alias for
|
80
|
+
cattr_accessor :primary_key, default: 'id', instance_writer: false
|
82
81
|
|
83
|
-
included do
|
84
82
|
# Returns the primary key attribute's value
|
85
83
|
#
|
86
84
|
# @example
|
@@ -94,8 +92,8 @@ module ActiveModelPersistence
|
|
94
92
|
#
|
95
93
|
# @return [Object] the primary key attribute's value
|
96
94
|
#
|
97
|
-
def
|
98
|
-
__send__(
|
95
|
+
def primary_key_value
|
96
|
+
__send__(primary_key)
|
99
97
|
end
|
100
98
|
|
101
99
|
# Sets the primary key atribute's value
|
@@ -114,8 +112,8 @@ module ActiveModelPersistence
|
|
114
112
|
#
|
115
113
|
# @return [void]
|
116
114
|
#
|
117
|
-
def
|
118
|
-
__send__("#{
|
115
|
+
def primary_key_value=(value)
|
116
|
+
__send__("#{primary_key}=", value)
|
119
117
|
end
|
120
118
|
|
121
119
|
# Returns true if the primary key attribute's value is not null or empty
|
@@ -133,8 +131,8 @@ module ActiveModelPersistence
|
|
133
131
|
#
|
134
132
|
# @return [Boolean] true if the primary key attribute's value is not null or empty
|
135
133
|
#
|
136
|
-
def
|
137
|
-
|
134
|
+
def primary_key_value?
|
135
|
+
primary_key_value.present?
|
138
136
|
end
|
139
137
|
end
|
140
138
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'active_support'
|
4
4
|
require 'active_model'
|
5
|
+
require 'pp'
|
5
6
|
|
6
7
|
# A set of mixins to add to ActiveModel classes to support persistence
|
7
8
|
#
|
@@ -18,7 +19,48 @@ module ActiveModelPersistence
|
|
18
19
|
class UniqueConstraintError < ModelError; end
|
19
20
|
|
20
21
|
# Raised when trying to save an invalid object
|
21
|
-
|
22
|
+
# @api public
|
23
|
+
class ObjectNotValidError < ModelError
|
24
|
+
# Create a new ObjectNotValidError constructing a message from the invalid object
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# class Person
|
28
|
+
# include ActiveModelPersistence::Persistence
|
29
|
+
# attribute :id, :integer
|
30
|
+
# validates :id, presence: true
|
31
|
+
# attribute :name, :integer
|
32
|
+
# validates :name, presence: true
|
33
|
+
# attribute :age, :integer
|
34
|
+
# validates :age, numericality: { greater_than: 13, less_than: 125 }, allow_nil: true
|
35
|
+
# end
|
36
|
+
# begin
|
37
|
+
# Person.create!(id: 1)
|
38
|
+
# rescue ObjectNotValidError => e
|
39
|
+
# puts e.message
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @param invalid_object [Object] the invalid object being reported
|
43
|
+
#
|
44
|
+
def initialize(invalid_object)
|
45
|
+
super(error_message(invalid_object))
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Create the exception message
|
51
|
+
# @return [String] the exception message
|
52
|
+
# @api private
|
53
|
+
def error_message(invalid_object)
|
54
|
+
<<~ERROR_MESSAGE
|
55
|
+
#{invalid_object.class} object is not valid
|
56
|
+
|
57
|
+
Errors:
|
58
|
+
#{invalid_object.errors.full_messages.pretty_inspect}
|
59
|
+
Attributes:
|
60
|
+
#{invalid_object.attributes.pretty_inspect}
|
61
|
+
ERROR_MESSAGE
|
62
|
+
end
|
63
|
+
end
|
22
64
|
|
23
65
|
# Raised when trying to save! or update! an object that has already been destroyed
|
24
66
|
class ObjectDestroyedError < ModelError; end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_model_persistence
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Couball
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|