active_model_persistence 0.2.0 → 0.3.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/.rubocop.yml +3 -0
- data/CHANGELOG.md +23 -0
- data/Rakefile +1 -1
- data/bin/create-release +537 -0
- data/lib/active_model_persistence/indexable.rb +6 -1
- data/lib/active_model_persistence/persistence.rb +121 -12
- data/lib/active_model_persistence/primary_key.rb +4 -1
- data/lib/active_model_persistence/primary_key_index.rb +12 -3
- data/lib/active_model_persistence/version.rb +1 -1
- data/lib/active_model_persistence.rb +3 -24
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a36224415c3d71eed1f01b39413b896af90a3b808f15c081800f31114c6105d4
|
4
|
+
data.tar.gz: f77057857a7cd5a6687017d2c9228e9c60ceb38a1cce75484ce40d93259de9d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '084f1a34f5dbff248ab739cbe3ae95be6209d696db4b4b63191b6b5c6d0ee2b3674127b035a4eb1aef5529ae5400c5bce48d762d30193277e25865f595346ffe'
|
7
|
+
data.tar.gz: 4cba73228e285a16226418fade9a753c7378201db8bbe622ab8de1e0afb9c484bfc99fbbf2b59e438c2b801dec4101025cd4174ad277aa285ae1a94b1c6ee0a8
|
data/.rubocop.yml
CHANGED
@@ -8,6 +8,9 @@ AllCops:
|
|
8
8
|
# RuboCop enforces rules depending on the oldest version of Ruby which
|
9
9
|
# your project supports:
|
10
10
|
TargetRubyVersion: 2.7
|
11
|
+
Exclude:
|
12
|
+
- bin/create-release
|
13
|
+
- vendor/**/*
|
11
14
|
|
12
15
|
# The default max line length is 80 characters
|
13
16
|
Layout/LineLength:
|
data/CHANGELOG.md
CHANGED
@@ -6,3 +6,26 @@
|
|
6
6
|
# Change Log
|
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
|
+
|
10
|
+
## v0.3.0
|
11
|
+
|
12
|
+
* 3b93fae List all changes in CHANGELOG.md instead of just a link (#10)
|
13
|
+
* 76d8a0b Backfill previous released in the CHANGELOG.md (#9)
|
14
|
+
* 67da6ed Add experimental script to create release (#8)
|
15
|
+
* 7da26b6 Refactor #save and #save! (#7)
|
16
|
+
* 81e3730 Add the #update and #update! methods (#6)
|
17
|
+
* b4c5f20 Add .create! (#5)
|
18
|
+
* 436ee3d Use "module ClassMethods" instead of "do class_methods" for ActiveConcern (#4)
|
19
|
+
* 1be8b11 Run yard:audit before yard:coverage (#3)
|
20
|
+
|
21
|
+
See https://github.com/ruby-git/ruby-git/releases/tag/v0.3.0
|
22
|
+
|
23
|
+
## v0.2.0
|
24
|
+
|
25
|
+
[Full Changelog](https://github.com/jcouball/active_model_persistence/compare/v0.1.0...v0.2.0)
|
26
|
+
|
27
|
+
* ca12c6d Implement save! (#1)
|
28
|
+
|
29
|
+
## v0.1.0
|
30
|
+
|
31
|
+
Initial release
|
data/Rakefile
CHANGED
data/bin/create-release
ADDED
@@ -0,0 +1,537 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Run this script while in the root directory of the project with the default
|
5
|
+
# branch checked out.
|
6
|
+
|
7
|
+
require 'bump'
|
8
|
+
require 'English'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'optparse'
|
11
|
+
require 'tempfile'
|
12
|
+
|
13
|
+
# TODO: Right now the default branch and the remote name are hard coded
|
14
|
+
|
15
|
+
class Options
|
16
|
+
attr_accessor :current_version, :next_version, :tag, :current_tag, :next_tag, :branch, :quiet
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
yield self if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def release_type
|
23
|
+
raise 'release_type not set' if @release_type.nil?
|
24
|
+
|
25
|
+
@release_type
|
26
|
+
end
|
27
|
+
|
28
|
+
VALID_RELEASE_TYPES = %w[major minor patch].freeze
|
29
|
+
|
30
|
+
def release_type=(release_type)
|
31
|
+
unless VALID_RELEASE_TYPES.include?(release_type)
|
32
|
+
raise "release_type must be one of: #{VALID_RELEASE_TYPES.join(', ')}"
|
33
|
+
end
|
34
|
+
|
35
|
+
@release_type = release_type
|
36
|
+
end
|
37
|
+
|
38
|
+
def quiet
|
39
|
+
@quiet = false unless instance_variable_defined?(:@quiet)
|
40
|
+
@quiet
|
41
|
+
end
|
42
|
+
|
43
|
+
def current_version
|
44
|
+
@current_version ||= Bump::Bump.current
|
45
|
+
end
|
46
|
+
|
47
|
+
def next_version
|
48
|
+
current_version # Save the current version before bumping
|
49
|
+
@next_version ||= Bump::Bump.next_version(release_type)
|
50
|
+
end
|
51
|
+
|
52
|
+
def tag
|
53
|
+
@tag ||= "v#{next_version}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def current_tag
|
57
|
+
@current_tag ||= "v#{current_version}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def next_tag
|
61
|
+
tag
|
62
|
+
end
|
63
|
+
|
64
|
+
def branch
|
65
|
+
@branch ||= "release-#{tag}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_branch
|
69
|
+
@default_branch ||= `git remote show '#{remote}'`.match(/HEAD branch: (.*?)$/)[1]
|
70
|
+
end
|
71
|
+
|
72
|
+
def remote
|
73
|
+
@remote ||= 'origin'
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
<<~OUTPUT
|
78
|
+
release_type='#{release_type}'
|
79
|
+
current_version='#{current_version}'
|
80
|
+
next_version='#{next_version}'
|
81
|
+
tag='#{tag}'
|
82
|
+
branch='#{branch}'
|
83
|
+
quiet=#{quiet}
|
84
|
+
OUTPUT
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class CommandLineParser
|
89
|
+
attr_reader :options
|
90
|
+
|
91
|
+
def initialize
|
92
|
+
@option_parser = OptionParser.new
|
93
|
+
define_options
|
94
|
+
@options = Options.new
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse(args)
|
98
|
+
option_parser.parse!(remaining_args = args.dup)
|
99
|
+
parse_remaining_args(remaining_args)
|
100
|
+
# puts options unless options.quiet
|
101
|
+
options
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
attr_reader :option_parser
|
107
|
+
|
108
|
+
def parse_remaining_args(remaining_args)
|
109
|
+
error_with_usage('No release type specified') if remaining_args.empty?
|
110
|
+
@options.release_type = remaining_args.shift || nil
|
111
|
+
error_with_usage('Too many args') unless remaining_args.empty?
|
112
|
+
end
|
113
|
+
|
114
|
+
def error_with_usage(message)
|
115
|
+
warn <<~MESSAGE
|
116
|
+
ERROR: #{message}
|
117
|
+
#{option_parser}
|
118
|
+
MESSAGE
|
119
|
+
exit 1
|
120
|
+
end
|
121
|
+
|
122
|
+
def define_options
|
123
|
+
option_parser.banner = 'Usage: create_release --help | release-type'
|
124
|
+
option_parser.separator ''
|
125
|
+
option_parser.separator 'Options:'
|
126
|
+
|
127
|
+
define_quiet_option
|
128
|
+
define_help_option
|
129
|
+
end
|
130
|
+
|
131
|
+
def define_quiet_option
|
132
|
+
option_parser.on('-q', '--[no-]quiet', 'Do not show output') do |quiet|
|
133
|
+
options.quiet = quiet
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def define_help_option
|
138
|
+
option_parser.on_tail('-h', '--help', 'Show this message') do
|
139
|
+
puts option_parser
|
140
|
+
exit 0
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class ReleaseAssertions
|
146
|
+
attr_reader :options
|
147
|
+
|
148
|
+
def initialize(options)
|
149
|
+
@options = options
|
150
|
+
end
|
151
|
+
|
152
|
+
def make_assertions
|
153
|
+
bundle_is_up_to_date
|
154
|
+
in_git_repo
|
155
|
+
in_repo_toplevel_directory
|
156
|
+
on_default_branch
|
157
|
+
no_uncommitted_changes
|
158
|
+
local_and_remote_on_same_commit
|
159
|
+
tag_does_not_exist
|
160
|
+
branch_does_not_exist
|
161
|
+
docker_is_running
|
162
|
+
changelog_docker_container_exists
|
163
|
+
gh_command_exists
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def gh_command_exists
|
169
|
+
print 'Checking that the gh command exists...'
|
170
|
+
`which gh > /dev/null 2>&1`
|
171
|
+
if $CHILD_STATUS.success?
|
172
|
+
puts 'OK'
|
173
|
+
else
|
174
|
+
error 'The gh command was not found'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def docker_is_running
|
179
|
+
print 'Checking that docker is installed and running...'
|
180
|
+
`docker info > /dev/null 2>&1`
|
181
|
+
if $CHILD_STATUS.success?
|
182
|
+
puts 'OK'
|
183
|
+
else
|
184
|
+
error 'Docker is not installed or not running'
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def changelog_docker_container_exists
|
189
|
+
print 'Checking that the changelog docker container exists (might take time to build)...'
|
190
|
+
`docker build --file Dockerfile.changelog-rs --tag changelog-rs . 1>/dev/null`
|
191
|
+
if $CHILD_STATUS.success?
|
192
|
+
puts 'OK'
|
193
|
+
else
|
194
|
+
error 'Failed to build the changelog-rs docker container'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def bundle_is_up_to_date
|
199
|
+
print 'Checking that the bundle is up to date...'
|
200
|
+
if File.exist?('Gemfile.lock')
|
201
|
+
print 'Running bundle update...'
|
202
|
+
`bundle update --quiet`
|
203
|
+
if $CHILD_STATUS.success?
|
204
|
+
puts 'OK'
|
205
|
+
else
|
206
|
+
error 'bundle update failed'
|
207
|
+
end
|
208
|
+
else
|
209
|
+
print 'Running bundle install...'
|
210
|
+
`bundle install --quiet`
|
211
|
+
if $CHILD_STATUS.success?
|
212
|
+
puts 'OK'
|
213
|
+
else
|
214
|
+
error 'bundle install failed'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def in_git_repo
|
220
|
+
print 'Checking that you are in a git repo...'
|
221
|
+
`git rev-parse --is-inside-work-tree --quiet > /dev/null 2>&1`
|
222
|
+
if $CHILD_STATUS.success?
|
223
|
+
puts 'OK'
|
224
|
+
else
|
225
|
+
error 'You are not in a git repo'
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def in_repo_toplevel_directory
|
230
|
+
print "Checking that you are in the repo's toplevel directory..."
|
231
|
+
toplevel_directory = `git rev-parse --show-toplevel`.chomp
|
232
|
+
if toplevel_directory == FileUtils.pwd
|
233
|
+
puts 'OK'
|
234
|
+
else
|
235
|
+
error "You are not in the repo's toplevel directory"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def on_default_branch
|
240
|
+
print 'Checking that you are on the default branch...'
|
241
|
+
current_branch = `git branch --show-current`.chomp
|
242
|
+
if current_branch == options.default_branch
|
243
|
+
puts 'OK'
|
244
|
+
else
|
245
|
+
error "You are not on the default branch '#{default_branch}'"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def no_uncommitted_changes
|
250
|
+
print 'Checking that there are no uncommitted changes...'
|
251
|
+
if `git status --porcelain | wc -l`.to_i.zero?
|
252
|
+
puts 'OK'
|
253
|
+
else
|
254
|
+
error 'There are uncommitted changes'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def no_staged_changes
|
259
|
+
print 'Checking that there are no staged changes...'
|
260
|
+
if `git diff --staged --name-only | wc -l`.to_i.zero?
|
261
|
+
puts 'OK'
|
262
|
+
else
|
263
|
+
error 'There are staged changes'
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def local_and_remote_on_same_commit
|
268
|
+
print 'Checking that local and remote are on the same commit...'
|
269
|
+
local_commit = `git rev-parse HEAD`.chomp
|
270
|
+
remote_commit = `git ls-remote '#{options.remote}' '#{options.default_branch}' | cut -f 1`.chomp
|
271
|
+
if local_commit == remote_commit
|
272
|
+
puts 'OK'
|
273
|
+
else
|
274
|
+
error 'Local and remote are not on the same commit'
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def local_tag_does_not_exist
|
279
|
+
print "Checking that local tag '#{options.tag}' does not exist..."
|
280
|
+
|
281
|
+
tags = `git tag --list "#{options.tag}"`.chomp
|
282
|
+
error 'Could not list tags' unless $CHILD_STATUS.success?
|
283
|
+
|
284
|
+
if tags.split.empty?
|
285
|
+
puts 'OK'
|
286
|
+
else
|
287
|
+
error "'#{options.tag}' already exists"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def remote_tag_does_not_exist
|
292
|
+
print "Checking that the remote tag '#{options.tag}' does not exist..."
|
293
|
+
`git ls-remote --tags --exit-code '#{options.remote}' #{options.tag} >/dev/null 2>&1`
|
294
|
+
if $CHILD_STATUS.success?
|
295
|
+
error "'#{options.tag}' already exists"
|
296
|
+
else
|
297
|
+
puts 'OK'
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def tag_does_not_exist
|
302
|
+
local_tag_does_not_exist
|
303
|
+
remote_tag_does_not_exist
|
304
|
+
end
|
305
|
+
|
306
|
+
def local_branch_does_not_exist
|
307
|
+
print "Checking that local branch '#{options.branch}' does not exist..."
|
308
|
+
|
309
|
+
if `git branch --list "#{options.branch}" | wc -l`.to_i.zero?
|
310
|
+
puts 'OK'
|
311
|
+
else
|
312
|
+
error "'#{options.branch}' already exists."
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def remote_branch_does_not_exist
|
317
|
+
print "Checking that the remote branch '#{options.branch}' does not exist..."
|
318
|
+
`git ls-remote --heads --exit-code '#{options.remote}' '#{options.branch}' >/dev/null 2>&1`
|
319
|
+
if $CHILD_STATUS.success?
|
320
|
+
error "'#{options.branch}' already exists"
|
321
|
+
else
|
322
|
+
puts 'OK'
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def branch_does_not_exist
|
327
|
+
local_branch_does_not_exist
|
328
|
+
remote_branch_does_not_exist
|
329
|
+
end
|
330
|
+
|
331
|
+
def print(*args)
|
332
|
+
super unless options.quiet
|
333
|
+
end
|
334
|
+
|
335
|
+
def puts(*args)
|
336
|
+
super unless options.quiet
|
337
|
+
end
|
338
|
+
|
339
|
+
def error(message)
|
340
|
+
warn "ERROR: #{message}"
|
341
|
+
exit 1
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
class ReleaseCreator
|
346
|
+
attr_reader :options
|
347
|
+
|
348
|
+
def initialize(options)
|
349
|
+
@options = options
|
350
|
+
end
|
351
|
+
|
352
|
+
def create_release
|
353
|
+
create_branch
|
354
|
+
update_changelog
|
355
|
+
update_version
|
356
|
+
make_release_commit
|
357
|
+
create_tag
|
358
|
+
push_release_commit_and_tag
|
359
|
+
create_github_release
|
360
|
+
create_release_pull_request
|
361
|
+
end
|
362
|
+
|
363
|
+
private
|
364
|
+
|
365
|
+
def create_branch
|
366
|
+
print "Creating branch '#{options.branch}'..."
|
367
|
+
`git checkout -b "#{options.branch}" > /dev/null 2>&1`
|
368
|
+
if $CHILD_STATUS.success?
|
369
|
+
puts 'OK'
|
370
|
+
else
|
371
|
+
error "Could not create branch '#{options.branch}'" unless $CHILD_STATUS.success?
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def update_changelog
|
376
|
+
print 'Updating CHANGELOG.md...'
|
377
|
+
changelog_lines = File.readlines('CHANGELOG.md')
|
378
|
+
first_entry = changelog_lines.index { |e| e =~ /^## / }
|
379
|
+
error 'Could not find changelog insertion point' unless first_entry
|
380
|
+
FileUtils.rm('CHANGELOG.md')
|
381
|
+
File.write('CHANGELOG.md', <<~CHANGELOG.chomp)
|
382
|
+
#{changelog_lines[0..first_entry - 1].join.chomp}
|
383
|
+
## #{options.tag}
|
384
|
+
|
385
|
+
#{changelog(to: 'HEAD').lines[2..].join}
|
386
|
+
|
387
|
+
See https://github.com/ruby-git/ruby-git/releases/tag/#{options.tag}
|
388
|
+
|
389
|
+
#{changelog_lines[first_entry..].join}
|
390
|
+
CHANGELOG
|
391
|
+
`git add CHANGELOG.md`
|
392
|
+
if $CHILD_STATUS.success?
|
393
|
+
puts 'OK'
|
394
|
+
else
|
395
|
+
error 'Could not stage changes to CHANGELOG.md'
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def update_version
|
400
|
+
print 'Updating version...'
|
401
|
+
message, status = Bump::Bump.run(options.release_type, commit: false)
|
402
|
+
error 'Could not bump version' unless status.zero?
|
403
|
+
version_file = Bump::Bump.file
|
404
|
+
`git add '#{version_file}'`
|
405
|
+
if $CHILD_STATUS.success?
|
406
|
+
puts 'OK'
|
407
|
+
else
|
408
|
+
error "Could not stage changes to the version file '#{version_file}'"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def make_release_commit
|
413
|
+
print 'Making release commit...'
|
414
|
+
`git commit -s -m 'Release #{options.tag}'`
|
415
|
+
error 'Could not make release commit' unless $CHILD_STATUS.success?
|
416
|
+
end
|
417
|
+
|
418
|
+
def create_tag
|
419
|
+
print "Creating tag '#{options.tag}'..."
|
420
|
+
`git tag '#{options.tag}'`
|
421
|
+
if $CHILD_STATUS.success?
|
422
|
+
puts 'OK'
|
423
|
+
else
|
424
|
+
error "Could not create tag '#{options.tag}'"
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def push_release_commit_and_tag
|
429
|
+
print "Pushing branch '#{options.branch}' to remote..."
|
430
|
+
`git push --tags --set-upstream '#{options.remote}' '#{options.branch}' > /dev/null 2>&1`
|
431
|
+
if $CHILD_STATUS.success?
|
432
|
+
puts 'OK'
|
433
|
+
else
|
434
|
+
error 'Could not push release commit'
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def changelog(from: options.current_tag, to: options.next_tag)
|
439
|
+
@changelog ||= begin
|
440
|
+
print "Generating changelog from #{from} to #{to}..."
|
441
|
+
pwd = FileUtils.pwd
|
442
|
+
command = "docker run --rm --volume '#{pwd}:/worktree' changelog-rs '#{from}' '#{to}'"
|
443
|
+
changelog = `#{command}`.chomp
|
444
|
+
if $CHILD_STATUS.success?
|
445
|
+
puts 'OK'
|
446
|
+
changelog.rstrip.lines[1..].join
|
447
|
+
else
|
448
|
+
error 'Could not generate the changelog'
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
def create_github_release
|
454
|
+
Tempfile.create do |f|
|
455
|
+
f.write changelog
|
456
|
+
f.close
|
457
|
+
|
458
|
+
print "Creating GitHub release '#{options.tag}'..."
|
459
|
+
tag = options.tag
|
460
|
+
`gh release create #{tag} --title 'Release #{tag}' --notes-file '#{f.path}' --target #{options.default_branch}`
|
461
|
+
if $CHILD_STATUS.success?
|
462
|
+
puts 'OK'
|
463
|
+
else
|
464
|
+
error 'Could not create release'
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def create_release_pull_request
|
470
|
+
Tempfile.create do |f|
|
471
|
+
f.write <<~PR
|
472
|
+
### Your checklist for this pull request
|
473
|
+
🚨Please review the [guidelines for contributing](https://github.com/ruby-git/ruby-git/blob/#{options.default_branch}/CONTRIBUTING.md) to this repository.
|
474
|
+
|
475
|
+
- [X] Ensure all commits include DCO sign-off.
|
476
|
+
- [X] Ensure that your contributions pass unit testing.
|
477
|
+
- [X] Ensure that your contributions contain documentation if applicable.
|
478
|
+
|
479
|
+
### Description
|
480
|
+
#{changelog}
|
481
|
+
|
482
|
+
### Next Steps
|
483
|
+
|
484
|
+
DO NOT MERGE THIS PULL REQUEST VIA THE GITHUB UI
|
485
|
+
|
486
|
+
* Get someone to review and approve the release pull request
|
487
|
+
* Merge the pull request manually from the command line with the following commands:
|
488
|
+
|
489
|
+
```Ruby
|
490
|
+
git checkout #{options.default_branch}
|
491
|
+
git merge --ff-only #{options.branch}
|
492
|
+
git push
|
493
|
+
````
|
494
|
+
PR
|
495
|
+
f.close
|
496
|
+
|
497
|
+
print 'Creating GitHub pull request...'
|
498
|
+
`gh pr create --title 'Release #{options.tag}' --body-file '#{f.path}' --base '#{options.default_branch}'`
|
499
|
+
if $CHILD_STATUS.success?
|
500
|
+
puts 'OK'
|
501
|
+
else
|
502
|
+
error 'Could not create release pull request'
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def error(message)
|
508
|
+
warn "ERROR: #{message}"
|
509
|
+
exit 1
|
510
|
+
end
|
511
|
+
|
512
|
+
def print(*args)
|
513
|
+
super unless options.quiet
|
514
|
+
end
|
515
|
+
|
516
|
+
def puts(*args)
|
517
|
+
super unless options.quiet
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
options = CommandLineParser.new.parse(ARGV)
|
522
|
+
ReleaseAssertions.new(options).make_assertions
|
523
|
+
puts unless options.quiet
|
524
|
+
ReleaseCreator.new(options).create_release
|
525
|
+
|
526
|
+
puts <<~MESSAGE unless options.quiet
|
527
|
+
Release '#{options.tag}' created successfully
|
528
|
+
See the release notes at https://github.com/jcouball/release_testing/releases
|
529
|
+
|
530
|
+
Next steps:
|
531
|
+
* Get someone to review and approve the release pull request
|
532
|
+
* Merge the pull request manually from the command line with the following commands:
|
533
|
+
|
534
|
+
git checkout #{options.default_branch}
|
535
|
+
git merge --ff-only #{options.branch}
|
536
|
+
git push
|
537
|
+
MESSAGE
|
@@ -56,7 +56,10 @@ module ActiveModelPersistence
|
|
56
56
|
include ActiveModel::Attributes
|
57
57
|
include ActiveModelPersistence::PrimaryKey
|
58
58
|
|
59
|
-
|
59
|
+
# When this module is included in another class, ActiveSupport::Concern will
|
60
|
+
# make these class methods on that class.
|
61
|
+
#
|
62
|
+
module ClassMethods
|
60
63
|
# Returns a hash of indexes for the model keyed by name
|
61
64
|
#
|
62
65
|
# @example
|
@@ -148,6 +151,8 @@ module ActiveModelPersistence
|
|
148
151
|
|
149
152
|
# Defines the default options for a new ActiveModelPersistence::Index
|
150
153
|
#
|
154
|
+
# @return [Hash] the default options
|
155
|
+
#
|
151
156
|
# @api private
|
152
157
|
#
|
153
158
|
def default_index_options(index_name)
|
@@ -53,11 +53,16 @@ module ActiveModelPersistence
|
|
53
53
|
include ActiveModelPersistence::PrimaryKey
|
54
54
|
include ActiveModelPersistence::PrimaryKeyIndex
|
55
55
|
|
56
|
-
|
57
|
-
|
56
|
+
# When this module is included in another class, ActiveSupport::Concern will
|
57
|
+
# make these class methods on that class.
|
58
|
+
#
|
59
|
+
module ClassMethods
|
60
|
+
# Creates a new model object in to the object store and returns it
|
58
61
|
#
|
59
62
|
# Create a new model object passing `attributes` and `block` to `.new` and then calls `#save`.
|
60
63
|
#
|
64
|
+
# The new model object is returned even if it could not be saved to the object store.
|
65
|
+
#
|
61
66
|
# @param attributes [Hash, Array<Hash>] attributes
|
62
67
|
#
|
63
68
|
# The attributes to set on the model object. These are passed to the model's `.new` method.
|
@@ -94,6 +99,50 @@ module ActiveModelPersistence
|
|
94
99
|
end
|
95
100
|
end
|
96
101
|
|
102
|
+
# Creates a new model object in to the object store
|
103
|
+
#
|
104
|
+
# Raises an error if the object could not be created.
|
105
|
+
#
|
106
|
+
# Create a new model object passing `attributes` and `block` to `.new` and then calls `#save!`.
|
107
|
+
#
|
108
|
+
# @param attributes [Hash, Array<Hash>] attributes
|
109
|
+
#
|
110
|
+
# The attributes to set on the model object. These are passed to the model's `.new` method.
|
111
|
+
#
|
112
|
+
# Multiple model objects can be created by passing an array of attribute Hashes.
|
113
|
+
#
|
114
|
+
# @param block [Proc] options
|
115
|
+
#
|
116
|
+
# The block to pass to the model's `.new` method.
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# m = ModelExample.new(id: 1, name: 'James')
|
120
|
+
# m.id #=> 1
|
121
|
+
# m.name #=> 'James'
|
122
|
+
#
|
123
|
+
# @example Multiple model objects can be created
|
124
|
+
# array_of_attributes = [
|
125
|
+
# { id: 1, name: 'James' },
|
126
|
+
# { id: 2, name: 'Frank' }
|
127
|
+
# ]
|
128
|
+
# objects = ModelExample.create(array_of_attributes)
|
129
|
+
# objects.class #=> Array
|
130
|
+
# objects.size #=> 2
|
131
|
+
# objects.first.id #=> 1
|
132
|
+
# objects.map(&:name) #=> ['James', 'Frank']
|
133
|
+
#
|
134
|
+
# @return [Object, Array<Object>] the model object or array of model objects created
|
135
|
+
#
|
136
|
+
# @raise [ModelError] if the model object could not be created
|
137
|
+
#
|
138
|
+
def create!(attributes = nil, &block)
|
139
|
+
if attributes.is_a?(Array)
|
140
|
+
attributes.collect { |attr| create!(attr, &block) }
|
141
|
+
else
|
142
|
+
new(attributes, &block).tap(&:save!)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
97
146
|
# Return all model objects that have been saved to the object store
|
98
147
|
#
|
99
148
|
# @example
|
@@ -128,7 +177,7 @@ module ActiveModelPersistence
|
|
128
177
|
object_array.size
|
129
178
|
end
|
130
179
|
|
131
|
-
|
180
|
+
alias size count
|
132
181
|
|
133
182
|
# Removes all model objects from the object store
|
134
183
|
#
|
@@ -258,7 +307,7 @@ module ActiveModelPersistence
|
|
258
307
|
# object.save
|
259
308
|
# ModelExample.all.count #=> 1
|
260
309
|
#
|
261
|
-
# @param
|
310
|
+
# @param options [Hash] save options (currently unused)
|
262
311
|
# @param block [Proc] a block to call after the save
|
263
312
|
#
|
264
313
|
# @yield [self] a block to call after the save
|
@@ -267,12 +316,12 @@ module ActiveModelPersistence
|
|
267
316
|
#
|
268
317
|
# @return [Boolean] true if the model object was saved
|
269
318
|
#
|
270
|
-
def save(**
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
319
|
+
def save(**options, &block)
|
320
|
+
save!(**options, &block)
|
321
|
+
rescue ModelError
|
322
|
+
false
|
323
|
+
else
|
324
|
+
true
|
276
325
|
end
|
277
326
|
|
278
327
|
# Calls #save and raises an error if #save returns false
|
@@ -293,8 +342,13 @@ module ActiveModelPersistence
|
|
293
342
|
#
|
294
343
|
# @return [Boolean] returns true or raises an error
|
295
344
|
#
|
296
|
-
def save!(**
|
297
|
-
|
345
|
+
def save!(**_options, &block)
|
346
|
+
raise ObjectDestroyedError if destroyed?
|
347
|
+
raise ObjectNotValidError unless valid?
|
348
|
+
|
349
|
+
new_record? ? _create(&block) : _update(&block)
|
350
|
+
update_indexes
|
351
|
+
true
|
298
352
|
end
|
299
353
|
|
300
354
|
# Deletes the object from the object store
|
@@ -328,10 +382,57 @@ module ActiveModelPersistence
|
|
328
382
|
attributes == other.attributes
|
329
383
|
end
|
330
384
|
|
385
|
+
# Updates the attributes of the model and saves it
|
386
|
+
#
|
387
|
+
# The attributes are updated from the passed in hash. If the object is invalid,
|
388
|
+
# the save will fail and false will be returned.
|
389
|
+
#
|
390
|
+
# @example
|
391
|
+
# object = ModelExample.create(id: 1, name: 'James')
|
392
|
+
# object.update(name: 'Frank')
|
393
|
+
# object.find(1).name #=> 'Frank'
|
394
|
+
#
|
395
|
+
# @param attributes [Hash] the attributes to update
|
396
|
+
#
|
397
|
+
# @return [Boolean] true if the model object was saved, otherwise false
|
398
|
+
#
|
399
|
+
def update(attributes)
|
400
|
+
update!(attributes)
|
401
|
+
rescue ModelError
|
402
|
+
false
|
403
|
+
else
|
404
|
+
true
|
405
|
+
end
|
406
|
+
|
407
|
+
# Updates just like #update but an exception is raised of the model is invalid
|
408
|
+
#
|
409
|
+
# @example
|
410
|
+
# object = ModelExample.create(id: 1, name: 'James')
|
411
|
+
# object.update!(id: nil) #=> raises ObjectNotSavedError
|
412
|
+
#
|
413
|
+
# @param attributes [Hash] the attributes to update
|
414
|
+
#
|
415
|
+
# @return [Boolean] true if the model object was saved, otherwise an error is raised
|
416
|
+
#
|
417
|
+
# @raise [ObjectNotValidError] if the model object is invalid
|
418
|
+
# @raise [ObjectDestroyedError] if the model object was previously destroyed
|
419
|
+
#
|
420
|
+
def update!(attributes)
|
421
|
+
raise ObjectDestroyedError if destroyed?
|
422
|
+
|
423
|
+
assign_attributes(attributes)
|
424
|
+
save!
|
425
|
+
end
|
426
|
+
|
331
427
|
private
|
332
428
|
|
333
429
|
# Creates a record with values matching those of the instance attributes
|
334
430
|
# and returns its id.
|
431
|
+
#
|
432
|
+
# @return [Object] the primary_key of the created object
|
433
|
+
#
|
434
|
+
# @api private
|
435
|
+
#
|
335
436
|
def _create
|
336
437
|
return false unless primary_key?
|
337
438
|
raise UniqueContraintError if primary_key_index.include?(primary_key)
|
@@ -345,10 +446,18 @@ module ActiveModelPersistence
|
|
345
446
|
primary_key
|
346
447
|
end
|
347
448
|
|
449
|
+
# Updates an object that is already in the object store
|
450
|
+
#
|
451
|
+
# @return [Boolean] true if the object was update successfully, otherwise raises a ModelError
|
452
|
+
#
|
453
|
+
# @api private
|
454
|
+
#
|
348
455
|
def _update
|
349
456
|
raise RecordNotFound unless primary_key_index.include?(primary_key)
|
350
457
|
|
351
458
|
yield(self) if block_given?
|
459
|
+
|
460
|
+
true
|
352
461
|
end
|
353
462
|
end
|
354
463
|
# rubocop:enable Metrics/BlockLength
|
@@ -38,7 +38,10 @@ module ActiveModelPersistence
|
|
38
38
|
include ActiveModel::Model
|
39
39
|
include ActiveModel::Attributes
|
40
40
|
|
41
|
-
|
41
|
+
# When this module is included in another class, ActiveSupport::Concern will
|
42
|
+
# make these class methods on that class.
|
43
|
+
#
|
44
|
+
module ClassMethods
|
42
45
|
# Identifies the attribute that the `primary_key` accessor maps to
|
43
46
|
#
|
44
47
|
# The primary key is 'id' by default.
|
@@ -16,7 +16,10 @@ module ActiveModelPersistence
|
|
16
16
|
include ActiveModelPersistence::PrimaryKey
|
17
17
|
include ActiveModelPersistence::Indexable
|
18
18
|
|
19
|
-
|
19
|
+
# When this module is included in another class, ActiveSupport::Concern will
|
20
|
+
# make these class methods on that class.
|
21
|
+
#
|
22
|
+
module ClassMethods
|
20
23
|
# Finds an object in the :primary_key index whose primary matches the given value
|
21
24
|
#
|
22
25
|
# @example
|
@@ -37,10 +40,10 @@ module ActiveModelPersistence
|
|
37
40
|
find_by_primary_key(primary_key_value).first
|
38
41
|
end
|
39
42
|
|
40
|
-
private
|
41
|
-
|
42
43
|
# Create the primary key index
|
43
44
|
#
|
45
|
+
# @return [void]
|
46
|
+
#
|
44
47
|
# @api private
|
45
48
|
#
|
46
49
|
def self.extended(base)
|
@@ -49,6 +52,12 @@ module ActiveModelPersistence
|
|
49
52
|
end
|
50
53
|
|
51
54
|
included do
|
55
|
+
# Returns the primary key index
|
56
|
+
#
|
57
|
+
# @return [ActiveModelPersistence::Index]
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
#
|
52
61
|
def primary_key_index
|
53
62
|
self.class.indexes[:primary_key]
|
54
63
|
end
|
@@ -18,31 +18,10 @@ module ActiveModelPersistence
|
|
18
18
|
class UniqueConstraintError < ModelError; end
|
19
19
|
|
20
20
|
# Raised when trying to save an invalid object
|
21
|
-
class
|
22
|
-
# The object that was not saved
|
23
|
-
#
|
24
|
-
# @example
|
25
|
-
# object = Object.new
|
26
|
-
# error = ObjectNotSavedError.new('Invalid object', object)
|
27
|
-
# error.object == object #=> true
|
28
|
-
#
|
29
|
-
# @return [Object] The object that was not saved
|
30
|
-
#
|
31
|
-
attr_reader :object
|
21
|
+
class ObjectNotValidError < ModelError; end
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
# @example
|
36
|
-
# ObjectNotSavedError.new('Invalid object', self)
|
37
|
-
#
|
38
|
-
# @param message [String] The error message
|
39
|
-
# @param object [Object] The object that was not saved
|
40
|
-
#
|
41
|
-
def initialize(message = nil, object = nil)
|
42
|
-
@object = object
|
43
|
-
super(message)
|
44
|
-
end
|
45
|
-
end
|
23
|
+
# Raised when trying to save! or update! an object that has already been destroyed
|
24
|
+
class ObjectDestroyedError < ModelError; end
|
46
25
|
end
|
47
26
|
|
48
27
|
require_relative 'active_model_persistence/index'
|
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.3.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-01-
|
11
|
+
date: 2022-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -197,6 +197,7 @@ files:
|
|
197
197
|
- Rakefile
|
198
198
|
- active_model_persistence.gemspec
|
199
199
|
- bin/console
|
200
|
+
- bin/create-release
|
200
201
|
- bin/setup
|
201
202
|
- lib/active_model_persistence.rb
|
202
203
|
- lib/active_model_persistence/index.rb
|