active_record-json_associations 0.7.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da40cb0d7c64c3ae4fc7e90333a9ceb5687ed65676435f68edc66563e13f609c
4
- data.tar.gz: 77cd9023e9877aa3709162fbcfc1ac08c4ea5c7c581264fff450e20af58b6e2e
3
+ metadata.gz: 82a8dd8639c2897e523652a7415754704227798f98e753681d0199cc70ec055d
4
+ data.tar.gz: bf59af54b968501e6b087a65ba7d95b5783fe5ec20c4c41182b5882bbdb9604d
5
5
  SHA512:
6
- metadata.gz: e219263a6d06ff6bc97dd86df3a289c82642f6b3c333779e107e8f21ae07c75fcad39f110cd84f7ef25303f752b43ee81b51c78a0d9f12591877cafddf861657
7
- data.tar.gz: 705383f050a1e3b84524c5a6dcc74e8e30027a3d663cd944e616da033527afd63f13bf4872c6b4c8175385939b240ceb6a0e4d4265622e53efa52e299831b3e2
6
+ metadata.gz: 1fbc06fdc2cd8dbf92bc5d336ab5d1b1dc45f393e26ae47e45573f346be04912544dee4b31097a3fa1767ce1981c05fa5eb046fd5428947afd9cfeb90cf9827c
7
+ data.tar.gz: 402f51353a4b4865e67ae982cc4794aa60658e2afc3e97c1c8faf3eb03f93ed0306020b4618dc870e62aa34c184d85dd3e424b647254bedaf5436b92a7af0199
data/Appraisals CHANGED
@@ -15,3 +15,7 @@ appraise "rails-6.0" do
15
15
  gem "rails", "~>6.0.0"
16
16
  end
17
17
 
18
+ appraise "rails-6.1" do
19
+ gem "rails", "~>6.1.0"
20
+ end
21
+
data/README.md CHANGED
@@ -43,6 +43,14 @@ And a scope method for finding records assocatied with an id:
43
43
  Parent.child_ids_including(2) # => [<Parent child_ids: [1,2,3]>]
44
44
  ```
45
45
 
46
+ Or any of specified array of ids:
47
+
48
+ ```ruby
49
+ Parent.child_ids_including([2,4,5]) # => [<Parent child_ids: [1,2,3]>]
50
+ ```
51
+
52
+ `touch: true` can be specified on belongs_to_many to touch the associated records' timestamps when the record is modified.
53
+
46
54
  It also adds an `json_foreign_key` option to `has_many` for specifying that the foreign keys are in a json array.
47
55
 
48
56
  ```ruby
@@ -26,5 +26,6 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "rspec"
27
27
  spec.add_development_dependency "sqlite3"
28
28
  spec.add_development_dependency "byebug"
29
+ spec.add_development_dependency "timecop"
29
30
  end
30
31
 
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~>6.1.0"
6
+
7
+ gemspec path: "../"
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  end
12
12
  private_constant :FIELD_INCLUDE_SCOPE_BUILDER_PROC
13
13
 
14
- def belongs_to_many(many, class_name: nil)
14
+ def belongs_to_many(many, class_name: nil, touch: nil)
15
15
  one = many.to_s.singularize
16
16
  one_ids = :"#{one}_ids"
17
17
  one_ids_equals = :"#{one_ids}="
@@ -22,10 +22,39 @@ module ActiveRecord
22
22
 
23
23
  serialize one_ids, JSON
24
24
 
25
+ if touch
26
+ after_commit do
27
+ if respond_to?(:saved_changes)
28
+ old_ids, new_ids = saved_changes[one_ids.to_s]
29
+ else
30
+ old_ids, new_ids = previous_changes[one_ids.to_s]
31
+ end
32
+ ids = Array(old_ids) | Array(new_ids)
33
+ scope = class_name.constantize.where(self.class.primary_key => ids)
34
+
35
+ if scope.respond_to?(:touch) # AR 6.0+
36
+ scope.touch_all
37
+ elsif self.class.respond_to?(:touch_attributes_with_time) # AR 5.1+
38
+ scope.update_all self.class.touch_attributes_with_time
39
+ else # AR 5.0
40
+ attributes = timestamp_attributes_for_update_in_model.inject({}) do |attributes, key|
41
+ attributes.merge(key => current_time_from_proper_timezone)
42
+ end
43
+ scope.update_all attributes
44
+ end
45
+ end
46
+ end
47
+
25
48
  extend Module.new {
26
49
  define_method :"#{one_ids}_including" do |id|
27
50
  raise "can't query for a record that does not have an id!" if id.blank?
28
- FIELD_INCLUDE_SCOPE_BUILDER_PROC.call(self, one_ids, id)
51
+ if id.is_a?(Hash)
52
+ Array(id[:any]).inject(none) do |context, id|
53
+ context.or(FIELD_INCLUDE_SCOPE_BUILDER_PROC.call(self, one_ids, id))
54
+ end
55
+ else
56
+ FIELD_INCLUDE_SCOPE_BUILDER_PROC.call(self, one_ids, id)
57
+ end
29
58
  end
30
59
  }
31
60
 
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module JsonAssociations
3
- VERSION = "0.7.0"
3
+ VERSION = "0.9.0"
4
4
  end
5
5
  end
@@ -9,16 +9,21 @@ describe ActiveRecord::JsonAssociations do
9
9
  create_table :parents do |t|
10
10
  t.text :child_ids
11
11
  t.text :fuzzy_ids
12
+ t.timestamps
12
13
  end
13
14
 
14
- create_table :children
15
+ create_table :children do |t|
16
+ t.timestamps
17
+ end
15
18
 
16
- create_table :pets
19
+ create_table :pets do |t|
20
+ t.timestamps
21
+ end
17
22
  end
18
23
  end
19
24
 
20
25
  class Parent < ActiveRecord::Base
21
- belongs_to_many :children
26
+ belongs_to_many :children, touch: true
22
27
  belongs_to_many :fuzzies, class_name: "Pet"
23
28
  end
24
29
 
@@ -38,6 +43,7 @@ describe ActiveRecord::JsonAssociations do
38
43
 
39
44
  describe ".belongs_to_many :children" do
40
45
  subject { Parent.new }
46
+ let!(:winner) { Parent.create! }
41
47
 
42
48
  describe ".child_ids_including" do
43
49
  context "finds records with the specified id" do
@@ -63,6 +69,113 @@ describe ActiveRecord::JsonAssociations do
63
69
  expect(Parent.child_ids_including(child.id)).to eq [parent]
64
70
  end
65
71
  end
72
+
73
+ context "finds records including any of the specified array of ids" do
74
+ let(:peter) { Child.create! }
75
+ let(:paul) { Child.create! }
76
+
77
+ it "both as the whole json array" do
78
+ parent = Parent.create(children: [peter, paul])
79
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
80
+ end
81
+
82
+ it "one as the whole json array" do
83
+ parent = Parent.create(children: [peter])
84
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
85
+ end
86
+
87
+ it "the other as the whole json array" do
88
+ parent = Parent.create(children: [paul])
89
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
90
+ end
91
+
92
+ it "both at the beginning of the json array" do
93
+ parent = Parent.create(children: [peter, paul, Child.create!])
94
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
95
+ end
96
+
97
+ it "one at the beginning of the json array" do
98
+ parent = Parent.create(children: [peter, Child.create!])
99
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
100
+ end
101
+
102
+ it "the other at the beginning of the json array" do
103
+ parent = Parent.create(children: [paul, Child.create!])
104
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
105
+ end
106
+
107
+ it "both in the middle of the json array" do
108
+ parent = Parent.create(children: [Child.create!, peter, paul, Child.create!])
109
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
110
+ end
111
+
112
+ it "one in the middle of the json array" do
113
+ parent = Parent.create(children: [Child.create!, peter, Child.create!])
114
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
115
+ end
116
+
117
+ it "the other in the middle of the json array" do
118
+ parent = Parent.create(children: [Child.create!, paul, Child.create!])
119
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
120
+ end
121
+
122
+ it "both at the end of the json array" do
123
+ parent = Parent.create(children: [Child.create!, peter, paul])
124
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
125
+ end
126
+
127
+ it "one at the end of the json array" do
128
+ parent = Parent.create(children: [Child.create!, peter])
129
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
130
+ end
131
+
132
+ it "the other at the end of the json array" do
133
+ parent = Parent.create(children: [Child.create!, paul])
134
+ expect(Parent.child_ids_including(any: [peter.id, paul.id])).to eq [parent]
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "touch: true" do
140
+ around do |example|
141
+ old_zone = Time.zone
142
+ Time.zone = "UTC"
143
+ example.run
144
+ Time.zone = old_zone
145
+ end
146
+
147
+ let(:old_time) { 1.year.ago.round }
148
+ let(:new_time) { 1.second.ago.round }
149
+
150
+ around do |example|
151
+ Timecop.freeze(new_time) do
152
+ example.run
153
+ end
154
+ end
155
+
156
+ it "touches associated records" do
157
+ children = [Child.create!(updated_at: old_time), Child.create!(updated_at: old_time)]
158
+ fuzzies = [Pet.create!(updated_at: old_time), Pet.create!(updated_at: old_time)]
159
+ parent = Parent.create!(children: children, fuzzies: fuzzies)
160
+ expect(children.each(&:reload).map(&:updated_at)).to eq [new_time, new_time] # touch: true
161
+ expect(fuzzies.each(&:reload).map(&:updated_at)).to eq [old_time, old_time] # touch: nil
162
+ end
163
+
164
+ it "touches removed associated records" do
165
+ peter, paul, mary = Child.create!, Child.create!, Child.create!
166
+ parent = Parent.create!(children: [peter, paul, mary])
167
+ [peter, paul, mary].each { |child| child.update_column :updated_at, old_time }
168
+ parent.update!(children: [peter, paul])
169
+ expect([peter, paul, mary].each(&:reload).map(&:updated_at)).to eq [new_time, new_time, new_time]
170
+ end
171
+
172
+ it "touches added associated records" do
173
+ peter, paul, mary = Child.create!, Child.create!, Child.create!
174
+ parent = Parent.create!(children: [peter, paul])
175
+ [peter, paul, mary].each { |child| child.update_column :updated_at, old_time }
176
+ parent.update!(children: [peter, paul, mary])
177
+ expect([peter, paul, mary].each(&:reload).map(&:updated_at)).to eq [new_time, new_time, new_time]
178
+ end
66
179
  end
67
180
 
68
181
  describe "#child_ids" do
@@ -1,4 +1,5 @@
1
1
  require "byebug"
2
+ require "timecop"
2
3
 
3
4
  RSpec.configure do |config|
4
5
  config.filter_run focus: true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record-json_associations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-27 00:00:00.000000000 Z
11
+ date: 2021-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  description: Instead of a many-to-many join table, serialize the ids into a JSON array.
112
126
  email:
113
127
  - micah@botandrose.com
@@ -130,6 +144,7 @@ files:
130
144
  - gemfiles/rails_5.1.gemfile
131
145
  - gemfiles/rails_5.2.gemfile
132
146
  - gemfiles/rails_6.0.gemfile
147
+ - gemfiles/rails_6.1.gemfile
133
148
  - lib/active_record/json_associations.rb
134
149
  - lib/active_record/json_associations/version.rb
135
150
  - spec/json_associations_spec.rb