uuid_associations-active_record 0.1.0 → 0.2.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/.travis.yml +4 -4
- data/README.md +216 -6
- data/lib/uuid_associations/active_record/{method_definitions.rb → association_method_definitions.rb} +1 -1
- data/lib/uuid_associations/active_record/inline.rb +4 -2
- data/lib/uuid_associations/active_record/nested_attributes/uuid_finder.rb +49 -0
- data/lib/uuid_associations/active_record/nested_attributes_method_definitions.rb +27 -0
- data/lib/uuid_associations/active_record/railtie.rb +4 -2
- data/lib/uuid_associations/active_record/version.rb +1 -1
- data/spec/integration/nested_attributes/collection_spec.rb +65 -0
- data/spec/{uuid_associations → integration/relationship_definitions}/belongs_to_spec.rb +0 -0
- data/spec/{uuid_associations → integration/relationship_definitions}/has_and_belongs_to_many_spec.rb +0 -0
- data/spec/{uuid_associations → integration/relationship_definitions}/has_many_spec.rb +0 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/models/pet.rb +4 -0
- data/spec/support/models/post.rb +2 -0
- data/spec/support/models/toy.rb +5 -0
- data/spec/support/models/user.rb +1 -0
- data/spec/support/schema.rb +9 -2
- data/spec/unit/uuid_associations/active_record/nested_attributes/uuid_finder_spec.rb +71 -0
- data/uuid_associations-active_record.gemspec +2 -0
- metadata +18 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e1538fc45bf664d391c0a3b2b335247bdf0a66cc1f9133d3936000850175e9c
|
|
4
|
+
data.tar.gz: '092c5dad84a0a0ae487ecc61516b43b7d0b0e19fe7e3a2805f1811602dbbd5ee'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 40acb305ecca02b540d97161942ef0bcf9161b1b5b07c4160df0adcb76a039e576da7a3afe53882bcb7501684b394170137f0c5df54df3eecf04fddf61ab7726
|
|
7
|
+
data.tar.gz: f064f70789721a9b789a76f096098548fa409ac69f63b818d8a232b3112a4ee58d524a72e2b7611c3a862596a7b22a17eb556655154cee828319aa7adbf59490
|
data/.travis.yml
CHANGED
|
@@ -5,9 +5,9 @@ cache: bundler
|
|
|
5
5
|
|
|
6
6
|
rvm:
|
|
7
7
|
- 2.2.10
|
|
8
|
-
- 2.3.
|
|
9
|
-
- 2.4.
|
|
10
|
-
- 2.5.
|
|
8
|
+
- 2.3.8
|
|
9
|
+
- 2.4.5
|
|
10
|
+
- 2.5.3
|
|
11
11
|
|
|
12
12
|
gemfile:
|
|
13
13
|
- gemfiles/active_record_4.2.gemfile
|
|
@@ -24,4 +24,4 @@ matrix:
|
|
|
24
24
|
- gemfile: gemfiles/active_record_edge.gemfile
|
|
25
25
|
rvm: 2.2.10
|
|
26
26
|
- gemfile: gemfiles/active_record_edge.gemfile
|
|
27
|
-
rvm: 2.3.
|
|
27
|
+
rvm: 2.3.8
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# UUID Associations ActiveRecord
|
|
2
2
|
[](https://travis-ci.org/mcelicalderon/uuid_associations-active_record)
|
|
3
3
|
[](https://badge.fury.io/rb/uuid_associations-active_record)
|
|
4
4
|
|
|
@@ -12,25 +12,235 @@ gem 'uuid_associations-active_record'
|
|
|
12
12
|
|
|
13
13
|
And then execute:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```bash
|
|
16
|
+
$ bundle
|
|
17
|
+
```
|
|
16
18
|
|
|
17
19
|
Or install it yourself as:
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
```bash
|
|
22
|
+
$ gem install uuid_associations-active_record
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Rationale
|
|
26
|
+
|
|
27
|
+
This gem is the result of some research I did to find out if using UUIDs as DB primary keys is a good thing
|
|
28
|
+
(Rails added support for this!). On the process
|
|
29
|
+
I ran into [this article](https://tomharrisonjr.com/uuid-or-guid-as-primary-keys-be-careful-7b2aa3dcb439) (among many others).
|
|
30
|
+
If you read the article you'll see how UUIDs have benefits over using sequential Int values as primary keys, but also how
|
|
31
|
+
you should be aware of what does that imply in terms of performance or even amount of storage used.
|
|
32
|
+
|
|
33
|
+
One of the author's conclusions is that you should use Int or Bigint columns as your primary keys, but also add a column
|
|
34
|
+
with a UUID that you can use to reference your records when exposing them to the outside world. If you finish reading the article
|
|
35
|
+
you'll find out that not even this is the perfect solution, but at least it's a huge improvement to exposing your sequential
|
|
36
|
+
primary keys. This gem helps you implement this approach.
|
|
20
37
|
|
|
21
38
|
## Usage
|
|
22
39
|
|
|
23
|
-
|
|
40
|
+
Adding the gem to your Gemfile is all you need for the gem to start working if you are using Rails. If you are not, you
|
|
41
|
+
you can always install the gem and require it manually (probably right after you require active_record). Take a look at
|
|
42
|
+
specs if you want to use the gem with plain ActiveRecord as that is how they are setup.
|
|
43
|
+
|
|
44
|
+
What this gem does is add some useful methods to your ActiveRecord models by calling the good old association methods like
|
|
45
|
+
`has_many`, `belongs_to`, `has_and_belongs_to_many`. As you may already know, calling these methods creates multiple
|
|
46
|
+
methods on the calling model for you. I'll explain the ones we are interested in in the next section.
|
|
47
|
+
|
|
48
|
+
This gem also adds a helpful mechanism to handle nested attributes. This will also be explained in the next section.
|
|
49
|
+
|
|
50
|
+
That's it! That is what the gem does. This will allow you to pass UUIDs to your update or create operations instead
|
|
51
|
+
of the actual IDs. Be aware that this will of course require an additional (but simple) DB query. That is the cost of
|
|
52
|
+
hiding the actual IDs (I'd say it's not too costly).
|
|
53
|
+
|
|
54
|
+
Also be aware that the initial version of this gem is not very configurable,
|
|
55
|
+
so the only thing it checks before creating the methods is that the right side association
|
|
56
|
+
on the caller model has a column named `uuid`, doesn't take the column type into account.
|
|
57
|
+
I have tested with string columns in SQlite3 and UUID columns in Postgres.
|
|
58
|
+
|
|
59
|
+
### Association Methods
|
|
60
|
+
|
|
61
|
+
Lets explore the next example:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# app/models/user.rb
|
|
65
|
+
class User < ActiveRecord::Base
|
|
66
|
+
has_and_belongs_to_many :posts
|
|
67
|
+
# generates
|
|
68
|
+
#
|
|
69
|
+
# def post_ids=(ids)
|
|
70
|
+
# # Adds or deletes the association with posts based on the array of IDs received
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# def post_ids
|
|
74
|
+
# # returns an array with all the post IDs
|
|
75
|
+
# end
|
|
76
|
+
|
|
77
|
+
has_many :comments
|
|
78
|
+
# generates
|
|
79
|
+
#
|
|
80
|
+
# def comment_ids=(ids)
|
|
81
|
+
# # Adds or deletes the association with comments based on the array of IDs received
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# def comment_ids
|
|
85
|
+
# # returns an array with all the comment IDs
|
|
86
|
+
# end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# app/models/post.rb
|
|
90
|
+
class Post < ActiveRecord::Base
|
|
91
|
+
has_and_belongs_to_many :users
|
|
92
|
+
# generates
|
|
93
|
+
#
|
|
94
|
+
# def user_ids=(ids)
|
|
95
|
+
# # Adds or deletes the association with users based on the array of IDs received
|
|
96
|
+
# end
|
|
97
|
+
#
|
|
98
|
+
# def user_ids
|
|
99
|
+
# # returns an array with all the user IDs
|
|
100
|
+
# end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# app/models/comment.rb
|
|
104
|
+
class Comment < ActiveRecord::Base
|
|
105
|
+
belongs_to :user
|
|
106
|
+
# generates
|
|
107
|
+
#
|
|
108
|
+
# def user_id=(id)
|
|
109
|
+
# associates the comment with the user
|
|
110
|
+
# end
|
|
111
|
+
|
|
112
|
+
# def user_id
|
|
113
|
+
# returns the ID of the associated user
|
|
114
|
+
# end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
This gem will add two new methods for each call to one of the association methods.
|
|
119
|
+
|
|
120
|
+
#### Generated Methods
|
|
121
|
+
|
|
122
|
+
Calling any of these methods will also add the following methods to your model:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# app/models/user.rb
|
|
126
|
+
class User < ActiveRecord::Base
|
|
127
|
+
has_and_belongs_to_many :posts
|
|
128
|
+
# generates
|
|
129
|
+
#
|
|
130
|
+
# def post_uuids=(uuids)
|
|
131
|
+
# self.post_ids = Post.where(uuid: uuids).pluck(:id)
|
|
132
|
+
# end
|
|
133
|
+
#
|
|
134
|
+
# def post_uuids
|
|
135
|
+
# posts.pluck(:uuid)
|
|
136
|
+
# end
|
|
137
|
+
|
|
138
|
+
has_many :comments
|
|
139
|
+
# generates
|
|
140
|
+
#
|
|
141
|
+
# def comment_uuids=(uuids)
|
|
142
|
+
# self.comment_ids = Comment.where(uuid: uuids).pluck(:id)
|
|
143
|
+
# end
|
|
144
|
+
#
|
|
145
|
+
# def comment_uuids
|
|
146
|
+
# comments.pluck(:uuid)
|
|
147
|
+
# end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# app/models/post.rb
|
|
151
|
+
class Post < ActiveRecord::Base
|
|
152
|
+
has_and_belongs_to_many :users
|
|
153
|
+
# generates
|
|
154
|
+
#
|
|
155
|
+
# def user_uuids=(uuids)
|
|
156
|
+
# self.user_ids = User.where(uuid: uuids).pluck(:id)
|
|
157
|
+
# end
|
|
158
|
+
#
|
|
159
|
+
# def user_uuids
|
|
160
|
+
# users.pluck(:uuid)
|
|
161
|
+
# end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# app/models/comment.rb
|
|
165
|
+
class Comment < ActiveRecord::Base
|
|
166
|
+
belongs_to :user
|
|
167
|
+
# generates
|
|
168
|
+
#
|
|
169
|
+
# def user_uuid=(uuid)
|
|
170
|
+
# self.user_id = User.find_by!(uuid: uuid)
|
|
171
|
+
# end
|
|
172
|
+
|
|
173
|
+
# def user_id
|
|
174
|
+
# user.uuid
|
|
175
|
+
# end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Nested Attributes
|
|
180
|
+
|
|
181
|
+
Nested attributes don't generate additional methods, this gem just modifies one so you can update nested record using
|
|
182
|
+
the record's UUID instead of the actual ID. Let's explore the next example:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
class Post < ActiveRecord::Base
|
|
186
|
+
has_many :comments
|
|
187
|
+
|
|
188
|
+
accepts_nested_attributes_for :comments, allow_destroy: true
|
|
189
|
+
# generates
|
|
190
|
+
#
|
|
191
|
+
# def comments_attributes=(attributes)
|
|
192
|
+
# # allows to create or update comments using this method on Post
|
|
193
|
+
# end
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
ActiveRecord allows for `attributes` to be an array of hashes or a hash of hashes. Here are some examples of the payload
|
|
198
|
+
you can pass to `comments_attributes` by using this gem:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
# This is supported without this gem
|
|
202
|
+
[
|
|
203
|
+
{ id: 1, comment_body: 'updating comment with ID 1' },
|
|
204
|
+
{ id: 2, _destroy: true },
|
|
205
|
+
{ comment_body: 'this will create a new comment' }
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
# With the gem, you can use UUIDs instead
|
|
209
|
+
[
|
|
210
|
+
{ uuid: 'some-uuid', comment_body: 'updating comment with UUID: some-uuid' }.
|
|
211
|
+
{ uuid: 'other-uuid', _destroy: true },
|
|
212
|
+
{ comment_body: 'this will create a new comment' }
|
|
213
|
+
]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Here are some things to take into account:
|
|
217
|
+
|
|
218
|
+
1. If the nested model (Comment in the example) does not have a column named `uuid`, the gem will take no action, will
|
|
219
|
+
just preserve the original behavior.
|
|
220
|
+
1. If the hash has both the `:id` and `:uuid` keys, the record will be fetched by `id`, and `uuid` will be passed as an attribute.
|
|
221
|
+
1. When the hash has a `:uuid` key and no record is found for that key, a new record will be created using that UUID
|
|
222
|
+
(this will change to raise a not found error instead).
|
|
223
|
+
|
|
224
|
+
## Future Work
|
|
225
|
+
|
|
226
|
+
1. Not commonly used by me, but testing and adding these methods to a `has_one` relationship.
|
|
227
|
+
1. Raise not found error if the array of UUIDs is bigger that the array
|
|
228
|
+
of IDs fetched with the `where` statement (ActiveRecord's behavior).
|
|
24
229
|
|
|
25
230
|
## Development
|
|
26
231
|
|
|
27
232
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
28
233
|
|
|
29
|
-
To
|
|
234
|
+
To run the specs with all the supported versions of ActiveRecord you can use:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
$ bundle exec appraisal install
|
|
238
|
+
$ bundle exec appraisal rspec
|
|
239
|
+
```
|
|
30
240
|
|
|
31
241
|
## Contributing
|
|
32
242
|
|
|
33
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
243
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mcelicalderon/uuid_associations-active_record.
|
|
34
244
|
|
|
35
245
|
## License
|
|
36
246
|
|
data/lib/uuid_associations/active_record/{method_definitions.rb → association_method_definitions.rb}
RENAMED
|
@@ -3,7 +3,7 @@ require 'uuid_associations/active_record/relationship_definitions/has_many'
|
|
|
3
3
|
|
|
4
4
|
module UuidAssociations
|
|
5
5
|
module ActiveRecord
|
|
6
|
-
module
|
|
6
|
+
module AssociationMethodDefinitions
|
|
7
7
|
def has_many(name, scope = nil, **options, &extension)
|
|
8
8
|
original_payload = super(name, scope, options, &extension)
|
|
9
9
|
RelationshipDefinitions::HasMany.define_accesors_for(self, original_payload, name)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require 'active_record'
|
|
2
2
|
require 'active_record/associations'
|
|
3
|
-
require 'uuid_associations/active_record/
|
|
3
|
+
require 'uuid_associations/active_record/association_method_definitions'
|
|
4
|
+
require 'uuid_associations/active_record/nested_attributes_method_definitions'
|
|
4
5
|
|
|
5
|
-
::ActiveRecord::
|
|
6
|
+
::ActiveRecord::Base.extend(UuidAssociations::ActiveRecord::AssociationMethodDefinitions)
|
|
7
|
+
::ActiveRecord::Base.prepend(UuidAssociations::ActiveRecord::NestedAttributesMethodDefinitions)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module UuidAssociations
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module NestedAttributes
|
|
4
|
+
class UuidFinder
|
|
5
|
+
def self.replaced_uuids_with_ids(association_klass, attribute_collection)
|
|
6
|
+
new(association_klass, attribute_collection).call
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(association_klass, attribute_collection)
|
|
10
|
+
@association_klass = association_klass
|
|
11
|
+
@attribute_collection = attribute_collection
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
to_replace, to_keep = attribute_collection.partition do |attributes|
|
|
16
|
+
symbol_keys = attributes.keys.map(&:to_sym)
|
|
17
|
+
|
|
18
|
+
(symbol_keys & [:uuid, :id]) == [:uuid]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
uuids_to_find = to_replace.map { |element| element[:uuid] }
|
|
22
|
+
found_records = association_klass.where(uuid: uuids_to_find)
|
|
23
|
+
|
|
24
|
+
replaced = to_replace.each_with_object([]) do |attributes, collection|
|
|
25
|
+
uuid = attributes.delete(:uuid)
|
|
26
|
+
|
|
27
|
+
record = found_records.find { |found_record| found_record.uuid == uuid }
|
|
28
|
+
|
|
29
|
+
collection << if record.blank?
|
|
30
|
+
attributes
|
|
31
|
+
else
|
|
32
|
+
attributes.merge(id: record.id)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
to_keep + replaced
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def attribute_collection
|
|
42
|
+
@attribute_collection.instance_of?(Hash) ? @attribute_collection.values : @attribute_collection
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attr_reader :association_klass
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'uuid_associations/active_record/nested_attributes/uuid_finder'
|
|
2
|
+
|
|
3
|
+
module UuidAssociations
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module NestedAttributesMethodDefinitions
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
|
9
|
+
association_klass = association(association_name).reflection.klass
|
|
10
|
+
unless nested_association_uuid_searchable?(association_klass, attributes_collection)
|
|
11
|
+
return super(association_name, attributes_collection)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
replaced_attributes = ActiveRecord::NestedAttributes::UuidFinder.replaced_uuids_with_ids(
|
|
15
|
+
association_klass,
|
|
16
|
+
attributes_collection
|
|
17
|
+
)
|
|
18
|
+
super(association_name, replaced_attributes)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def nested_association_uuid_searchable?(association_klass, attributes_collection)
|
|
22
|
+
association_klass.column_names.include?('uuid') &&
|
|
23
|
+
(attributes_collection.instance_of?(Hash) || attributes_collection.instance_of?(Array))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
require 'uuid_associations/active_record/
|
|
1
|
+
require 'uuid_associations/active_record/association_method_definitions'
|
|
2
|
+
require 'uuid_associations/active_record/nested_attributes_method_definitions'
|
|
2
3
|
|
|
3
4
|
module UuidAssociations
|
|
4
5
|
module ActiveRecord
|
|
@@ -7,7 +8,8 @@ module UuidAssociations
|
|
|
7
8
|
ActiveSupport.on_load(:active_record) do
|
|
8
9
|
::ActiveRecord::Associations.eager_load!
|
|
9
10
|
|
|
10
|
-
extend
|
|
11
|
+
extend(UuidAssociations::ActiveRecord::AssociationMethodDefinitions)
|
|
12
|
+
prepend(UuidAssociations::ActiveRecord::NestedAttributesMethodDefinitions)
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
RSpec.describe 'nested attributes for collection' do
|
|
2
|
+
context 'when nested association has no UUID column' do
|
|
3
|
+
let(:pet) { Pet.create!(name: 'Simba') }
|
|
4
|
+
|
|
5
|
+
it 'does not call UUID finder' do
|
|
6
|
+
expect(UuidAssociations::ActiveRecord::NestedAttributes::UuidFinder).not_to receive(:replaced_uuids_with_ids)
|
|
7
|
+
|
|
8
|
+
pet.update(toys_attributes: [{ name: 'no-uuid-here ;)' }])
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context 'when creating a new record' do
|
|
13
|
+
it 'creates nested resources when not existing UUID is passed' do
|
|
14
|
+
expect do
|
|
15
|
+
Post.create!(
|
|
16
|
+
uuid: SecureRandom.uuid,
|
|
17
|
+
content: 'post',
|
|
18
|
+
comments_attributes: [
|
|
19
|
+
body: 'New comment',
|
|
20
|
+
uuid: SecureRandom.uuid
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
end.to change(Post, :count).from(0).to(1)
|
|
24
|
+
.and change(Comment, :count).from(0).to(1)
|
|
25
|
+
|
|
26
|
+
expect(Post.first.comments.count).to eq(1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'creates nested resources when no UUID or ID is passed' do
|
|
30
|
+
expect do
|
|
31
|
+
Post.create!(
|
|
32
|
+
uuid: SecureRandom.uuid,
|
|
33
|
+
content: 'post',
|
|
34
|
+
comments_attributes: [
|
|
35
|
+
body: 'New comment'
|
|
36
|
+
]
|
|
37
|
+
)
|
|
38
|
+
end.to change(Post, :count).from(0).to(1)
|
|
39
|
+
.and change(Comment, :count).from(0).to(1)
|
|
40
|
+
|
|
41
|
+
expect(Post.first.comments.count).to eq(1)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'when updating existing records' do
|
|
46
|
+
let(:comment_uuid) { SecureRandom.uuid }
|
|
47
|
+
let(:post) { Post.create!(uuid: SecureRandom.uuid, content: 'My post') }
|
|
48
|
+
let!(:comment) { Comment.create!(post: post, body: 'My comment', uuid: comment_uuid) }
|
|
49
|
+
|
|
50
|
+
it 'updates nested records when a UUID is passed and no ID' do
|
|
51
|
+
expect do
|
|
52
|
+
post.update(comments_attributes: [{ uuid: comment.uuid, body: 'updated comment' }])
|
|
53
|
+
comment.reload
|
|
54
|
+
end.to change(comment, :body).from('My comment').to('updated comment')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'updates nested records by ID if ID is passed' do
|
|
58
|
+
expect do
|
|
59
|
+
post.update(comments_attributes: [{ id: comment.id, uuid: 'new-uuid', body: 'updated comment' }])
|
|
60
|
+
comment.reload
|
|
61
|
+
end.to change(comment, :body).from('My comment').to('updated comment')
|
|
62
|
+
.and change(comment, :uuid).from(comment_uuid).to('new-uuid')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
File without changes
|
data/spec/{uuid_associations → integration/relationship_definitions}/has_and_belongs_to_many_spec.rb
RENAMED
|
File without changes
|
|
File without changes
|
data/spec/spec_helper.rb
CHANGED
|
@@ -6,6 +6,8 @@ require 'support/models/team'
|
|
|
6
6
|
require 'support/models/user'
|
|
7
7
|
require 'support/models/post'
|
|
8
8
|
require 'support/models/comment'
|
|
9
|
+
require 'support/models/pet'
|
|
10
|
+
require 'support/models/toy'
|
|
9
11
|
|
|
10
12
|
RSpec.configure do |config|
|
|
11
13
|
# Enable flags like --only-failures and --next-failure
|
data/spec/support/models/pet.rb
CHANGED
data/spec/support/models/post.rb
CHANGED
data/spec/support/models/user.rb
CHANGED
data/spec/support/schema.rb
CHANGED
|
@@ -10,7 +10,7 @@ ActiveRecord::Schema.define version: 0 do
|
|
|
10
10
|
t.timestamps
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
create_table :teams_users,
|
|
13
|
+
create_table :teams_users, id: false do |t|
|
|
14
14
|
t.integer :user_id, null: false
|
|
15
15
|
t.integer :team_id, null: false
|
|
16
16
|
end
|
|
@@ -22,7 +22,7 @@ ActiveRecord::Schema.define version: 0 do
|
|
|
22
22
|
t.timestamps
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
create_table :pets_users,
|
|
25
|
+
create_table :pets_users, id: false do |t|
|
|
26
26
|
t.integer :user_id, null: false
|
|
27
27
|
t.integer :pet_id, null: false
|
|
28
28
|
end
|
|
@@ -34,6 +34,13 @@ ActiveRecord::Schema.define version: 0 do
|
|
|
34
34
|
t.timestamps
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
create_table :toys do |t|
|
|
38
|
+
t.string :name, null: false
|
|
39
|
+
t.belongs_to :pet, foreign_key: { on_delete: :cascade }
|
|
40
|
+
|
|
41
|
+
t.timestamps
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
create_table :posts do |t|
|
|
38
45
|
t.belongs_to :user, foreign_key: { on_delete: :cascade }
|
|
39
46
|
t.string :uuid
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'uuid_associations/active_record/nested_attributes/uuid_finder'
|
|
2
|
+
|
|
3
|
+
RSpec.describe UuidAssociations::ActiveRecord::NestedAttributes::UuidFinder do
|
|
4
|
+
describe '.replaced_uuids_with_ids' do
|
|
5
|
+
subject { described_class.replaced_uuids_with_ids(klass, attribute_collection) }
|
|
6
|
+
|
|
7
|
+
let(:post) { Post.create!(content: 'content', uuid: SecureRandom.uuid) }
|
|
8
|
+
let(:comment) { Comment.create!(post: post, body: 'my comment', uuid: SecureRandom.uuid) }
|
|
9
|
+
let(:klass) { Comment }
|
|
10
|
+
|
|
11
|
+
context 'when attributes come as an array' do
|
|
12
|
+
context 'when UUID is present, but ID is not' do
|
|
13
|
+
let(:attribute_collection) { [{ uuid: comment.uuid, body: 'updated comment' }] }
|
|
14
|
+
|
|
15
|
+
it { is_expected.to contain_exactly(id: comment.id, body: 'updated comment') }
|
|
16
|
+
|
|
17
|
+
context 'when record with specified UUID does not exist on the sytem' do
|
|
18
|
+
let(:attribute_collection) { [{ uuid: SecureRandom.uuid, body: 'new comment :/' }] }
|
|
19
|
+
|
|
20
|
+
it { is_expected.to match_array(attribute_collection) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when both UUID and ID are present' do
|
|
25
|
+
let(:attribute_collection) do
|
|
26
|
+
[
|
|
27
|
+
{ uuid: comment.uuid, id: comment.id, body: 'updated comment' },
|
|
28
|
+
{ body: 'new comment' }
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it do
|
|
33
|
+
is_expected.to contain_exactly(
|
|
34
|
+
{ uuid: comment.uuid, id: comment.id, body: 'updated comment' },
|
|
35
|
+
{ body: 'new comment' }
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'when attributes come as a hash' do
|
|
42
|
+
context 'when UUID is present, but ID is not' do
|
|
43
|
+
let(:attribute_collection) { { first: { uuid: comment.uuid, body: 'updated comment' } } }
|
|
44
|
+
|
|
45
|
+
it { is_expected.to contain_exactly(id: comment.id, body: 'updated comment') }
|
|
46
|
+
|
|
47
|
+
context 'when record with specified UUID does not exist on the sytem' do
|
|
48
|
+
let(:attribute_collection) { { first: { uuid: SecureRandom.uuid, body: 'new comment :/' } } }
|
|
49
|
+
|
|
50
|
+
it { is_expected.to match_array(attribute_collection.values) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'when both UUID and ID are present' do
|
|
55
|
+
let(:attribute_collection) do
|
|
56
|
+
{
|
|
57
|
+
first: { uuid: comment.uuid, id: comment.id, body: 'updated comment' },
|
|
58
|
+
second: { body: 'new comment' }
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it do
|
|
63
|
+
is_expected.to contain_exactly(
|
|
64
|
+
{ uuid: comment.uuid, id: comment.id, body: 'updated comment' },
|
|
65
|
+
{ body: 'new comment' }
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -19,6 +19,8 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
20
|
spec.require_paths = ['lib']
|
|
21
21
|
|
|
22
|
+
spec.required_ruby_version = '>= 2.1'
|
|
23
|
+
|
|
22
24
|
spec.add_dependency 'activerecord', '>= 4.2', '< 6.0'
|
|
23
25
|
|
|
24
26
|
spec.add_development_dependency 'appraisal', '~> 2.0'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: uuid_associations-active_record
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mario Celi
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-10-
|
|
11
|
+
date: 2018-10-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -139,23 +139,28 @@ files:
|
|
|
139
139
|
- gemfiles/active_record_5.2.gemfile
|
|
140
140
|
- gemfiles/active_record_edge.gemfile
|
|
141
141
|
- lib/uuid_associations/active_record.rb
|
|
142
|
+
- lib/uuid_associations/active_record/association_method_definitions.rb
|
|
142
143
|
- lib/uuid_associations/active_record/inline.rb
|
|
143
|
-
- lib/uuid_associations/active_record/
|
|
144
|
+
- lib/uuid_associations/active_record/nested_attributes/uuid_finder.rb
|
|
145
|
+
- lib/uuid_associations/active_record/nested_attributes_method_definitions.rb
|
|
144
146
|
- lib/uuid_associations/active_record/railtie.rb
|
|
145
147
|
- lib/uuid_associations/active_record/relationship_definitions/base.rb
|
|
146
148
|
- lib/uuid_associations/active_record/relationship_definitions/belongs_to.rb
|
|
147
149
|
- lib/uuid_associations/active_record/relationship_definitions/has_many.rb
|
|
148
150
|
- lib/uuid_associations/active_record/version.rb
|
|
151
|
+
- spec/integration/nested_attributes/collection_spec.rb
|
|
152
|
+
- spec/integration/relationship_definitions/belongs_to_spec.rb
|
|
153
|
+
- spec/integration/relationship_definitions/has_and_belongs_to_many_spec.rb
|
|
154
|
+
- spec/integration/relationship_definitions/has_many_spec.rb
|
|
149
155
|
- spec/spec_helper.rb
|
|
150
156
|
- spec/support/models/comment.rb
|
|
151
157
|
- spec/support/models/pet.rb
|
|
152
158
|
- spec/support/models/post.rb
|
|
153
159
|
- spec/support/models/team.rb
|
|
160
|
+
- spec/support/models/toy.rb
|
|
154
161
|
- spec/support/models/user.rb
|
|
155
162
|
- spec/support/schema.rb
|
|
156
|
-
- spec/uuid_associations/
|
|
157
|
-
- spec/uuid_associations/has_and_belongs_to_many_spec.rb
|
|
158
|
-
- spec/uuid_associations/has_many_spec.rb
|
|
163
|
+
- spec/unit/uuid_associations/active_record/nested_attributes/uuid_finder_spec.rb
|
|
159
164
|
- uuid_associations-active_record.gemspec
|
|
160
165
|
homepage: https://github.com/mcelicalderon/uuid_associations-active_record
|
|
161
166
|
licenses:
|
|
@@ -169,7 +174,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
169
174
|
requirements:
|
|
170
175
|
- - ">="
|
|
171
176
|
- !ruby/object:Gem::Version
|
|
172
|
-
version: '
|
|
177
|
+
version: '2.1'
|
|
173
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
179
|
requirements:
|
|
175
180
|
- - ">="
|
|
@@ -182,13 +187,16 @@ signing_key:
|
|
|
182
187
|
specification_version: 4
|
|
183
188
|
summary: Helper methods for UUID associations in Active Record
|
|
184
189
|
test_files:
|
|
190
|
+
- spec/integration/nested_attributes/collection_spec.rb
|
|
191
|
+
- spec/integration/relationship_definitions/belongs_to_spec.rb
|
|
192
|
+
- spec/integration/relationship_definitions/has_and_belongs_to_many_spec.rb
|
|
193
|
+
- spec/integration/relationship_definitions/has_many_spec.rb
|
|
185
194
|
- spec/spec_helper.rb
|
|
186
195
|
- spec/support/models/comment.rb
|
|
187
196
|
- spec/support/models/pet.rb
|
|
188
197
|
- spec/support/models/post.rb
|
|
189
198
|
- spec/support/models/team.rb
|
|
199
|
+
- spec/support/models/toy.rb
|
|
190
200
|
- spec/support/models/user.rb
|
|
191
201
|
- spec/support/schema.rb
|
|
192
|
-
- spec/uuid_associations/
|
|
193
|
-
- spec/uuid_associations/has_and_belongs_to_many_spec.rb
|
|
194
|
-
- spec/uuid_associations/has_many_spec.rb
|
|
202
|
+
- spec/unit/uuid_associations/active_record/nested_attributes/uuid_finder_spec.rb
|