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