active_model_persistence 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +20 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +102 -0
- data/Rakefile +89 -0
- data/active_model_persistence.gemspec +52 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/active_model_persistence/index.rb +290 -0
- data/lib/active_model_persistence/indexable.rb +209 -0
- data/lib/active_model_persistence/persistence.rb +333 -0
- data/lib/active_model_persistence/primary_key.rb +138 -0
- data/lib/active_model_persistence/primary_key_index.rb +57 -0
- data/lib/active_model_persistence/version.rb +6 -0
- data/lib/active_model_persistence.rb +24 -0
- metadata +234 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model_persistence/index'
|
4
|
+
require 'active_model_persistence/primary_key'
|
5
|
+
|
6
|
+
module ActiveModelPersistence
|
7
|
+
# Include in your model to enable index support
|
8
|
+
#
|
9
|
+
# Define an index in the model's class using the `index` method. This will create a `find_by_*`
|
10
|
+
# method for each index to find the objects by their keys.
|
11
|
+
#
|
12
|
+
# Each index has a name which must be unique for the model. The name is used to create the
|
13
|
+
# `find_by_*` method. eg. for the 'id' index, the `find_by_id` method will be created.
|
14
|
+
#
|
15
|
+
# Unique indexes are defined by passing `unique: true` to the `index` method. A unique index
|
16
|
+
# defines a `find_by_*` method that will return a single object or nil if no object is found for
|
17
|
+
# the key.
|
18
|
+
#
|
19
|
+
# A non-unique index will define a `find_by_*` method that will return an array of objects which
|
20
|
+
# may be empty.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class Employee
|
24
|
+
# include ActiveModelPersistence::Indexable
|
25
|
+
#
|
26
|
+
# # Define ActiveModel attributes
|
27
|
+
# attribute :id, :integer
|
28
|
+
# attribute :name, :string
|
29
|
+
# attribute :manager_id, :integer
|
30
|
+
# attribute :birth_date, :date
|
31
|
+
#
|
32
|
+
# # Define indexes
|
33
|
+
# index :id, unique: true
|
34
|
+
# index :manager_id, key_value_source: :manager_id
|
35
|
+
# index :birth_year, key_value_source: ->(o) { o.birth_date.year }
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# e1 = Employee.new(id: 1, name: 'James', manager_id: 3, birth_date: Date.new(1967, 3, 15))
|
39
|
+
# e2 = Employee.new(id: 2, name: 'Frank', manager_id: 3, birth_date: Date.new(1968, 1, 27))
|
40
|
+
# e3 = Employee.new(id: 3, name: 'Aaron', birth_date: Date.new(1968, 6, 16))
|
41
|
+
#
|
42
|
+
# # This should be done by Employee.create or Employee#save from ActiveModelPersistence::Persistence
|
43
|
+
# [e1, e2, e3].each { |e| e.update_indexes }
|
44
|
+
#
|
45
|
+
# # Use the find_by_* methods to find objects
|
46
|
+
# #
|
47
|
+
# Employee.find_by_id(1).name # => 'James'
|
48
|
+
# Employee.find_by_manager_id(3).map(&:name) # => ['James', 'Frank']
|
49
|
+
# Employee.find_by_birth_year(1967).map(&:name) # => ['James']
|
50
|
+
# Employee.find_by_birth_year(1968).map(&:name) # => ['Frank', 'Aaron']
|
51
|
+
#
|
52
|
+
module Indexable
|
53
|
+
extend ActiveSupport::Concern
|
54
|
+
|
55
|
+
include ActiveModel::Model
|
56
|
+
include ActiveModel::Attributes
|
57
|
+
include ActiveModelPersistence::PrimaryKey
|
58
|
+
|
59
|
+
class_methods do
|
60
|
+
# Returns a hash of indexes for the model keyed by name
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# Employee.indexes.keys # => %w[id manager_id birth_year]
|
64
|
+
#
|
65
|
+
# @return [Hash<String, ActiveModelPersistence::Index>] the indexes defined by the model
|
66
|
+
#
|
67
|
+
def indexes
|
68
|
+
@indexes ||= {}
|
69
|
+
end
|
70
|
+
|
71
|
+
# Adds an index to the model
|
72
|
+
#
|
73
|
+
# @example Add a unique index on the id attribute
|
74
|
+
# Employee.index(:id, unique: true)
|
75
|
+
#
|
76
|
+
# @example with a key_value_source when the name and the attribute are different
|
77
|
+
# Employee.index(:manager, key_value_source: :manager_id)
|
78
|
+
#
|
79
|
+
# @example with a Proc for key_value_source
|
80
|
+
# Employee.index(:birth_year, key_value_source: ->(o) { o.birth_date.year })
|
81
|
+
#
|
82
|
+
# @param index_name [String] the name of the index
|
83
|
+
# @param options [Hash] the options for the index
|
84
|
+
# @option options :unique [Boolean] whether the index is unique, default is false
|
85
|
+
# @option options :key_value_source [Symbol, Proc] the source of the key value of the object for this index
|
86
|
+
#
|
87
|
+
# If a Symbol is given, it will name a zero arg method on the object which returns
|
88
|
+
# the key's value. If a Proc is given, the key's value will be the result of calling the
|
89
|
+
# Proc with the object.
|
90
|
+
#
|
91
|
+
# @return [void]
|
92
|
+
#
|
93
|
+
def index(index_name, **options)
|
94
|
+
index = Index.new(**default_index_options(index_name).merge(options))
|
95
|
+
indexes[index_name.to_sym] = index
|
96
|
+
|
97
|
+
singleton_class.define_method("find_by_#{index_name}") do |key|
|
98
|
+
index.objects(key).tap do |objects|
|
99
|
+
objects.each { |o| o.instance_variable_set(:@previously_new_record, false) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Adds or updates all defined indexes for the given object
|
105
|
+
#
|
106
|
+
# Call this after changing the object to ensure the indexes are up to date.
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# e1 = Employee.new(id: 1, name: 'James', manager_id: 3, birth_date: Date.new(1967, 3, 15))
|
110
|
+
# Employee.update_indexes(e1)
|
111
|
+
# Employee.find_by_id(1) # => e1
|
112
|
+
# Employee.find_by_manager_id(3) # => [e1]
|
113
|
+
# Employee.find_by_birth_year(1967) # => [e1]
|
114
|
+
#
|
115
|
+
# e1.birth_date = Date.new(1968, 1, 27)
|
116
|
+
# Employee.find_by_birth_year(1968) # => []
|
117
|
+
# Employee.update_indexes(e1)
|
118
|
+
# Employee.find_by_birth_year(1968) # => [e1]
|
119
|
+
#
|
120
|
+
# @param object [Object] the object to add to the indexes
|
121
|
+
#
|
122
|
+
# @return [void]
|
123
|
+
#
|
124
|
+
def update_indexes(object)
|
125
|
+
indexes.each_value { |index| index.add_or_update(object) }
|
126
|
+
end
|
127
|
+
|
128
|
+
# Removes the given object from all defined indexes
|
129
|
+
#
|
130
|
+
# Call this before deleting the object to ensure the indexes are up to date.
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
# e1 = Employee.new(id: 1, name: 'James', manager_id: 3, birth_date: Date.new(1967, 3, 15))
|
134
|
+
# Employee.update_indexes(e1)
|
135
|
+
# Employee.find_by_id(1) # => e1
|
136
|
+
# Employee.remove_from_indexes(e1)
|
137
|
+
# Employee.find_by_id(1) # => nil
|
138
|
+
#
|
139
|
+
# @param object [Object] the object to remove from the indexes
|
140
|
+
#
|
141
|
+
# @return [void]
|
142
|
+
#
|
143
|
+
def remove_from_indexes(object)
|
144
|
+
indexes.each_value { |index| index.remove(object) }
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# Defines the default options for a new ActiveModelPersistence::Index
|
150
|
+
#
|
151
|
+
# @api private
|
152
|
+
#
|
153
|
+
def default_index_options(index_name)
|
154
|
+
{
|
155
|
+
name: index_name.to_sym,
|
156
|
+
unique: false
|
157
|
+
}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
included do
|
162
|
+
# Adds the object to the indexes defined by the model
|
163
|
+
#
|
164
|
+
# @example
|
165
|
+
# e1 = Employee.new(id: 1, name: 'James', manager_id: 3, birth_date: Date.new(1967, 3, 15))
|
166
|
+
# e1.add_to_indexes
|
167
|
+
# Employee.find_by_id(1) # => e1
|
168
|
+
#
|
169
|
+
# @return [void]
|
170
|
+
#
|
171
|
+
def update_indexes
|
172
|
+
self.class.update_indexes(self)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Removes the object from the indexes defined by the model
|
176
|
+
#
|
177
|
+
# @example
|
178
|
+
# e1 = Employee.new(id: 1, name: 'James', manager_id: 3, birth_date: Date.new(1967, 3, 15))
|
179
|
+
# e1.add_to_indexes
|
180
|
+
# Employee.find_by_id(1) # => e1
|
181
|
+
# e1.remove_from_indexes
|
182
|
+
# Employee.find_by_id(1) # => nil
|
183
|
+
#
|
184
|
+
# @return [void]
|
185
|
+
#
|
186
|
+
def remove_from_indexes
|
187
|
+
self.class.remove_from_indexes(self)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns the key value for the object in the index named by index_name
|
191
|
+
# @api private
|
192
|
+
def previous_index_key(index_name)
|
193
|
+
instance_variable_get("@#{index_name}_index_key")
|
194
|
+
end
|
195
|
+
|
196
|
+
# Set the key value for the object in the index named by index_name
|
197
|
+
# @api private
|
198
|
+
def save_index_key(index_name, key)
|
199
|
+
instance_variable_set("@#{index_name}_index_key", key)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Clears the key value for the object in the index named by index_name
|
203
|
+
# @api private
|
204
|
+
def clear_index_key(index_name)
|
205
|
+
instance_variable_set("@#{index_name}_index_key", nil)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model_persistence/indexable'
|
4
|
+
require 'active_model_persistence/primary_key'
|
5
|
+
require 'active_model_persistence/primary_key_index'
|
6
|
+
|
7
|
+
module ActiveModelPersistence
|
8
|
+
# This mixin adds the ability to store and manage Ruby objects in an in-memory object store.
|
9
|
+
# These objects are commonly called 'models'.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# class ModelExample
|
13
|
+
# include ActiveModelPersistence::Persistence
|
14
|
+
# attribute :id, :integer
|
15
|
+
# attribute :name, :string
|
16
|
+
# index :id, unique: true
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Creating a model instance with `.new` does not save it to the object store
|
20
|
+
# #
|
21
|
+
# m = ModelExample.new(id: 1, name: 'James')
|
22
|
+
# m.new_record? # => true
|
23
|
+
# m.persisted? # => false
|
24
|
+
# m.destroyed? # => false
|
25
|
+
#
|
26
|
+
# # `save` will save the object to the object store
|
27
|
+
# #
|
28
|
+
# m.save
|
29
|
+
# m.new_record? # => false
|
30
|
+
# m.persisted? # => true
|
31
|
+
# m.destroyed? # => false
|
32
|
+
#
|
33
|
+
# # Once an object is persisted, it can be fetched from the object store using `.find`
|
34
|
+
# # and `.find_by_*` methods.
|
35
|
+
# m2 = ModelExample.find(1)
|
36
|
+
# m == m2 # => true
|
37
|
+
#
|
38
|
+
# m2 = ModelExample.find_by_id(1)
|
39
|
+
# m == m2 # => true
|
40
|
+
#
|
41
|
+
# # `destroy` will remove the object from the object store
|
42
|
+
# #
|
43
|
+
# m.destroy
|
44
|
+
# m.new_record? # => true
|
45
|
+
# m.persisted? # => false
|
46
|
+
# m.destroyed? # => true
|
47
|
+
#
|
48
|
+
module Persistence
|
49
|
+
extend ActiveSupport::Concern
|
50
|
+
|
51
|
+
include ActiveModelPersistence::Indexable
|
52
|
+
include ActiveModelPersistence::PrimaryKey
|
53
|
+
include ActiveModelPersistence::PrimaryKeyIndex
|
54
|
+
|
55
|
+
class_methods do
|
56
|
+
# Creates a new model object in to the object store
|
57
|
+
#
|
58
|
+
# Create a new model object passing `attributes` and `block` to `.new` and then calls `#save`.
|
59
|
+
#
|
60
|
+
# @param attributes [Hash, Array<Hash>] attributes
|
61
|
+
#
|
62
|
+
# The attributes to set on the model object. These are passed to the model's `.new` method.
|
63
|
+
#
|
64
|
+
# Multiple model objects can be created by passing an array of attribute Hashes.
|
65
|
+
#
|
66
|
+
# @param block [Proc] options
|
67
|
+
#
|
68
|
+
# The block to pass to the model's `.new` method.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# m = ModelExample.new(id: 1, name: 'James')
|
72
|
+
# m.id #=> 1
|
73
|
+
# m.name #=> 'James'
|
74
|
+
#
|
75
|
+
# @example Multiple model objects can be created
|
76
|
+
# array_of_attributes = [
|
77
|
+
# { id: 1, name: 'James' },
|
78
|
+
# { id: 2, name: 'Frank' }
|
79
|
+
# ]
|
80
|
+
# objects = ModelExample.create(array_of_attributes)
|
81
|
+
# objects.class #=> Array
|
82
|
+
# objects.size #=> 2
|
83
|
+
# objects.first.id #=> 1
|
84
|
+
# objects.map(&:name) #=> ['James', 'Frank']
|
85
|
+
#
|
86
|
+
# @return [Object, Array<Object>] the model object or array of model objects created
|
87
|
+
#
|
88
|
+
def create(attributes = nil, &block)
|
89
|
+
if attributes.is_a?(Array)
|
90
|
+
attributes.collect { |attr| create(attr, &block) }
|
91
|
+
else
|
92
|
+
new(attributes, &block).tap(&:save)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return all model objects that have been saved to the object store
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# array_of_attributes = [
|
100
|
+
# { id: 1, name: 'James' },
|
101
|
+
# { id: 2, name: 'Frank' }
|
102
|
+
# ]
|
103
|
+
# ModelExample.create(array_of_attributes)
|
104
|
+
# ModelExample.all.count #=> 2
|
105
|
+
# ModelExample.all.map(&:id) #=> [1, 2]
|
106
|
+
# ModelExample.all.map(&:name) #=> ['James', 'Frank']
|
107
|
+
#
|
108
|
+
# @return [Array<Object>] the model objects in the object store
|
109
|
+
#
|
110
|
+
def all
|
111
|
+
object_array.each
|
112
|
+
end
|
113
|
+
|
114
|
+
# The number of model objects saved in the object store
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# array_of_attributes = [
|
118
|
+
# { id: 1, name: 'James' },
|
119
|
+
# { id: 2, name: 'Frank' }
|
120
|
+
# ]
|
121
|
+
# ModelExample.create(array_of_attributes)
|
122
|
+
# ModelExample.all.count #=> 2
|
123
|
+
#
|
124
|
+
# @return [Integer] the number of model objects in the object store
|
125
|
+
#
|
126
|
+
def count
|
127
|
+
object_array.size
|
128
|
+
end
|
129
|
+
|
130
|
+
alias_method(:size, :count)
|
131
|
+
|
132
|
+
# Removes all model objects from the object store
|
133
|
+
#
|
134
|
+
# Each saved model object's `#destroy` method is called.
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# array_of_attributes = [
|
138
|
+
# { id: 1, name: 'James' },
|
139
|
+
# { id: 2, name: 'Frank' }
|
140
|
+
# ]
|
141
|
+
# ModelExample.create(array_of_attributes)
|
142
|
+
# ModelExample.all.count #=> 2
|
143
|
+
# ModelExample.destroy_all
|
144
|
+
# ModelExample.all.count #=> 0
|
145
|
+
#
|
146
|
+
# @return [void]
|
147
|
+
#
|
148
|
+
def destroy_all
|
149
|
+
object_array.first.destroy while object_array.size.positive?
|
150
|
+
end
|
151
|
+
|
152
|
+
# Removes all model objects from the object store
|
153
|
+
#
|
154
|
+
# Each saved model object's `#destroy` method is NOT called.
|
155
|
+
#
|
156
|
+
# @example
|
157
|
+
# array_of_attributes = [
|
158
|
+
# { id: 1, name: 'James' },
|
159
|
+
# { id: 2, name: 'Frank' }
|
160
|
+
# ]
|
161
|
+
# ModelExample.create(array_of_attributes)
|
162
|
+
# ModelExample.all.count #=> 2
|
163
|
+
# ModelExample.destroy_all
|
164
|
+
# ModelExample.all.count #=> 0
|
165
|
+
#
|
166
|
+
# @return [void]
|
167
|
+
#
|
168
|
+
def delete_all
|
169
|
+
@object_array = []
|
170
|
+
indexes.values.each(&:remove_all)
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# private
|
175
|
+
|
176
|
+
# All saved model objects are stored in this array (this is the object store)
|
177
|
+
#
|
178
|
+
# @return [Array<Object>] the model objects in the object store
|
179
|
+
#
|
180
|
+
# @api private
|
181
|
+
#
|
182
|
+
def object_array
|
183
|
+
@object_array ||= []
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# rubocop:disable Metrics/BlockLength
|
188
|
+
included do
|
189
|
+
# Returns true if this object hasn't been saved or destroyed yet
|
190
|
+
#
|
191
|
+
# @example
|
192
|
+
# object = ModelExample.new(id: 1, name: 'James')
|
193
|
+
# object.new_record? #=> true
|
194
|
+
# object.save
|
195
|
+
# object.new_record? #=> false
|
196
|
+
# object.destroy
|
197
|
+
# object.new_record? #=> false
|
198
|
+
#
|
199
|
+
# @return [Boolean] true if this object hasn't been saved yet
|
200
|
+
#
|
201
|
+
def new_record?
|
202
|
+
if instance_variable_defined?(:@new_record)
|
203
|
+
@new_record
|
204
|
+
else
|
205
|
+
@new_record = true
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns true if this object has been destroyed
|
210
|
+
#
|
211
|
+
# @example Destroying a saved model object
|
212
|
+
# object = ModelExample.new(id: 1, name: 'James')
|
213
|
+
# object.destroyed? #=> false
|
214
|
+
# object.save
|
215
|
+
# object.destroyed? #=> false
|
216
|
+
# object.destroy
|
217
|
+
# object.destroyed? #=> true
|
218
|
+
#
|
219
|
+
# @example Destroying a unsaved model object
|
220
|
+
# object = ModelExample.new(id: 1, name: 'James')
|
221
|
+
# object.destroyed? #=> false
|
222
|
+
# object.destroy
|
223
|
+
# object.destroyed? #=> true
|
224
|
+
#
|
225
|
+
# @return [Boolean] true if this object has been destroyed
|
226
|
+
#
|
227
|
+
def destroyed?
|
228
|
+
if instance_variable_defined?(:@destroyed)
|
229
|
+
@destroyed
|
230
|
+
else
|
231
|
+
@destroyed = false
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns true if the record is persisted in the object store
|
236
|
+
#
|
237
|
+
# @example
|
238
|
+
# object = ModelExample.new(id: 1, name: 'James')
|
239
|
+
# object.persisted? #=> false
|
240
|
+
# object.save
|
241
|
+
# object.persisted? #=> true
|
242
|
+
# object.destroy
|
243
|
+
# object.persisted? #=> false
|
244
|
+
#
|
245
|
+
# @return [Boolean] true if the record is persisted in the object store
|
246
|
+
#
|
247
|
+
def persisted?
|
248
|
+
!(new_record? || destroyed?)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Saves the model object in the object store and updates all indexes
|
252
|
+
#
|
253
|
+
# @example
|
254
|
+
# ModelExample.all.count #=> 0
|
255
|
+
# object = ModelExample.new(id: 1, name: 'James')
|
256
|
+
# ModelExample.all.count #=> 0
|
257
|
+
# object.save
|
258
|
+
# ModelExample.all.count #=> 1
|
259
|
+
#
|
260
|
+
# @param _options [Hash] save options (currently unused)
|
261
|
+
# @param block [Proc] a block to call after the save
|
262
|
+
#
|
263
|
+
# @yield [self] a block to call after the save
|
264
|
+
# @yieldparam saved_model [self] the model object after it was saved
|
265
|
+
# @yieldreturn [void]
|
266
|
+
#
|
267
|
+
# @return [Boolean] true if the model object was saved
|
268
|
+
#
|
269
|
+
def save(**_options, &block)
|
270
|
+
return false if destroyed?
|
271
|
+
|
272
|
+
result = new_record? ? _create(&block) : _update(&block)
|
273
|
+
update_indexes
|
274
|
+
result != false
|
275
|
+
end
|
276
|
+
|
277
|
+
# Deletes the object from the object store
|
278
|
+
#
|
279
|
+
# This model object is frozen to reflect that no changes should be made
|
280
|
+
# since they can't be persisted.
|
281
|
+
#
|
282
|
+
# @example
|
283
|
+
# ModelExample.create(id: 1, name: 'James')
|
284
|
+
# object = ModelExample.create(id: 2, name: 'Frank')
|
285
|
+
# object.destroyed? #=> false
|
286
|
+
# ModelExample.all.count #=> 2
|
287
|
+
# object.destroy
|
288
|
+
# object.destroyed? #=> true
|
289
|
+
# ModelExample.all.count #=> 1
|
290
|
+
# ModelExample.all.first.name #=> 'James'
|
291
|
+
#
|
292
|
+
# @return [void]
|
293
|
+
#
|
294
|
+
def destroy
|
295
|
+
if persisted?
|
296
|
+
remove_from_indexes
|
297
|
+
self.class.object_array.delete_if { |o| o.primary_key == primary_key }
|
298
|
+
end
|
299
|
+
@new_record = false
|
300
|
+
@destroyed = true
|
301
|
+
freeze
|
302
|
+
end
|
303
|
+
|
304
|
+
def ==(other)
|
305
|
+
attributes == other.attributes
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
# Creates a record with values matching those of the instance attributes
|
311
|
+
# and returns its id.
|
312
|
+
def _create
|
313
|
+
return false unless primary_key?
|
314
|
+
raise UniqueContraintError if primary_key_index.include?(primary_key)
|
315
|
+
|
316
|
+
self.class.object_array << self
|
317
|
+
|
318
|
+
@new_record = false
|
319
|
+
|
320
|
+
yield(self) if block_given?
|
321
|
+
|
322
|
+
primary_key
|
323
|
+
end
|
324
|
+
|
325
|
+
def _update
|
326
|
+
raise RecordNotFound unless primary_key_index.include?(primary_key)
|
327
|
+
|
328
|
+
yield(self) if block_given?
|
329
|
+
end
|
330
|
+
end
|
331
|
+
# rubocop:enable Metrics/BlockLength
|
332
|
+
end
|
333
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelPersistence
|
4
|
+
# Exposes the `primary_key` accessor to read or write the primary key attribute value
|
5
|
+
#
|
6
|
+
# The primary key should be a unique (within its model class) identifier
|
7
|
+
# for a model.
|
8
|
+
#
|
9
|
+
# By default, the `primary_key` accessors maps to the `id` attribute. You can change
|
10
|
+
# the attribute by setting the `primary_key` at the class level.
|
11
|
+
#
|
12
|
+
# @example By default, the primary key maps to the `id` attribute
|
13
|
+
# class Employee
|
14
|
+
# include ActiveModelPersistence::PrimaryKey
|
15
|
+
# attribute :id, :integer
|
16
|
+
# end
|
17
|
+
# e1 = Employee.new(id: 1)
|
18
|
+
# e1.primary_key #=> 1
|
19
|
+
#
|
20
|
+
# @example Changing the primary key attribute
|
21
|
+
# class Employee
|
22
|
+
# include ActiveModelPersistence::PrimaryKey
|
23
|
+
# attribute :short_id, :string
|
24
|
+
# # change the primary key
|
25
|
+
# self.primary_key = :short_id
|
26
|
+
# end
|
27
|
+
# e1 = Employee.new(short_id: 'couballj')
|
28
|
+
# # `primary_key` can be used as an alias for `short_id`
|
29
|
+
# e1.primary_key #=> 'couballj'
|
30
|
+
# e1.primary_key = 'fthrock'
|
31
|
+
# e1.short_id #=> 'fthrock'
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
#
|
35
|
+
module PrimaryKey
|
36
|
+
extend ActiveSupport::Concern
|
37
|
+
|
38
|
+
include ActiveModel::Model
|
39
|
+
include ActiveModel::Attributes
|
40
|
+
|
41
|
+
class_methods do
|
42
|
+
# Identifies the attribute that the `primary_key` accessor maps to
|
43
|
+
#
|
44
|
+
# The primary key is 'id' by default.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# class Employee
|
48
|
+
# include ActiveModelPersistence::PrimaryKey
|
49
|
+
# attribute :username, :string
|
50
|
+
# self.primary_key = :username
|
51
|
+
# end
|
52
|
+
# Employee.primary_key #=> :username
|
53
|
+
#
|
54
|
+
# @return [Symbol] the attribute that the `primary_key` accessor is an alias for
|
55
|
+
#
|
56
|
+
def primary_key
|
57
|
+
@primary_key ||= 'id'
|
58
|
+
end
|
59
|
+
|
60
|
+
# Sets the attribute to use for the primary key
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# class Employee
|
64
|
+
# include ActiveModelPersistence::PrimaryKey
|
65
|
+
# attribute :username, :string
|
66
|
+
# primary_key = :username
|
67
|
+
# end
|
68
|
+
# e = Employee.new(username: 'couballj')
|
69
|
+
# e.primary_key #=> 'couballj'
|
70
|
+
#
|
71
|
+
# @param attribute [Symbol] the attribute to use for the primary key
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
#
|
75
|
+
def primary_key=(attribute)
|
76
|
+
@primary_key = attribute.to_s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
included do
|
81
|
+
# Returns the primary key attribute's value
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# class Employee
|
85
|
+
# include ActiveModelPersistence::PrimaryKey
|
86
|
+
# attribute :username, :string
|
87
|
+
# self.primary_key = :username
|
88
|
+
# end
|
89
|
+
# e = Employee.new(username: 'couballj')
|
90
|
+
# e.primary_key #=> 'couballj'
|
91
|
+
#
|
92
|
+
# @return [Object] the primary key attribute's value
|
93
|
+
#
|
94
|
+
def primary_key
|
95
|
+
__send__(self.class.primary_key)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Sets the primary key atribute's value
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# class Employee
|
102
|
+
# include ActiveModelPersistence::PrimaryKey
|
103
|
+
# attribute :username, :string
|
104
|
+
# primary_key = :username
|
105
|
+
# end
|
106
|
+
# e = Employee.new(username: 'couballj')
|
107
|
+
# e.primary_key = 'other'
|
108
|
+
# e.username #=> 'other'
|
109
|
+
#
|
110
|
+
# @param value [Object] the value to set the primary key attribute to
|
111
|
+
#
|
112
|
+
# @return [void]
|
113
|
+
#
|
114
|
+
def primary_key=(value)
|
115
|
+
__send__("#{self.class.primary_key}=", value)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns true if the primary key attribute's value is not null or empty
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# class Employee
|
122
|
+
# include ActiveModelPersistence::PrimaryKey
|
123
|
+
# attribute :id, :integer
|
124
|
+
# end
|
125
|
+
# e = Employee.new
|
126
|
+
# e.primary_key #=> nil
|
127
|
+
# e.primary_key? #=> false
|
128
|
+
# e.primary_key = 1
|
129
|
+
# e.primary_key? #=> true
|
130
|
+
#
|
131
|
+
# @return [Boolean] true if the primary key attribute's value is not null or empty
|
132
|
+
#
|
133
|
+
def primary_key?
|
134
|
+
primary_key.present?
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|