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