active_record-json_associations 0.13.0 → 1.0.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/.github/workflows/ci.yml +46 -10
- data/Appraisals +6 -8
- data/CLAUDE.md +38 -0
- data/README.md +9 -6
- data/active_record-json_associations.gemspec +3 -0
- data/gemfiles/{rails_7.1.gemfile → rails_8.0.gemfile} +1 -1
- data/gemfiles/{rails_7.0.gemfile → rails_8.1.gemfile} +1 -2
- data/lib/active_record/json_associations/version.rb +1 -1
- data/lib/active_record/json_associations.rb +63 -46
- data/spec/{json_associations_spec.rb → belongs_to_many_spec.rb} +6 -218
- data/spec/has_many_spec.rb +263 -0
- data/spec/spec_helper.rb +8 -0
- metadata +41 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0425a5d0c7935d72007daa4961178471c58a88dac30522a9137e89923f158dbd
|
|
4
|
+
data.tar.gz: cfbc21cae4d8655ad0c62c2a73c95715d6207f882e3de6433dc079abc3eff9d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15b8f1ea8aebceea06a143635cfb8242fec793f319196d1af11e90a5dbc77bbc68e43d08ff6e9c893d94f79180bb9d1f11db2bf8c4a6f3048e2c870a9f70cffc
|
|
7
|
+
data.tar.gz: 2862a70dfb5c0db59dbf30facb73f0c5d810e7c4c00404c5c5eb74daf7aa6c1cacdca86560db5727b1c7e6537f3d0d3bdea2515837c4af0ee3a18b4a5b680fb4
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -1,21 +1,57 @@
|
|
|
1
1
|
name: CI
|
|
2
|
+
|
|
2
3
|
on: [push, pull_request]
|
|
4
|
+
|
|
3
5
|
jobs:
|
|
4
6
|
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
5
8
|
strategy:
|
|
6
9
|
fail-fast: false
|
|
7
10
|
matrix:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
ruby: ["3.2", "3.3", "3.4", "4.0"]
|
|
12
|
+
gemfile:
|
|
13
|
+
- gemfiles/rails_7.2.gemfile
|
|
14
|
+
- gemfiles/rails_8.0.gemfile
|
|
15
|
+
- gemfiles/rails_8.1.gemfile
|
|
16
|
+
database: [sqlite, postgres, mysql]
|
|
17
|
+
services:
|
|
18
|
+
postgres:
|
|
19
|
+
image: postgres:16
|
|
20
|
+
env:
|
|
21
|
+
POSTGRES_PASSWORD: postgres
|
|
22
|
+
options: >-
|
|
23
|
+
--health-cmd pg_isready
|
|
24
|
+
--health-interval 10s
|
|
25
|
+
--health-timeout 5s
|
|
26
|
+
--health-retries 5
|
|
27
|
+
ports:
|
|
28
|
+
- 5432:5432
|
|
29
|
+
mysql:
|
|
30
|
+
image: mysql:8
|
|
31
|
+
env:
|
|
32
|
+
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
|
33
|
+
options: >-
|
|
34
|
+
--health-cmd "mysqladmin ping"
|
|
35
|
+
--health-interval 10s
|
|
36
|
+
--health-timeout 5s
|
|
37
|
+
--health-retries 5
|
|
38
|
+
ports:
|
|
39
|
+
- 3306:3306
|
|
40
|
+
env:
|
|
41
|
+
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
|
14
42
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
43
|
+
- uses: actions/checkout@v4
|
|
16
44
|
- uses: ruby/setup-ruby@v1
|
|
17
45
|
with:
|
|
18
46
|
ruby-version: ${{ matrix.ruby }}
|
|
19
|
-
bundler-cache: true
|
|
20
|
-
-
|
|
21
|
-
|
|
47
|
+
bundler-cache: true
|
|
48
|
+
- name: Create test database
|
|
49
|
+
if: matrix.database == 'postgres'
|
|
50
|
+
run: PGPASSWORD=postgres createdb -h localhost -U postgres test
|
|
51
|
+
- name: Create test database
|
|
52
|
+
if: matrix.database == 'mysql'
|
|
53
|
+
run: mysql -h 127.0.0.1 -u root -e "CREATE DATABASE test"
|
|
54
|
+
- name: Run tests
|
|
55
|
+
run: bundle exec rake
|
|
56
|
+
env:
|
|
57
|
+
DATABASE_URL: ${{ matrix.database == 'postgres' && 'postgres://postgres:postgres@localhost:5432/test' || matrix.database == 'mysql' && 'trilogy://root@127.0.0.1:3306/test' || 'sqlite3::memory:' }}
|
data/Appraisals
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
appraise "rails-7.
|
|
2
|
-
gem "rails", "~>7.
|
|
3
|
-
gem "sqlite3", "~>1.0"
|
|
1
|
+
appraise "rails-7.2" do
|
|
2
|
+
gem "rails", "~>7.2.0"
|
|
4
3
|
end
|
|
5
4
|
|
|
6
|
-
appraise "rails-
|
|
7
|
-
gem "rails", "~>
|
|
5
|
+
appraise "rails-8.0" do
|
|
6
|
+
gem "rails", "~>8.0.0"
|
|
8
7
|
end
|
|
9
8
|
|
|
10
|
-
appraise "rails-
|
|
11
|
-
gem "rails", "~>
|
|
9
|
+
appraise "rails-8.1" do
|
|
10
|
+
gem "rails", "~>8.1.0"
|
|
12
11
|
end
|
|
13
|
-
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## About
|
|
6
|
+
|
|
7
|
+
ActiveRecord::JsonAssociations is a Ruby gem that provides an alternative to traditional many-to-many join tables by storing foreign keys in a JSON array on the parent record.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Run all tests
|
|
13
|
+
bundle exec rake
|
|
14
|
+
|
|
15
|
+
# Run a single spec file
|
|
16
|
+
bundle exec rspec spec/belongs_to_many_spec.rb
|
|
17
|
+
|
|
18
|
+
# Run a specific test by line number
|
|
19
|
+
bundle exec rspec spec/belongs_to_many_spec.rb:42
|
|
20
|
+
|
|
21
|
+
# Test against specific Rails versions using Appraisal
|
|
22
|
+
BUNDLE_GEMFILE=gemfiles/rails_7.1.gemfile bundle exec rake
|
|
23
|
+
BUNDLE_GEMFILE=gemfiles/rails_8.0.gemfile bundle exec rake
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Architecture
|
|
27
|
+
|
|
28
|
+
The gem extends `ActiveRecord::Base` with a single module (`ActiveRecord::JsonAssociations`) that provides two main class methods:
|
|
29
|
+
|
|
30
|
+
- **`belongs_to_many`** - Stores foreign keys as a JSON array in a text/json column on the parent model. Provides `children`, `children=`, `child_ids`, `child_ids=`, `children?` methods plus a `child_ids_including` scope for querying.
|
|
31
|
+
|
|
32
|
+
- **`has_many :json_foreign_key`** - The inverse relationship. When a child model uses this option, it can find parents that reference it via their JSON arrays. Also provides `build_parent`, `create_parent`, `create_parent!` builder methods.
|
|
33
|
+
|
|
34
|
+
Both methods support native JSON columns (using `JSON_CONTAINS`) or text columns (using `LIKE` queries with serialized JSON).
|
|
35
|
+
|
|
36
|
+
## Testing
|
|
37
|
+
|
|
38
|
+
Tests use RSpec with an in-memory SQLite database. Each spec file sets up its own schema and model classes. Use `focus: true` on individual specs during development.
|
data/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# ActiveRecord::JsonAssociations
|
|
2
2
|
|
|
3
3
|
[](https://github.com/botandrose/active_record-json_associations/actions/workflows/ci.yml)
|
|
4
|
-
[](https://codeclimate.com/github/botandrose/active_record-json_associations)
|
|
5
4
|
|
|
6
5
|
Instead of keeping the foreign keys on the children, or in a many-to-many join table, let's keep them in a JSON array on the parent.
|
|
7
6
|
|
|
@@ -12,7 +11,7 @@ require "active_record/json_associations"
|
|
|
12
11
|
|
|
13
12
|
ActiveRecord::Schema.define do
|
|
14
13
|
create_table :parents do |t|
|
|
15
|
-
t.
|
|
14
|
+
t.json :child_ids, default: []
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
create_table :children
|
|
@@ -23,6 +22,8 @@ class Parent < ActiveRecord::Base
|
|
|
23
22
|
end
|
|
24
23
|
```
|
|
25
24
|
|
|
25
|
+
**Note:** The `child_ids` column must be a native JSON type. Text columns are not supported.
|
|
26
|
+
|
|
26
27
|
This will add some familiar `has_many`-style methods:
|
|
27
28
|
|
|
28
29
|
```ruby
|
|
@@ -37,16 +38,16 @@ parent.child_ids #=> [1,2]
|
|
|
37
38
|
parent.children? #=> true
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
And a scope method for finding records
|
|
41
|
+
And a scope method for finding records associated with an id:
|
|
41
42
|
|
|
42
43
|
```ruby
|
|
43
44
|
Parent.child_ids_including(2) # => [<Parent child_ids: [1,2,3]>]
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
Or any of specified array of ids:
|
|
47
|
+
Or any of a specified array of ids:
|
|
47
48
|
|
|
48
49
|
```ruby
|
|
49
|
-
Parent.child_ids_including([2,4,5]) # => [<Parent child_ids: [1,2,3]>]
|
|
50
|
+
Parent.child_ids_including(any: [2,4,5]) # => [<Parent child_ids: [1,2,3]>]
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
`touch: true` can be specified on belongs_to_many to touch the associated records' timestamps when the record is modified.
|
|
@@ -77,7 +78,9 @@ child.build_parent(name: "Momma")
|
|
|
77
78
|
|
|
78
79
|
## Requirements
|
|
79
80
|
|
|
80
|
-
*
|
|
81
|
+
* Ruby 3.2+
|
|
82
|
+
* ActiveRecord 7.2+
|
|
83
|
+
* Database with JSON column support (MySQL, PostgreSQL, SQLite 3.9+)
|
|
81
84
|
|
|
82
85
|
## Contributing
|
|
83
86
|
|
|
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = %q{Instead of a many-to-many join table, serialize the ids into a JSON array.}
|
|
13
13
|
spec.homepage = "https://github.com/botandrose/active_record-json_associations"
|
|
14
14
|
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.2"
|
|
15
16
|
|
|
16
17
|
spec.files = `git ls-files -z`.split("\x0")
|
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
@@ -25,6 +26,8 @@ Gem::Specification.new do |spec|
|
|
|
25
26
|
spec.add_development_dependency "rake"
|
|
26
27
|
spec.add_development_dependency "rspec"
|
|
27
28
|
spec.add_development_dependency "sqlite3"
|
|
29
|
+
spec.add_development_dependency "pg"
|
|
30
|
+
spec.add_development_dependency "trilogy"
|
|
28
31
|
spec.add_development_dependency "byebug"
|
|
29
32
|
spec.add_development_dependency "timecop"
|
|
30
33
|
end
|
|
@@ -1,18 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "active_record"
|
|
2
4
|
require "json"
|
|
3
5
|
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module JsonAssociations
|
|
8
|
+
ORDER_BY_IDS_PROC = proc do |scope, ids|
|
|
9
|
+
if ids.empty?
|
|
10
|
+
scope
|
|
11
|
+
else
|
|
12
|
+
pk = scope.klass.primary_key
|
|
13
|
+
quoted_ids = ids.map { |id| scope.connection.quote(id) }
|
|
14
|
+
order_sql = case scope.connection.adapter_name
|
|
15
|
+
when "Mysql2", "Trilogy"
|
|
16
|
+
"FIELD(#{pk}, #{quoted_ids.join(",")})"
|
|
17
|
+
when "PostgreSQL"
|
|
18
|
+
"array_position(ARRAY[#{quoted_ids.join(",")}], #{pk})"
|
|
19
|
+
else
|
|
20
|
+
fragments = ids.each_with_index.map { |id, i| "WHEN #{quoted_ids[i]} THEN #{i}" }
|
|
21
|
+
"CASE #{pk} #{fragments.join(" ")} END"
|
|
22
|
+
end
|
|
23
|
+
scope.order!(Arel.sql(order_sql))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
private_constant :ORDER_BY_IDS_PROC
|
|
27
|
+
|
|
6
28
|
FIELD_INCLUDE_SCOPE_BUILDER_PROC = proc do |context, field, id|
|
|
7
|
-
|
|
29
|
+
unless context.columns_hash[field.to_s].type == :json
|
|
30
|
+
raise ArgumentError, "#{field} column must be of type :json"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sanitized_id = id.to_i
|
|
8
34
|
|
|
9
|
-
|
|
10
|
-
|
|
35
|
+
case context.connection.adapter_name
|
|
36
|
+
when "Mysql2", "Trilogy"
|
|
37
|
+
context.where("JSON_CONTAINS(#{field}, ?, '$')", sanitized_id.to_json)
|
|
38
|
+
when "PostgreSQL"
|
|
39
|
+
context.where("#{field}::jsonb @> ?::jsonb", [sanitized_id].to_json)
|
|
11
40
|
else
|
|
12
|
-
context.where("#{field}=
|
|
13
|
-
context.where("#{field} LIKE '[#{id},%'")).or(
|
|
14
|
-
context.where("#{field} LIKE '%,#{id},%'")).or(
|
|
15
|
-
context.where("#{field} LIKE '%,#{id}]'"))
|
|
41
|
+
context.where("EXISTS (SELECT 1 FROM json_each(#{field}) WHERE value = ?)", sanitized_id)
|
|
16
42
|
end
|
|
17
43
|
end
|
|
18
44
|
private_constant :FIELD_INCLUDE_SCOPE_BUILDER_PROC
|
|
@@ -26,34 +52,16 @@ module ActiveRecord
|
|
|
26
52
|
|
|
27
53
|
class_name ||= one.classify
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if !using_json
|
|
32
|
-
if ActiveRecord.version >= Gem::Version.new("7.1")
|
|
33
|
-
serialize one_ids, coder: JSON
|
|
34
|
-
else
|
|
35
|
-
serialize one_ids, JSON
|
|
36
|
-
end
|
|
55
|
+
unless columns_hash[one_ids.to_s].type == :json
|
|
56
|
+
raise ArgumentError, "#{one_ids} column must be of type :json"
|
|
37
57
|
end
|
|
38
58
|
|
|
39
59
|
if touch
|
|
40
60
|
after_commit do
|
|
41
61
|
unless no_touching?
|
|
42
|
-
|
|
43
|
-
old_ids, new_ids = send(method)[one_ids.to_s]
|
|
62
|
+
old_ids, new_ids = saved_changes[one_ids.to_s]
|
|
44
63
|
ids = Array(send(one_ids)) | Array(old_ids) | Array(new_ids)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if scope.respond_to?(:touch) # AR 6.0+
|
|
48
|
-
scope.touch_all
|
|
49
|
-
elsif self.class.respond_to?(:touch_attributes_with_time) # AR 5.1+
|
|
50
|
-
scope.update_all self.class.touch_attributes_with_time
|
|
51
|
-
else # AR 5.0
|
|
52
|
-
attributes = timestamp_attributes_for_update_in_model.inject({}) do |attributes, key|
|
|
53
|
-
attributes.merge(key => current_time_from_proper_timezone)
|
|
54
|
-
end
|
|
55
|
-
scope.update_all attributes
|
|
56
|
-
end
|
|
64
|
+
class_name.constantize.where(self.class.primary_key => ids).touch_all
|
|
57
65
|
end
|
|
58
66
|
end
|
|
59
67
|
end
|
|
@@ -79,21 +87,14 @@ module ActiveRecord
|
|
|
79
87
|
end
|
|
80
88
|
|
|
81
89
|
define_method one_ids_equals do |ids|
|
|
82
|
-
super Array(ids).select(&:present?).map(&:to_i)
|
|
90
|
+
super Array(ids).select(&:present?).map(&:to_i).uniq
|
|
83
91
|
end
|
|
84
92
|
|
|
85
93
|
define_method many do
|
|
86
94
|
klass = class_name.constantize
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
ids
|
|
90
|
-
scope.where!(klass.primary_key => ids)
|
|
91
|
-
|
|
92
|
-
fragments = []
|
|
93
|
-
fragments += ["#{klass.primary_key} NOT IN (#{ids.map(&:to_s).join(",")})"] if ids.any?
|
|
94
|
-
fragments += ids.reverse.map { |id| "#{klass.primary_key}=#{id}" }
|
|
95
|
-
order_by_ids = fragments.join(", ")
|
|
96
|
-
scope.order!(Arel.sql(order_by_ids))
|
|
95
|
+
ids = send(one_ids).map(&:to_i)
|
|
96
|
+
scope = klass.where(klass.primary_key => ids)
|
|
97
|
+
ORDER_BY_IDS_PROC.call(scope, ids)
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
define_method many_equals do |collection|
|
|
@@ -126,30 +127,43 @@ module ActiveRecord
|
|
|
126
127
|
create_one_bang = :"create_#{one}!"
|
|
127
128
|
|
|
128
129
|
class_name = options[:class_name] || one.classify
|
|
129
|
-
klass = class_name.constantize
|
|
130
130
|
|
|
131
131
|
foreign_key = options[:json_foreign_key]
|
|
132
132
|
foreign_key = :"#{model_name.singular}_ids" if foreign_key == true
|
|
133
133
|
|
|
134
|
+
pending_associations_var = :"@pending_#{many}"
|
|
135
|
+
|
|
136
|
+
after_create do
|
|
137
|
+
if (pending = instance_variable_get(pending_associations_var))
|
|
138
|
+
instance_variable_set(pending_associations_var, nil)
|
|
139
|
+
send(many_equals, pending)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
134
143
|
include Module.new {
|
|
135
144
|
define_method one_ids do
|
|
136
145
|
send(many).pluck(:id)
|
|
137
146
|
end
|
|
138
147
|
|
|
139
148
|
define_method one_ids_equals do |ids|
|
|
140
|
-
|
|
149
|
+
klass = class_name.constantize
|
|
150
|
+
normalized_ids = Array(ids).select(&:present?).map(&:to_i).uniq
|
|
141
151
|
send many_equals, klass.find(normalized_ids)
|
|
142
152
|
end
|
|
143
153
|
|
|
144
154
|
define_method many do
|
|
155
|
+
klass = class_name.constantize
|
|
145
156
|
FIELD_INCLUDE_SCOPE_BUILDER_PROC.call(klass, foreign_key, id)
|
|
146
157
|
end
|
|
147
158
|
|
|
148
159
|
define_method many_equals do |collection|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
if new_record?
|
|
161
|
+
instance_variable_set(pending_associations_var, collection)
|
|
162
|
+
else
|
|
163
|
+
collection.each do |record|
|
|
164
|
+
new_id_array = Array(record.send(foreign_key)) | [id]
|
|
165
|
+
record.update foreign_key => new_id_array
|
|
166
|
+
end
|
|
153
167
|
end
|
|
154
168
|
end
|
|
155
169
|
|
|
@@ -158,14 +172,17 @@ module ActiveRecord
|
|
|
158
172
|
end
|
|
159
173
|
|
|
160
174
|
define_method build_one do |attributes={}|
|
|
175
|
+
klass = class_name.constantize
|
|
161
176
|
klass.new attributes.merge!(foreign_key => [id])
|
|
162
177
|
end
|
|
163
178
|
|
|
164
179
|
define_method create_one do |attributes={}|
|
|
180
|
+
klass = class_name.constantize
|
|
165
181
|
klass.create attributes.merge!(foreign_key => [id])
|
|
166
182
|
end
|
|
167
183
|
|
|
168
184
|
define_method create_one_bang do |attributes={}|
|
|
185
|
+
klass = class_name.constantize
|
|
169
186
|
klass.create! attributes.merge!(foreign_key => [id])
|
|
170
187
|
end
|
|
171
188
|
}
|
|
@@ -2,22 +2,22 @@ require "active_record/json_associations"
|
|
|
2
2
|
|
|
3
3
|
describe ActiveRecord::JsonAssociations do
|
|
4
4
|
before do
|
|
5
|
-
ActiveRecord::Base.establish_connection
|
|
5
|
+
ActiveRecord::Base.establish_connection database_config
|
|
6
6
|
|
|
7
7
|
silence_stream(STDOUT) do
|
|
8
8
|
ActiveRecord::Schema.define do
|
|
9
|
-
create_table :parents do |t|
|
|
9
|
+
create_table :parents, force: true do |t|
|
|
10
10
|
t.string :name
|
|
11
|
-
t.
|
|
12
|
-
t.
|
|
11
|
+
t.json :child_ids
|
|
12
|
+
t.json :fuzzy_ids
|
|
13
13
|
t.timestamps
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
create_table :children do |t|
|
|
16
|
+
create_table :children, force: true do |t|
|
|
17
17
|
t.timestamps
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
create_table :pets do |t|
|
|
20
|
+
create_table :pets, force: true do |t|
|
|
21
21
|
t.timestamps
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -274,216 +274,4 @@ describe ActiveRecord::JsonAssociations do
|
|
|
274
274
|
end
|
|
275
275
|
end
|
|
276
276
|
end
|
|
277
|
-
|
|
278
|
-
describe ".has_many :parents, json_foreign_key: true" do
|
|
279
|
-
subject { Child.create! }
|
|
280
|
-
|
|
281
|
-
let(:parents) { [Parent.create!, Parent.create!, Parent.create!] }
|
|
282
|
-
|
|
283
|
-
describe "#parent_ids" do
|
|
284
|
-
it "is empty by default" do
|
|
285
|
-
expect(subject.parent_ids).to eq []
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
it "is an accessor" do
|
|
289
|
-
subject.parent_ids = parents.map(&:id)
|
|
290
|
-
expect(subject.parent_ids).to eq parents.map(&:id)
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
describe "#parent_ids=" do
|
|
295
|
-
before { parents } # ensure parents exist
|
|
296
|
-
|
|
297
|
-
it "normalizes to integers" do
|
|
298
|
-
subject.parent_ids = ["1",2,"3"]
|
|
299
|
-
expect(subject.parent_ids).to eq [1,2,3]
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
it "ignores empty strings" do
|
|
303
|
-
subject.parent_ids = ["","1","2","3"]
|
|
304
|
-
expect(subject.parent_ids).to eq [1,2,3]
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
describe "#parents" do
|
|
309
|
-
it "returns an empty array when there are no parents" do
|
|
310
|
-
expect(subject.parents).to eq []
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
it "finds the children by id" do
|
|
314
|
-
subject.parent_ids = parents.map(&:id)
|
|
315
|
-
expect(subject.parents).to eq parents
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
it "is an accessor" do
|
|
319
|
-
subject.parents = parents
|
|
320
|
-
expect(subject.parents).to eq parents
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
context "finds records with the specified id" do
|
|
324
|
-
let(:child) { Child.create! }
|
|
325
|
-
|
|
326
|
-
it "as the whole json array" do
|
|
327
|
-
parent = Parent.create(children: [child])
|
|
328
|
-
expect(child.parents).to eq [parent]
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
it "at the beginning of the json array" do
|
|
332
|
-
parent = Parent.create(children: [child, Child.create!])
|
|
333
|
-
expect(child.parents).to eq [parent]
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
it "in the middle of the json array" do
|
|
337
|
-
parent = Parent.create(children: [Child.create!, child, Child.create!])
|
|
338
|
-
expect(child.parents).to eq [parent]
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
it "at the end of the json array" do
|
|
342
|
-
parent = Parent.create(children: [Child.create!, child])
|
|
343
|
-
expect(child.parents).to eq [parent]
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
describe "#parents?" do
|
|
349
|
-
it "returns false when there are no parents" do
|
|
350
|
-
expect(subject.parents?).to be_falsey
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
it "returns true when there are parents" do
|
|
354
|
-
subject.parents = parents
|
|
355
|
-
expect(subject.parents?).to be_truthy
|
|
356
|
-
end
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
describe "#build_parent" do
|
|
360
|
-
it "doesnt save the record" do
|
|
361
|
-
parent = subject.build_parent
|
|
362
|
-
expect(parent).to be_new_record
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
it "sets the foreign key column" do
|
|
366
|
-
parent = subject.build_parent
|
|
367
|
-
expect(parent.children).to eq([subject])
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
it "passes attributes through" do
|
|
371
|
-
parent = subject.build_parent(name: "Parent")
|
|
372
|
-
expect(parent.name).to eq("Parent")
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
describe "#create_parent" do
|
|
377
|
-
it "saves the record" do
|
|
378
|
-
parent = subject.create_parent
|
|
379
|
-
expect(parent).to be_persisted
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
it "sets the foreign key column" do
|
|
383
|
-
parent = subject.create_parent
|
|
384
|
-
expect(parent.children).to eq([subject])
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
it "passes attributes through" do
|
|
388
|
-
parent = subject.create_parent(name: "Parent")
|
|
389
|
-
expect(parent.name).to eq("Parent")
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
it "calls create on the model" do
|
|
393
|
-
expect(Parent).to receive(:create)
|
|
394
|
-
subject.create_parent
|
|
395
|
-
end
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
describe "#create_parent!" do
|
|
399
|
-
it "saves the record" do
|
|
400
|
-
parent = subject.create_parent!
|
|
401
|
-
expect(parent).to be_persisted
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
it "sets the foreign key column" do
|
|
405
|
-
parent = subject.create_parent!
|
|
406
|
-
expect(parent.children).to eq([subject])
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
it "passes attributes through" do
|
|
410
|
-
parent = subject.create_parent!(name: "Parent")
|
|
411
|
-
expect(parent.name).to eq("Parent")
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
it "calls create! on the model" do
|
|
415
|
-
expect(Parent).to receive(:create!)
|
|
416
|
-
subject.create_parent!
|
|
417
|
-
end
|
|
418
|
-
end
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
describe ".has_many :parents, json_foreign_key: :fuzzy_ids" do
|
|
422
|
-
subject { Pet.create! }
|
|
423
|
-
|
|
424
|
-
let(:parents) { [Parent.create!, Parent.create!, Parent.create!] }
|
|
425
|
-
|
|
426
|
-
describe "#parent_ids" do
|
|
427
|
-
it "is empty by default" do
|
|
428
|
-
expect(subject.parent_ids).to eq []
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
it "is an accessor" do
|
|
432
|
-
subject.parent_ids = parents.map(&:id)
|
|
433
|
-
expect(subject.parent_ids).to eq parents.map(&:id)
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
describe "#parents" do
|
|
438
|
-
it "returns an empty array when there are no parents" do
|
|
439
|
-
expect(subject.parents).to eq []
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
it "finds the parents by id" do
|
|
443
|
-
subject.parent_ids = parents.map(&:id)
|
|
444
|
-
expect(subject.parents).to eq parents
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
it "is an accessor" do
|
|
448
|
-
subject.parents = parents
|
|
449
|
-
expect(subject.parents).to eq parents
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
context "finds records with the specified id" do
|
|
453
|
-
let(:pet) { Pet.create! }
|
|
454
|
-
|
|
455
|
-
it "as the whole json array" do
|
|
456
|
-
parent = Parent.create(fuzzies: [pet])
|
|
457
|
-
expect(pet.parents).to eq [parent]
|
|
458
|
-
end
|
|
459
|
-
|
|
460
|
-
it "at the beginning of the json array" do
|
|
461
|
-
parent = Parent.create(fuzzies: [pet, Pet.create!])
|
|
462
|
-
expect(pet.parents).to eq [parent]
|
|
463
|
-
end
|
|
464
|
-
|
|
465
|
-
it "in the middle of the json array" do
|
|
466
|
-
parent = Parent.create(fuzzies: [Pet.create!, pet, Pet.create!])
|
|
467
|
-
expect(pet.parents).to eq [parent]
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
it "at the end of the json array" do
|
|
471
|
-
parent = Parent.create(fuzzies: [Pet.create!, pet])
|
|
472
|
-
expect(pet.parents).to eq [parent]
|
|
473
|
-
end
|
|
474
|
-
end
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
describe "#parents?" do
|
|
478
|
-
it "returns false when there are no parents" do
|
|
479
|
-
expect(subject.parents?).to be_falsey
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
it "returns true when there are parents" do
|
|
483
|
-
subject.parents = parents
|
|
484
|
-
expect(subject.parents?).to be_truthy
|
|
485
|
-
end
|
|
486
|
-
end
|
|
487
|
-
end
|
|
488
277
|
end
|
|
489
|
-
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
require "active_record/json_associations"
|
|
2
|
+
|
|
3
|
+
describe ActiveRecord::JsonAssociations do
|
|
4
|
+
before do
|
|
5
|
+
ActiveRecord::Base.establish_connection database_config
|
|
6
|
+
|
|
7
|
+
silence_stream(STDOUT) do
|
|
8
|
+
ActiveRecord::Schema.define do
|
|
9
|
+
create_table :parents, force: true do |t|
|
|
10
|
+
t.string :name
|
|
11
|
+
t.json :child_ids
|
|
12
|
+
t.json :fuzzy_ids
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
create_table :children, force: true do |t|
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
create_table :pets, force: true do |t|
|
|
21
|
+
t.timestamps
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Parent < ActiveRecord::Base
|
|
27
|
+
belongs_to_many :children, touch: true
|
|
28
|
+
belongs_to_many :fuzzies, class_name: "Pet"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Child < ActiveRecord::Base
|
|
32
|
+
has_many :parents, json_foreign_key: true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Pet < ActiveRecord::Base
|
|
36
|
+
has_many :parents, json_foreign_key: :fuzzy_ids
|
|
37
|
+
|
|
38
|
+
# ensure that regular .has_many invocations still work
|
|
39
|
+
has_many :fallback_parents
|
|
40
|
+
has_many :fallback_parents_with_options, class_name: "Pet"
|
|
41
|
+
has_many :fallback_parents_with_scope, -> { order(:id) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe ".has_many :parents, json_foreign_key: true" do
|
|
46
|
+
subject { Child.create! }
|
|
47
|
+
|
|
48
|
+
let(:parents) { [Parent.create!, Parent.create!, Parent.create!] }
|
|
49
|
+
|
|
50
|
+
describe "#parent_ids" do
|
|
51
|
+
it "is empty by default" do
|
|
52
|
+
expect(subject.parent_ids).to eq []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "is an accessor" do
|
|
56
|
+
subject.parent_ids = parents.map(&:id)
|
|
57
|
+
expect(subject.parent_ids).to eq parents.map(&:id)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#parent_ids=" do
|
|
62
|
+
before { parents } # ensure parents exist
|
|
63
|
+
|
|
64
|
+
it "normalizes to integers" do
|
|
65
|
+
subject.parent_ids = ["1",2,"3"]
|
|
66
|
+
expect(subject.parent_ids).to eq [1,2,3]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "ignores empty strings" do
|
|
70
|
+
subject.parent_ids = ["","1","2","3"]
|
|
71
|
+
expect(subject.parent_ids).to eq [1,2,3]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "#parents" do
|
|
76
|
+
it "returns an empty array when there are no parents" do
|
|
77
|
+
expect(subject.parents).to eq []
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "finds the children by id" do
|
|
81
|
+
subject.parent_ids = parents.map(&:id)
|
|
82
|
+
expect(subject.parents).to eq parents
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "is an accessor" do
|
|
86
|
+
subject.parents = parents
|
|
87
|
+
expect(subject.parents).to eq parents
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "defers assignment until after create for new records" do
|
|
91
|
+
child = Child.new
|
|
92
|
+
child.parents = parents
|
|
93
|
+
expect(parents.map { |p| p.reload.child_ids }).to eq [[], [], []]
|
|
94
|
+
child.save!
|
|
95
|
+
expect(parents.map { |p| p.reload.child_ids }).to eq [[child.id], [child.id], [child.id]]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context "finds records with the specified id" do
|
|
99
|
+
let(:child) { Child.create! }
|
|
100
|
+
|
|
101
|
+
it "as the whole json array" do
|
|
102
|
+
parent = Parent.create(children: [child])
|
|
103
|
+
expect(child.parents).to eq [parent]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "at the beginning of the json array" do
|
|
107
|
+
parent = Parent.create(children: [child, Child.create!])
|
|
108
|
+
expect(child.parents).to eq [parent]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "in the middle of the json array" do
|
|
112
|
+
parent = Parent.create(children: [Child.create!, child, Child.create!])
|
|
113
|
+
expect(child.parents).to eq [parent]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "at the end of the json array" do
|
|
117
|
+
parent = Parent.create(children: [Child.create!, child])
|
|
118
|
+
expect(child.parents).to eq [parent]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "#parents?" do
|
|
124
|
+
it "returns false when there are no parents" do
|
|
125
|
+
expect(subject.parents?).to be_falsey
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "returns true when there are parents" do
|
|
129
|
+
subject.parents = parents
|
|
130
|
+
expect(subject.parents?).to be_truthy
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe "#build_parent" do
|
|
135
|
+
it "doesnt save the record" do
|
|
136
|
+
parent = subject.build_parent
|
|
137
|
+
expect(parent).to be_new_record
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "sets the foreign key column" do
|
|
141
|
+
parent = subject.build_parent
|
|
142
|
+
expect(parent.children).to eq([subject])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "passes attributes through" do
|
|
146
|
+
parent = subject.build_parent(name: "Parent")
|
|
147
|
+
expect(parent.name).to eq("Parent")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "#create_parent" do
|
|
152
|
+
it "saves the record" do
|
|
153
|
+
parent = subject.create_parent
|
|
154
|
+
expect(parent).to be_persisted
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "sets the foreign key column" do
|
|
158
|
+
parent = subject.create_parent
|
|
159
|
+
expect(parent.children).to eq([subject])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "passes attributes through" do
|
|
163
|
+
parent = subject.create_parent(name: "Parent")
|
|
164
|
+
expect(parent.name).to eq("Parent")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "calls create on the model" do
|
|
168
|
+
expect(Parent).to receive(:create)
|
|
169
|
+
subject.create_parent
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe "#create_parent!" do
|
|
174
|
+
it "saves the record" do
|
|
175
|
+
parent = subject.create_parent!
|
|
176
|
+
expect(parent).to be_persisted
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "sets the foreign key column" do
|
|
180
|
+
parent = subject.create_parent!
|
|
181
|
+
expect(parent.children).to eq([subject])
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "passes attributes through" do
|
|
185
|
+
parent = subject.create_parent!(name: "Parent")
|
|
186
|
+
expect(parent.name).to eq("Parent")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "calls create! on the model" do
|
|
190
|
+
expect(Parent).to receive(:create!)
|
|
191
|
+
subject.create_parent!
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
describe ".has_many :parents, json_foreign_key: :fuzzy_ids" do
|
|
197
|
+
subject { Pet.create! }
|
|
198
|
+
|
|
199
|
+
let(:parents) { [Parent.create!, Parent.create!, Parent.create!] }
|
|
200
|
+
|
|
201
|
+
describe "#parent_ids" do
|
|
202
|
+
it "is empty by default" do
|
|
203
|
+
expect(subject.parent_ids).to eq []
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "is an accessor" do
|
|
207
|
+
subject.parent_ids = parents.map(&:id)
|
|
208
|
+
expect(subject.parent_ids).to eq parents.map(&:id)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
describe "#parents" do
|
|
213
|
+
it "returns an empty array when there are no parents" do
|
|
214
|
+
expect(subject.parents).to eq []
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "finds the parents by id" do
|
|
218
|
+
subject.parent_ids = parents.map(&:id)
|
|
219
|
+
expect(subject.parents).to eq parents
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it "is an accessor" do
|
|
223
|
+
subject.parents = parents
|
|
224
|
+
expect(subject.parents).to eq parents
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
context "finds records with the specified id" do
|
|
228
|
+
let(:pet) { Pet.create! }
|
|
229
|
+
|
|
230
|
+
it "as the whole json array" do
|
|
231
|
+
parent = Parent.create(fuzzies: [pet])
|
|
232
|
+
expect(pet.parents).to eq [parent]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "at the beginning of the json array" do
|
|
236
|
+
parent = Parent.create(fuzzies: [pet, Pet.create!])
|
|
237
|
+
expect(pet.parents).to eq [parent]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "in the middle of the json array" do
|
|
241
|
+
parent = Parent.create(fuzzies: [Pet.create!, pet, Pet.create!])
|
|
242
|
+
expect(pet.parents).to eq [parent]
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it "at the end of the json array" do
|
|
246
|
+
parent = Parent.create(fuzzies: [Pet.create!, pet])
|
|
247
|
+
expect(pet.parents).to eq [parent]
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
describe "#parents?" do
|
|
253
|
+
it "returns false when there are no parents" do
|
|
254
|
+
expect(subject.parents?).to be_falsey
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it "returns true when there are parents" do
|
|
258
|
+
subject.parents = parents
|
|
259
|
+
expect(subject.parents?).to be_truthy
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
require "byebug"
|
|
2
2
|
require "timecop"
|
|
3
3
|
|
|
4
|
+
def database_config
|
|
5
|
+
if ENV["DATABASE_URL"]
|
|
6
|
+
{ url: ENV["DATABASE_URL"] }
|
|
7
|
+
else
|
|
8
|
+
{ adapter: "sqlite3", database: ":memory:" }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
4
12
|
RSpec.configure do |config|
|
|
5
13
|
config.filter_run focus: true
|
|
6
14
|
config.run_all_when_everything_filtered = 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.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Micah Geisel
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -94,6 +94,34 @@ dependencies:
|
|
|
94
94
|
- - ">="
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: pg
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: trilogy
|
|
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'
|
|
97
125
|
- !ruby/object:Gem::Dependency
|
|
98
126
|
name: byebug
|
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -134,24 +162,26 @@ files:
|
|
|
134
162
|
- ".gitignore"
|
|
135
163
|
- ".rspec"
|
|
136
164
|
- Appraisals
|
|
165
|
+
- CLAUDE.md
|
|
137
166
|
- Gemfile
|
|
138
167
|
- LICENSE.txt
|
|
139
168
|
- README.md
|
|
140
169
|
- Rakefile
|
|
141
170
|
- active_record-json_associations.gemspec
|
|
142
171
|
- bin/setup
|
|
143
|
-
- gemfiles/rails_7.0.gemfile
|
|
144
|
-
- gemfiles/rails_7.1.gemfile
|
|
145
172
|
- gemfiles/rails_7.2.gemfile
|
|
173
|
+
- gemfiles/rails_8.0.gemfile
|
|
174
|
+
- gemfiles/rails_8.1.gemfile
|
|
146
175
|
- lib/active_record/json_associations.rb
|
|
147
176
|
- lib/active_record/json_associations/version.rb
|
|
148
|
-
- spec/
|
|
177
|
+
- spec/belongs_to_many_spec.rb
|
|
178
|
+
- spec/has_many_spec.rb
|
|
149
179
|
- spec/spec_helper.rb
|
|
150
180
|
homepage: https://github.com/botandrose/active_record-json_associations
|
|
151
181
|
licenses:
|
|
152
182
|
- MIT
|
|
153
183
|
metadata: {}
|
|
154
|
-
post_install_message:
|
|
184
|
+
post_install_message:
|
|
155
185
|
rdoc_options: []
|
|
156
186
|
require_paths:
|
|
157
187
|
- lib
|
|
@@ -159,7 +189,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
159
189
|
requirements:
|
|
160
190
|
- - ">="
|
|
161
191
|
- !ruby/object:Gem::Version
|
|
162
|
-
version: '
|
|
192
|
+
version: '3.2'
|
|
163
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
164
194
|
requirements:
|
|
165
195
|
- - ">="
|
|
@@ -167,9 +197,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
167
197
|
version: '0'
|
|
168
198
|
requirements: []
|
|
169
199
|
rubygems_version: 3.5.11
|
|
170
|
-
signing_key:
|
|
200
|
+
signing_key:
|
|
171
201
|
specification_version: 4
|
|
172
202
|
summary: Instead of a many-to-many join table, serialize the ids into a JSON array.
|
|
173
203
|
test_files:
|
|
174
|
-
- spec/
|
|
204
|
+
- spec/belongs_to_many_spec.rb
|
|
205
|
+
- spec/has_many_spec.rb
|
|
175
206
|
- spec/spec_helper.rb
|