active_record-json_associations 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67f879bede0474e8a73e798f7f4facf1370f2d98
4
+ data.tar.gz: 17f1a8d6d4654a93fb2bdeb373b1a44e3d9bf21c
5
+ SHA512:
6
+ metadata.gz: 2830b22a574bcc27a058ec51f0a7d39f9429c5211ca42b5373edd3b6cc9e10eaa986b9c0d5279275b0624bcd97c9efc5a75ef8f96e92224ed66f3bea7a137a27
7
+ data.tar.gz: abf5a10b7af2e76d2d5ff0e2edd3357d3e22adee309291262630fb8801ab0b7a60b7163680cdc20e48a111dc730e8ef5769b7250ca4223ae7c2cf50ab214f95f
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .ruby-version
7
+ .ruby-gemset
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
20
+ *.bundle
21
+ *.so
22
+ *.o
23
+ *.a
24
+ mkmf.log
25
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - 2.3.3
5
+ - 2.4.0
6
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_record-json_associations.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Micah Geisel
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,63 @@
1
+ # ActiveRecord::JsonAssociations
2
+
3
+ [![Build Status](https://travis-ci.org/botandrose/active_record-json_associations.svg)](https://travis-ci.org/botandrose/active_record-json_associations)
4
+ [![Code Climate](https://codeclimate.com/github/botandrose/active_record-json_associations/badges/gpa.svg)](https://codeclimate.com/github/botandrose/active_record-json_associations)
5
+
6
+ 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
+
8
+ ## Usage
9
+
10
+ ```ruby
11
+ require "active_record/json_associations"
12
+
13
+ ActiveRecord::Schema.define do
14
+ create_table :parents do |t|
15
+ t.text :child_ids
16
+ end
17
+
18
+ create_table :children
19
+ end
20
+
21
+ class Parent < ActiveRecord::Base
22
+ belongs_to_many :children
23
+ end
24
+ ```
25
+
26
+ This will add some familiar `has_many`-style methods:
27
+
28
+ ```ruby
29
+ parent.children? #=> false
30
+
31
+ parent.children = [Child.create!, Child.create!, Child.create!]
32
+ parent.children #=> [#<Child id: 1>, #<Child id: 2>, #<Child id: 3>]
33
+
34
+ parent.child_ids = [1,2]
35
+ parent.child_ids #=> [1,2]
36
+
37
+ parent.children? #=> true
38
+ ```
39
+
40
+ It also adds an `json_foreign_key` option to `has_many` for specifying that the foreign keys are in a json array.
41
+
42
+ ```ruby
43
+ class Child
44
+ has_many :parents, json_foreign_key: true # infers :child_ids, but can be overridden
45
+ end
46
+
47
+ child = Child.create!
48
+ parent = Parent.create children: [child]
49
+ child.parents == [parent] #=> true
50
+ ```
51
+
52
+ ## Requirements
53
+
54
+ * ActiveRecord 5.0+
55
+
56
+ ## Contributing
57
+
58
+ 1. Fork it ( https://github.com/botandrose/active_record-json_associations/fork )
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create a new Pull Request
63
+
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_record/json_associations/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "active_record-json_associations"
8
+ spec.version = ActiveRecord::JsonAssociations::VERSION
9
+ spec.authors = ["Micah Geisel"]
10
+ spec.email = ["micah@botandrose.com"]
11
+ spec.summary = %q{Instead of a many-to-many join table, serialize the ids into a JSON array.}
12
+ spec.description = %q{Instead of a many-to-many join table, serialize the ids into a JSON array.}
13
+ spec.homepage = "https://github.com/botandrose/active_record-json_associations"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "sqlite3"
27
+ spec.add_development_dependency "byebug"
28
+ end
29
+
@@ -0,0 +1,89 @@
1
+ require "active_record"
2
+ require "json"
3
+
4
+ module ActiveRecord
5
+ module JsonAssociations
6
+ def belongs_to_many(many, class_name: nil)
7
+ one = many.to_s.singularize
8
+ one_ids = :"#{one}_ids"
9
+ one_ids_equals = :"#{one_ids}="
10
+ class_name ||= one.classify
11
+ many_equals = :"#{many}="
12
+ many_eh = :"#{many}?"
13
+
14
+ serialize one_ids, JSON
15
+
16
+ include Module.new {
17
+ define_method one_ids do
18
+ super() || []
19
+ end
20
+
21
+ define_method many do
22
+ class_name.constantize.where(id: send(one_ids))
23
+ end
24
+
25
+ define_method many_equals do |collection|
26
+ send one_ids_equals, collection.map(&:id)
27
+ end
28
+
29
+ define_method many_eh do
30
+ send(one_ids).any?
31
+ end
32
+ }
33
+ end
34
+
35
+ def has_many many, scope = nil, options = {}, &extension
36
+ unless scope.try(:[], :json_foreign_key) || options.try(:[], :json_foreign_key)
37
+ return super
38
+ end
39
+
40
+ if scope.is_a?(Hash)
41
+ options = scope
42
+ scope = nil
43
+ end
44
+
45
+ one = many.to_s.singularize
46
+ class_name ||= one.classify
47
+ klass = class_name.constantize
48
+
49
+ one_ids = :"#{one}_ids"
50
+ one_ids_equals = :"#{one_ids}="
51
+ many_equals = :"#{many}="
52
+ many_eh = :"#{many}?"
53
+
54
+ foreign_key = options[:json_foreign_key]
55
+ foreign_key = :"#{model_name.singular}_ids" if foreign_key == true
56
+
57
+ include Module.new {
58
+ define_method one_ids do
59
+ send(many).pluck(:id)
60
+ end
61
+
62
+ define_method one_ids_equals do |ids|
63
+ send many_equals, klass.find(ids)
64
+ end
65
+
66
+ define_method many do
67
+ klass.where("#{foreign_key} LIKE '[#{id}]'").or(
68
+ klass.where("#{foreign_key} LIKE '[#{id},%'")).or(
69
+ klass.where("#{foreign_key} LIKE '%,#{id},%'")).or(
70
+ klass.where("#{foreign_key} LIKE '%,#{id}]'"))
71
+ end
72
+
73
+ define_method many_equals do |collection|
74
+ collection.each do |record|
75
+ new_id_array = Array(record.send(foreign_key)) | [id]
76
+ record.update foreign_key => new_id_array
77
+ end
78
+ end
79
+
80
+ define_method many_eh do
81
+ send(many).any?
82
+ end
83
+ }
84
+ end
85
+ end
86
+
87
+ Base.extend JsonAssociations
88
+ end
89
+
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module JsonAssociations
3
+ VERSION = "0.6.0"
4
+ end
5
+ end
@@ -0,0 +1,229 @@
1
+ require "active_record/json_associations"
2
+
3
+ describe ActiveRecord::JsonAssociations do
4
+ before do
5
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
6
+
7
+ silence_stream(STDOUT) do
8
+ ActiveRecord::Schema.define do
9
+ create_table :parents do |t|
10
+ t.text :child_ids
11
+ t.text :fuzzy_ids
12
+ end
13
+
14
+ create_table :children
15
+
16
+ create_table :pets
17
+ end
18
+ end
19
+
20
+ class Parent < ActiveRecord::Base
21
+ belongs_to_many :children
22
+ belongs_to_many :fuzzies, class_name: "Pet"
23
+ end
24
+
25
+ class Child < ActiveRecord::Base
26
+ has_many :parents, json_foreign_key: true
27
+ end
28
+
29
+ class Pet < ActiveRecord::Base
30
+ has_many :parents, json_foreign_key: :fuzzy_ids
31
+ end
32
+ end
33
+
34
+ describe ".belongs_to_many :children" do
35
+ subject { Parent.new }
36
+
37
+ describe "#child_ids" do
38
+ it "is empty by default" do
39
+ expect(subject.child_ids).to eq []
40
+ end
41
+
42
+ it "is an accessor" do
43
+ subject.child_ids = [1,2,3]
44
+ expect(subject.child_ids).to eq [1,2,3]
45
+ end
46
+ end
47
+
48
+ describe "#children" do
49
+ let(:children) { [Child.create!, Child.create!, Child.create!] }
50
+
51
+ it "returns an empty array when there are no children" do
52
+ expect(subject.children).to eq []
53
+ end
54
+
55
+ it "finds the children by id" do
56
+ subject.child_ids = [1,2,3]
57
+ expect(subject.children).to eq children
58
+ end
59
+
60
+ it "is an accessor" do
61
+ subject.children = children
62
+ expect(subject.children).to eq children
63
+ end
64
+ end
65
+
66
+ describe "#children?" do
67
+ let(:children) { [Child.create!, Child.create!, Child.create!] }
68
+
69
+ it "returns false when there are no children" do
70
+ expect(subject.children?).to be_falsey
71
+ end
72
+
73
+ it "returns true when there are children" do
74
+ subject.children = children
75
+ expect(subject.children?).to be_truthy
76
+ end
77
+ end
78
+
79
+ context "when overriding class name" do
80
+ let(:pets) { [Pet.create!, Pet.create!, Pet.create!] }
81
+
82
+ it "returns an empty array when there are no children" do
83
+ expect(subject.fuzzies).to eq []
84
+ end
85
+
86
+ it "finds the children by id" do
87
+ subject.fuzzy_ids = [1,2,3]
88
+ expect(subject.fuzzies).to eq pets
89
+ end
90
+ end
91
+ end
92
+
93
+ describe ".has_many :parents, json_foreign_key: true" do
94
+ subject { Child.create! }
95
+
96
+ let(:parents) { [Parent.create!, Parent.create!, Parent.create!] }
97
+
98
+ describe "#parent_ids" do
99
+ it "is empty by default" do
100
+ expect(subject.parent_ids).to eq []
101
+ end
102
+
103
+ it "is an accessor" do
104
+ subject.parent_ids = parents.map(&:id)
105
+ expect(subject.parent_ids).to eq parents.map(&:id)
106
+ end
107
+ end
108
+
109
+ describe "#parents" do
110
+ it "returns an empty array when there are no parents" do
111
+ expect(subject.parents).to eq []
112
+ end
113
+
114
+ it "finds the children by id" do
115
+ subject.parent_ids = parents.map(&:id)
116
+ expect(subject.parents).to eq parents
117
+ end
118
+
119
+ it "is an accessor" do
120
+ subject.parents = parents
121
+ expect(subject.parents).to eq parents
122
+ end
123
+
124
+ context "finds records with the specified id" do
125
+ let(:child) { Child.create! }
126
+
127
+ it "as the whole json array" do
128
+ parent = Parent.create(children: [child])
129
+ expect(child.parents).to eq [parent]
130
+ end
131
+
132
+ it "at the beginning of the json array" do
133
+ parent = Parent.create(children: [child, Child.create!])
134
+ expect(child.parents).to eq [parent]
135
+ end
136
+
137
+ it "in the middle of the json array" do
138
+ parent = Parent.create(children: [Child.create!, child, Child.create!])
139
+ expect(child.parents).to eq [parent]
140
+ end
141
+
142
+ it "at the end of the json array" do
143
+ parent = Parent.create(children: [Child.create!, child])
144
+ expect(child.parents).to eq [parent]
145
+ end
146
+ end
147
+ end
148
+
149
+ describe "#parents?" do
150
+ it "returns false when there are no parents" do
151
+ expect(subject.parents?).to be_falsey
152
+ end
153
+
154
+ it "returns true when there are parents" do
155
+ subject.parents = parents
156
+ expect(subject.parents?).to be_truthy
157
+ end
158
+ end
159
+ end
160
+
161
+ describe ".has_many :parents, json_foreign_key: :fuzzy_ids" do
162
+ subject { Pet.create! }
163
+
164
+ let(:parents) { [Parent.create!, Parent.create!, Parent.create!] }
165
+
166
+ describe "#parent_ids" do
167
+ it "is empty by default" do
168
+ expect(subject.parent_ids).to eq []
169
+ end
170
+
171
+ it "is an accessor" do
172
+ subject.parent_ids = parents.map(&:id)
173
+ expect(subject.parent_ids).to eq parents.map(&:id)
174
+ end
175
+ end
176
+
177
+ describe "#parents" do
178
+ it "returns an empty array when there are no parents" do
179
+ expect(subject.parents).to eq []
180
+ end
181
+
182
+ it "finds the parents by id" do
183
+ subject.parent_ids = parents.map(&:id)
184
+ expect(subject.parents).to eq parents
185
+ end
186
+
187
+ it "is an accessor" do
188
+ subject.parents = parents
189
+ expect(subject.parents).to eq parents
190
+ end
191
+
192
+ context "finds records with the specified id" do
193
+ let(:pet) { Pet.create! }
194
+
195
+ it "as the whole json array" do
196
+ parent = Parent.create(fuzzies: [pet])
197
+ expect(pet.parents).to eq [parent]
198
+ end
199
+
200
+ it "at the beginning of the json array" do
201
+ parent = Parent.create(fuzzies: [pet, Pet.create!])
202
+ expect(pet.parents).to eq [parent]
203
+ end
204
+
205
+ it "in the middle of the json array" do
206
+ parent = Parent.create(fuzzies: [Pet.create!, pet, Pet.create!])
207
+ expect(pet.parents).to eq [parent]
208
+ end
209
+
210
+ it "at the end of the json array" do
211
+ parent = Parent.create(fuzzies: [Pet.create!, pet])
212
+ expect(pet.parents).to eq [parent]
213
+ end
214
+ end
215
+ end
216
+
217
+ describe "#parents?" do
218
+ it "returns false when there are no parents" do
219
+ expect(subject.parents?).to be_falsey
220
+ end
221
+
222
+ it "returns true when there are parents" do
223
+ subject.parents = parents
224
+ expect(subject.parents?).to be_truthy
225
+ end
226
+ end
227
+ end
228
+ end
229
+
@@ -0,0 +1,17 @@
1
+ require "byebug"
2
+
3
+ RSpec.configure do |config|
4
+ config.filter_run focus: true
5
+ config.run_all_when_everything_filtered = true
6
+ end
7
+
8
+ def silence_stream(stream)
9
+ old_stream = stream.dup
10
+ stream.reopen "/dev/null"
11
+ stream.sync = true
12
+ yield
13
+ ensure
14
+ stream.reopen(old_stream)
15
+ old_stream.close
16
+ end
17
+
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_record-json_associations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Micah Geisel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-16 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: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Instead of a many-to-many join table, serialize the ids into a JSON array.
98
+ email:
99
+ - micah@botandrose.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - active_record-json_associations.gemspec
112
+ - lib/active_record/json_associations.rb
113
+ - lib/active_record/json_associations/version.rb
114
+ - spec/json_associations_spec.rb
115
+ - spec/spec_helper.rb
116
+ homepage: https://github.com/botandrose/active_record-json_associations
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.4.8
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Instead of a many-to-many join table, serialize the ids into a JSON array.
140
+ test_files:
141
+ - spec/json_associations_spec.rb
142
+ - spec/spec_helper.rb