nested_attribute_reassignable 0.6.2
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/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Appraisals +7 -0
- data/Gemfile +11 -0
- data/Guardfile +43 -0
- data/README.md +117 -0
- data/Rakefile +6 -0
- data/bin/appraisal +17 -0
- data/bin/console +14 -0
- data/bin/rspec +16 -0
- data/bin/setup +8 -0
- data/gemfiles/activerecord_4.gemfile +14 -0
- data/gemfiles/activerecord_4.gemfile.lock +112 -0
- data/gemfiles/activerecord_5.gemfile +14 -0
- data/gemfiles/activerecord_5.gemfile.lock +109 -0
- data/lib/nested_attribute_reassignable.rb +136 -0
- data/lib/nested_attribute_reassignable/version.rb +3 -0
- data/nested_attribute_reassignable.gemspec +27 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1bda179cf854b25b013cadbbb7838bf11572c4f7
|
4
|
+
data.tar.gz: 8b4afb437325e23f618343374d58f9c8a7c57257
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e0c05e1fce17388ebf90640b94f5785e78c93bcf96f76e8344f27c724bbc093a78fafc6faef9a267f9455da52b2afdced51a243014e518ba284dcd194ed664d
|
7
|
+
data.tar.gz: ea6998aa4c6bca31fb52ce89ecd3f97390e6a26e0bc9e6915ace85314de280acd30aaf5bf5d688e3179ff8bc1d3940fec9623a2f31650b9b0dfcff7d57bc4d72
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Appraisals
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
watch(%r{^spec/(.*)\/?(.*)_spec\.rb$})
|
31
|
+
|
32
|
+
# Feel free to open issues for suggestions and improvements
|
33
|
+
|
34
|
+
# RSpec files
|
35
|
+
rspec = dsl.rspec
|
36
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
38
|
+
watch(rspec.spec_files)
|
39
|
+
|
40
|
+
# Ruby files
|
41
|
+
ruby = dsl.ruby
|
42
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
43
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# NestedAttributeReassignable
|
2
|
+
|
3
|
+
[](https://travis-ci.org/jsonapi-suite/nested_attribute_reassignable)
|
4
|
+
|
5
|
+
Normal `accepts_nested_attributes_for` works find when all objects being
|
6
|
+
created are unpersisted. But if the base object is unpersisted and being
|
7
|
+
associated to pre-existing records, it will blow up:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
pet = Pet.create!(name: 'Spot')
|
11
|
+
Person.create(name: 'Joe', pets_attributes: [{ id: pet.id }])
|
12
|
+
# => ActiveRecord::RecordNotFound: Couldn't find Pet with ID=1 for Person with ID=
|
13
|
+
```
|
14
|
+
|
15
|
+
This gem allows you to assign pre-existing records without error
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'nested_attribute_reassignable'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install nested_attribute_reassignable
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
* Instead of `accepts_nested_attributes_for` use
|
36
|
+
`reassignable_nested_attributes_for`.
|
37
|
+
* You can pass nested IDs **or** nested attributes, not both. If you
|
38
|
+
pass both, the attributes will be dropped and the nested record will
|
39
|
+
not update:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
pet = Pet.create!(name: 'Spot')
|
43
|
+
person = Person.create(name: 'Joe', pets_attributes: [{ id: pet.id,
|
44
|
+
name: 'Elmo' }])
|
45
|
+
person.reload.pets.first.id == pet.id # => true
|
46
|
+
person.reload.pets.first.name # => 'Spot', not 'Elmo'
|
47
|
+
```
|
48
|
+
|
49
|
+
`nested_attribute_reassignable` internally calls `accepts_nested_attributes_for`
|
50
|
+
with `allow_destroy: true` option.
|
51
|
+
|
52
|
+
Supports customizing the lookup_key for nested attributes as shown below
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class Person < ApplicationRecord
|
56
|
+
has_many :pets
|
57
|
+
has_many :bills
|
58
|
+
has_many :services, through: :bills, through: :destroy
|
59
|
+
|
60
|
+
reassignable_nested_attributes_for :services, lookup_key: :name
|
61
|
+
reassignable_nested_attributes_for :pets, lookup_key: :name
|
62
|
+
end
|
63
|
+
|
64
|
+
rent = Service.create!(name: 'Rent')
|
65
|
+
mobile = Service.create!(name: 'Mobile')
|
66
|
+
cat = Pet.create!(name: 'Cat')
|
67
|
+
|
68
|
+
person = Person.create({
|
69
|
+
name: 'Joe',
|
70
|
+
services_attributes: [{ id: rent.name }]),
|
71
|
+
pets_attributes: [{ id: cat.name }])
|
72
|
+
}
|
73
|
+
|
74
|
+
person.reload.bills.first.service_id == rent.id # => true
|
75
|
+
person.reload.pets.first.id == cat.id # => true
|
76
|
+
|
77
|
+
person.update_attributes({
|
78
|
+
pets_attributes: [{ id: cat.name, _destroy: true }])
|
79
|
+
}
|
80
|
+
|
81
|
+
#has_many
|
82
|
+
person.reload.pets #=> []
|
83
|
+
Pet.all #=> [] deletes associated records
|
84
|
+
|
85
|
+
#has_many => through
|
86
|
+
person.update_attributes({
|
87
|
+
services_attributes: [{ id: rent.name, _destroy: true }])
|
88
|
+
}
|
89
|
+
|
90
|
+
person.reload.bills #=> []
|
91
|
+
Service.all #=> [rent, mobile] won't destroy Service, only the join record
|
92
|
+
|
93
|
+
person.update_attributes({
|
94
|
+
services_attributes: [{ id: rent.name, _delete: true }])
|
95
|
+
}
|
96
|
+
|
97
|
+
person.reload.bills #=> []
|
98
|
+
Service.all #=> [rent, mobile] won't delete Service, only the join record
|
99
|
+
```
|
100
|
+
|
101
|
+
|
102
|
+
### _delete
|
103
|
+
|
104
|
+
Normal `accepts_nested_attributes_for` accepts a `_destroy` parameter
|
105
|
+
for destroying the association. This will destroy the underlying record.
|
106
|
+
If you only want to disassociate the record, you can now use `_delete`.
|
107
|
+
|
108
|
+
### Running the tests
|
109
|
+
|
110
|
+
This library uses [appraisal](https://github.com/thoughtbot/appraisal) to test against activerecord >= 4.1. Run
|
111
|
+
|
112
|
+
```bash
|
113
|
+
$ bin/appraisal activerecord-4 rspec
|
114
|
+
$ bin/appraisal activerecord-5 rspec
|
115
|
+
```
|
116
|
+
|
117
|
+
Or `bin/appraisal rspec` to run against all versions.
|
data/Rakefile
ADDED
data/bin/appraisal
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'appraisal' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
|
10
|
+
require "pathname"
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
|
+
Pathname.new(__FILE__).realpath)
|
13
|
+
|
14
|
+
require "rubygems"
|
15
|
+
require "bundler/setup"
|
16
|
+
|
17
|
+
load Gem.bin_path("appraisal", "appraisal")
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "nested_attribute_reassignable"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require "pathname"
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require "rubygems"
|
14
|
+
require "bundler/setup"
|
15
|
+
|
16
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/setup
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "http://artprod.dev.bloomberg.com/artifactory/api/gems/rubygems/"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.1"
|
6
|
+
|
7
|
+
group :test do
|
8
|
+
gem "appraisal"
|
9
|
+
gem "pry"
|
10
|
+
gem "pry-byebug"
|
11
|
+
gem "guard-rspec", "~> 4.7"
|
12
|
+
end
|
13
|
+
|
14
|
+
gemspec :path => "../"
|
@@ -0,0 +1,112 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
nested_attribute_reassignable (0.6.1)
|
5
|
+
activerecord (>= 4.1, < 6)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://artprod.dev.bloomberg.com/artifactory/api/gems/rubygems/
|
9
|
+
specs:
|
10
|
+
activemodel (4.2.6)
|
11
|
+
activesupport (= 4.2.6)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activerecord (4.2.6)
|
14
|
+
activemodel (= 4.2.6)
|
15
|
+
activesupport (= 4.2.6)
|
16
|
+
arel (~> 6.0)
|
17
|
+
activesupport (4.2.6)
|
18
|
+
i18n (~> 0.7)
|
19
|
+
json (~> 1.7, >= 1.7.7)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
22
|
+
tzinfo (~> 1.1)
|
23
|
+
appraisal (2.1.0)
|
24
|
+
bundler
|
25
|
+
rake
|
26
|
+
thor (>= 0.14.0)
|
27
|
+
arel (6.0.3)
|
28
|
+
builder (3.2.2)
|
29
|
+
byebug (9.0.5)
|
30
|
+
coderay (1.1.1)
|
31
|
+
database_cleaner (1.5.3)
|
32
|
+
diff-lcs (1.2.5)
|
33
|
+
ffi (1.9.14)
|
34
|
+
formatador (0.2.5)
|
35
|
+
guard (2.14.0)
|
36
|
+
formatador (>= 0.2.4)
|
37
|
+
listen (>= 2.7, < 4.0)
|
38
|
+
lumberjack (~> 1.0)
|
39
|
+
nenv (~> 0.1)
|
40
|
+
notiffany (~> 0.0)
|
41
|
+
pry (>= 0.9.12)
|
42
|
+
shellany (~> 0.0)
|
43
|
+
thor (>= 0.18.1)
|
44
|
+
guard-compat (1.2.1)
|
45
|
+
guard-rspec (4.7.3)
|
46
|
+
guard (~> 2.1)
|
47
|
+
guard-compat (~> 1.1)
|
48
|
+
rspec (>= 2.99.0, < 4.0)
|
49
|
+
i18n (0.7.0)
|
50
|
+
json (1.8.3)
|
51
|
+
listen (3.1.5)
|
52
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
53
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
54
|
+
ruby_dep (~> 1.2)
|
55
|
+
lumberjack (1.0.10)
|
56
|
+
method_source (0.8.2)
|
57
|
+
minitest (5.9.0)
|
58
|
+
nenv (0.3.0)
|
59
|
+
notiffany (0.1.1)
|
60
|
+
nenv (~> 0.1)
|
61
|
+
shellany (~> 0.0)
|
62
|
+
pry (0.10.4)
|
63
|
+
coderay (~> 1.1.0)
|
64
|
+
method_source (~> 0.8.1)
|
65
|
+
slop (~> 3.4)
|
66
|
+
pry-byebug (3.4.0)
|
67
|
+
byebug (~> 9.0)
|
68
|
+
pry (~> 0.10)
|
69
|
+
rake (10.5.0)
|
70
|
+
rb-fsevent (0.9.7)
|
71
|
+
rb-inotify (0.9.7)
|
72
|
+
ffi (>= 0.5.0)
|
73
|
+
rspec (3.5.0)
|
74
|
+
rspec-core (~> 3.5.0)
|
75
|
+
rspec-expectations (~> 3.5.0)
|
76
|
+
rspec-mocks (~> 3.5.0)
|
77
|
+
rspec-core (3.5.3)
|
78
|
+
rspec-support (~> 3.5.0)
|
79
|
+
rspec-expectations (3.5.0)
|
80
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
81
|
+
rspec-support (~> 3.5.0)
|
82
|
+
rspec-mocks (3.5.0)
|
83
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
84
|
+
rspec-support (~> 3.5.0)
|
85
|
+
rspec-support (3.5.0)
|
86
|
+
ruby_dep (1.3.1)
|
87
|
+
shellany (0.0.1)
|
88
|
+
slop (3.6.0)
|
89
|
+
sqlite3 (1.3.11)
|
90
|
+
thor (0.19.1)
|
91
|
+
thread_safe (0.3.5)
|
92
|
+
tzinfo (1.2.2)
|
93
|
+
thread_safe (~> 0.1)
|
94
|
+
|
95
|
+
PLATFORMS
|
96
|
+
ruby
|
97
|
+
|
98
|
+
DEPENDENCIES
|
99
|
+
activerecord (~> 4.1)
|
100
|
+
appraisal
|
101
|
+
bundler (~> 1.11)
|
102
|
+
database_cleaner
|
103
|
+
guard-rspec (~> 4.7)
|
104
|
+
nested_attribute_reassignable!
|
105
|
+
pry
|
106
|
+
pry-byebug
|
107
|
+
rake (~> 10.0)
|
108
|
+
rspec (~> 3.0)
|
109
|
+
sqlite3
|
110
|
+
|
111
|
+
BUNDLED WITH
|
112
|
+
1.12.5
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "http://artprod.dev.bloomberg.com/artifactory/api/gems/rubygems/"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 5.0"
|
6
|
+
|
7
|
+
group :test do
|
8
|
+
gem "appraisal"
|
9
|
+
gem "pry"
|
10
|
+
gem "pry-byebug"
|
11
|
+
gem "guard-rspec", "~> 4.7"
|
12
|
+
end
|
13
|
+
|
14
|
+
gemspec :path => "../"
|
@@ -0,0 +1,109 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
nested_attribute_reassignable (0.6.1)
|
5
|
+
activerecord (>= 4.1, < 6)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://artprod.dev.bloomberg.com/artifactory/api/gems/rubygems/
|
9
|
+
specs:
|
10
|
+
activemodel (5.0.0.1)
|
11
|
+
activesupport (= 5.0.0.1)
|
12
|
+
activerecord (5.0.0.1)
|
13
|
+
activemodel (= 5.0.0.1)
|
14
|
+
activesupport (= 5.0.0.1)
|
15
|
+
arel (~> 7.0)
|
16
|
+
activesupport (5.0.0.1)
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
|
+
i18n (~> 0.7)
|
19
|
+
minitest (~> 5.1)
|
20
|
+
tzinfo (~> 1.1)
|
21
|
+
appraisal (2.1.0)
|
22
|
+
bundler
|
23
|
+
rake
|
24
|
+
thor (>= 0.14.0)
|
25
|
+
arel (7.1.1)
|
26
|
+
byebug (9.0.5)
|
27
|
+
coderay (1.1.1)
|
28
|
+
concurrent-ruby (1.0.2)
|
29
|
+
database_cleaner (1.5.3)
|
30
|
+
diff-lcs (1.2.5)
|
31
|
+
ffi (1.9.14)
|
32
|
+
formatador (0.2.5)
|
33
|
+
guard (2.14.0)
|
34
|
+
formatador (>= 0.2.4)
|
35
|
+
listen (>= 2.7, < 4.0)
|
36
|
+
lumberjack (~> 1.0)
|
37
|
+
nenv (~> 0.1)
|
38
|
+
notiffany (~> 0.0)
|
39
|
+
pry (>= 0.9.12)
|
40
|
+
shellany (~> 0.0)
|
41
|
+
thor (>= 0.18.1)
|
42
|
+
guard-compat (1.2.1)
|
43
|
+
guard-rspec (4.7.3)
|
44
|
+
guard (~> 2.1)
|
45
|
+
guard-compat (~> 1.1)
|
46
|
+
rspec (>= 2.99.0, < 4.0)
|
47
|
+
i18n (0.7.0)
|
48
|
+
listen (3.1.5)
|
49
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
50
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
51
|
+
ruby_dep (~> 1.2)
|
52
|
+
lumberjack (1.0.10)
|
53
|
+
method_source (0.8.2)
|
54
|
+
minitest (5.9.0)
|
55
|
+
nenv (0.3.0)
|
56
|
+
notiffany (0.1.1)
|
57
|
+
nenv (~> 0.1)
|
58
|
+
shellany (~> 0.0)
|
59
|
+
pry (0.10.4)
|
60
|
+
coderay (~> 1.1.0)
|
61
|
+
method_source (~> 0.8.1)
|
62
|
+
slop (~> 3.4)
|
63
|
+
pry-byebug (3.4.0)
|
64
|
+
byebug (~> 9.0)
|
65
|
+
pry (~> 0.10)
|
66
|
+
rake (10.5.0)
|
67
|
+
rb-fsevent (0.9.7)
|
68
|
+
rb-inotify (0.9.7)
|
69
|
+
ffi (>= 0.5.0)
|
70
|
+
rspec (3.5.0)
|
71
|
+
rspec-core (~> 3.5.0)
|
72
|
+
rspec-expectations (~> 3.5.0)
|
73
|
+
rspec-mocks (~> 3.5.0)
|
74
|
+
rspec-core (3.5.3)
|
75
|
+
rspec-support (~> 3.5.0)
|
76
|
+
rspec-expectations (3.5.0)
|
77
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
78
|
+
rspec-support (~> 3.5.0)
|
79
|
+
rspec-mocks (3.5.0)
|
80
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
81
|
+
rspec-support (~> 3.5.0)
|
82
|
+
rspec-support (3.5.0)
|
83
|
+
ruby_dep (1.3.1)
|
84
|
+
shellany (0.0.1)
|
85
|
+
slop (3.6.0)
|
86
|
+
sqlite3 (1.3.11)
|
87
|
+
thor (0.19.1)
|
88
|
+
thread_safe (0.3.5)
|
89
|
+
tzinfo (1.2.2)
|
90
|
+
thread_safe (~> 0.1)
|
91
|
+
|
92
|
+
PLATFORMS
|
93
|
+
ruby
|
94
|
+
|
95
|
+
DEPENDENCIES
|
96
|
+
activerecord (~> 5.0)
|
97
|
+
appraisal
|
98
|
+
bundler (~> 1.11)
|
99
|
+
database_cleaner
|
100
|
+
guard-rspec (~> 4.7)
|
101
|
+
nested_attribute_reassignable!
|
102
|
+
pry
|
103
|
+
pry-byebug
|
104
|
+
rake (~> 10.0)
|
105
|
+
rspec (~> 3.0)
|
106
|
+
sqlite3
|
107
|
+
|
108
|
+
BUNDLED WITH
|
109
|
+
1.12.5
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require "nested_attribute_reassignable/version"
|
2
|
+
require "active_support/concern"
|
3
|
+
|
4
|
+
module NestedAttributeReassignable
|
5
|
+
class RelationExists < StandardError
|
6
|
+
def initialize(model, relation)
|
7
|
+
@model = model.class.name.pluralize.underscore
|
8
|
+
@relation = relation
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
<<-STR
|
13
|
+
Relation '#{@relation}' already exists on '#{@model}' object but attributes were passed with no id.
|
14
|
+
|
15
|
+
It is invalid to create a new '#{@relation}' relation when one already exists, as it would leave orphaned records. Update the existing record instead.
|
16
|
+
STR
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Helper
|
21
|
+
def self.has_delete_flag?(hash)
|
22
|
+
truthy?(hash, :_delete)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.has_destroy_flag?(hash)
|
26
|
+
truthy?(hash, :_destroy)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.truthy?(hash, key)
|
30
|
+
if defined?(Rails) && Rails::VERSION::MAJOR == 5
|
31
|
+
ActiveRecord::Type::Boolean.new.cast(hash[key])
|
32
|
+
else
|
33
|
+
value = hash[key]
|
34
|
+
[true, 1, '1', 'true'].include?(value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.symbolize_keys!(attributes)
|
39
|
+
if attributes.is_a?(Array)
|
40
|
+
return unless attributes[0].respond_to?(:symbolize_keys!)
|
41
|
+
attributes.each { |a| a.symbolize_keys! }
|
42
|
+
else
|
43
|
+
return unless attributes.respond_to?(:symbolize_keys!)
|
44
|
+
attributes.symbolize_keys!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
extend ActiveSupport::Concern
|
50
|
+
|
51
|
+
# Yes, this could use refactoring love, I do not
|
52
|
+
# have time right now D:
|
53
|
+
# Just go by the tests.
|
54
|
+
module ClassMethods
|
55
|
+
def reassignable_nested_attributes_for(association_name, *args)
|
56
|
+
options = args.extract_options!.symbolize_keys
|
57
|
+
options.update({ :allow_destroy => true })
|
58
|
+
|
59
|
+
accepts_nested_attributes_for(association_name, options.except(:lookup_key))
|
60
|
+
|
61
|
+
define_method "#{association_name}_attributes=" do |attributes|
|
62
|
+
reflection_klass = self.class._reflect_on_association(association_name)
|
63
|
+
association_klass = reflection_klass.klass
|
64
|
+
association = association(association_name)
|
65
|
+
lookup_key = options[:lookup_key] || association.klass.primary_key
|
66
|
+
|
67
|
+
Helper.symbolize_keys!(attributes)
|
68
|
+
|
69
|
+
if attributes.is_a?(Array)
|
70
|
+
id_attribute_sets = attributes.select { |a| a.has_key?(:id) }
|
71
|
+
|
72
|
+
ids = attributes.map { |a| a[:id] }
|
73
|
+
children = association_klass.where(lookup_key => ids)
|
74
|
+
|
75
|
+
# If we're deleting or destroying, we want to validate the record in question
|
76
|
+
# is actually part of this relationship
|
77
|
+
if id_attribute_sets.any? { |set| Helper.has_destroy_flag?(set) || Helper.has_delete_flag?(set) }
|
78
|
+
existing_associated = association.scope.where(lookup_key => ids)
|
79
|
+
end
|
80
|
+
|
81
|
+
id_attribute_sets.each do |id_attributes|
|
82
|
+
if existing_record = children.find { |c| c.send(lookup_key).to_s == id_attributes[:id].to_s }
|
83
|
+
if Helper.has_destroy_flag?(id_attributes)
|
84
|
+
if record = existing_associated.find { |e| e.send(lookup_key).to_s == id_attributes[:id].to_s }
|
85
|
+
record.mark_for_destruction
|
86
|
+
association.add_to_target(record, :skip_callbacks)
|
87
|
+
else
|
88
|
+
raise_nested_attributes_record_not_found!(association_name, id_attributes[:id])
|
89
|
+
end
|
90
|
+
elsif Helper.has_delete_flag?(id_attributes)
|
91
|
+
if record = existing_associated.find { |e| e.send(lookup_key).to_s == id_attributes[:id].to_s }
|
92
|
+
association.add_to_target(record, :skip_callbacks)
|
93
|
+
send(association_name).delete(record)
|
94
|
+
else
|
95
|
+
raise_nested_attributes_record_not_found!(association_name, id_attributes[:id])
|
96
|
+
end
|
97
|
+
else
|
98
|
+
id_attributes[lookup_key] = id_attributes[:id]
|
99
|
+
existing_record.assign_attributes(id_attributes.except(:id))
|
100
|
+
self.send(association_name).concat(existing_record)
|
101
|
+
end
|
102
|
+
else
|
103
|
+
raise_nested_attributes_record_not_found!(association_name, id_attributes[:id])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
non_id_attribute_sets = attributes.reject { |a| a.has_key?(:id) }
|
107
|
+
non_id_attribute_sets.each do |non_id_attributes|
|
108
|
+
self.send(association_name).build(non_id_attributes)
|
109
|
+
end
|
110
|
+
self.association(association_name).loaded!
|
111
|
+
else
|
112
|
+
if attributes[:id]
|
113
|
+
if Helper.has_destroy_flag?(attributes)
|
114
|
+
self.send(association_name).mark_for_destruction
|
115
|
+
elsif Helper.has_delete_flag?(attributes)
|
116
|
+
send("#{association_name}=", nil)
|
117
|
+
elsif existing_record = association_klass.find_by(lookup_key => attributes[:id])
|
118
|
+
attributes[lookup_key] = attributes.delete(:id)
|
119
|
+
existing_record.assign_attributes(attributes)
|
120
|
+
self.send("#{association_name}=", existing_record)
|
121
|
+
else
|
122
|
+
raise_nested_attributes_record_not_found!(association_name, attributes[:id])
|
123
|
+
end
|
124
|
+
else
|
125
|
+
reflection = self.class._reflect_on_association(association_name)
|
126
|
+
if reflection.has_one? and send(association_name).present?
|
127
|
+
raise RelationExists.new(self, association_name)
|
128
|
+
else
|
129
|
+
super(attributes)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nested_attribute_reassignable/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "nested_attribute_reassignable"
|
8
|
+
spec.version = NestedAttributeReassignable::VERSION
|
9
|
+
spec.authors = ["Lee Richmond"]
|
10
|
+
spec.email = ["lrichmond1@bloomberg.net"]
|
11
|
+
|
12
|
+
spec.summary = %q{Allows accepts_nested_attributes_for to accept preexisting records}
|
13
|
+
spec.description = %q{Have an unpersisted base object, and nested_attributes_for already-persisted assocations}
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency 'activerecord', [">= 4.1", "< 6"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
spec.add_development_dependency "database_cleaner"
|
26
|
+
spec.add_development_dependency "sqlite3"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nested_attribute_reassignable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lee Richmond
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.1'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bundler
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.11'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.11'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '10.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '10.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: database_cleaner
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: sqlite3
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
description: Have an unpersisted base object, and nested_attributes_for already-persisted
|
104
|
+
assocations
|
105
|
+
email:
|
106
|
+
- lrichmond1@bloomberg.net
|
107
|
+
executables: []
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files: []
|
110
|
+
files:
|
111
|
+
- ".gitignore"
|
112
|
+
- ".rspec"
|
113
|
+
- ".travis.yml"
|
114
|
+
- Appraisals
|
115
|
+
- Gemfile
|
116
|
+
- Guardfile
|
117
|
+
- README.md
|
118
|
+
- Rakefile
|
119
|
+
- bin/appraisal
|
120
|
+
- bin/console
|
121
|
+
- bin/rspec
|
122
|
+
- bin/setup
|
123
|
+
- gemfiles/activerecord_4.gemfile
|
124
|
+
- gemfiles/activerecord_4.gemfile.lock
|
125
|
+
- gemfiles/activerecord_5.gemfile
|
126
|
+
- gemfiles/activerecord_5.gemfile.lock
|
127
|
+
- lib/nested_attribute_reassignable.rb
|
128
|
+
- lib/nested_attribute_reassignable/version.rb
|
129
|
+
- nested_attribute_reassignable.gemspec
|
130
|
+
homepage:
|
131
|
+
licenses: []
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.4.5.1
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: Allows accepts_nested_attributes_for to accept preexisting records
|
153
|
+
test_files: []
|
154
|
+
has_rdoc:
|