goldiloader 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/Rakefile +9 -0
- data/goldiloader.gemspec +37 -0
- data/lib/goldiloader.rb +9 -0
- data/lib/goldiloader/active_record_patches.rb +128 -0
- data/lib/goldiloader/association_loader.rb +35 -0
- data/lib/goldiloader/association_options.rb +37 -0
- data/lib/goldiloader/auto_include_context.rb +19 -0
- data/lib/goldiloader/model_registry.rb +26 -0
- data/lib/goldiloader/version.rb +5 -0
- data/spec/db/database.yml +3 -0
- data/spec/db/schema.rb +107 -0
- data/spec/goldiloader/goldiloader_spec.rb +399 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/compatibility.rb +14 -0
- metadata +193 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1cd22c993cb7fd5659c5371a85deb6c89ada5deb
|
4
|
+
data.tar.gz: 9a3ecaff4ef43be8ee6f376613e896cb9875be59
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1b8c078b155765e688e60c6471df92bff1a915812f9dada3ed8def41b65fc6783528adc4cb5890ae41f661baa3aec63189f68212ed52c7d66eaea33de991a2d7
|
7
|
+
data.tar.gz: dabb87bd0a802bca323364851e78e210a43e1e4c9139a4b6faf9908912a57c86f43c33622cc1ae694132dba1bed6b4f9e67214d23d65ce0a0fcc5d08ea266dea
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
language: ruby
|
2
|
+
env:
|
3
|
+
matrix:
|
4
|
+
- RAILS_VERSION="~> 3.2.18" JRUBY_OPTS="$JRUBY_OPTS --debug"
|
5
|
+
- RAILS_VERSION="~> 4.0.5" JRUBY_OPTS="$JRUBY_OPTS --debug"
|
6
|
+
- RAILS_VERSION="~> 4.1.1" JRUBY_OPTS="$JRUBY_OPTS --debug"
|
7
|
+
rvm:
|
8
|
+
- 1.9.3
|
9
|
+
- 2.0.0
|
10
|
+
- 2.1.0
|
11
|
+
- jruby-19mode
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Joel Turkel
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# Goldiloader
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/goldiloader.png)][gem]
|
4
|
+
[![Build Status](https://secure.travis-ci.org/salsify/goldiloader.png?branch=master)][travis]
|
5
|
+
[![Code Climate](https://codeclimate.com/github/salsify/goldiloader.png)][codeclimate]
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/salsify/goldiloader/badge.png)][coveralls]
|
7
|
+
|
8
|
+
[gem]: https://rubygems.org/gems/goldiloader
|
9
|
+
[travis]: http://travis-ci.org/salsify/goldiloader
|
10
|
+
[codeclimate]: https://codeclimate.com/github/salsify/goldiloader
|
11
|
+
[coveralls]: https://coveralls.io/r/salsify/goldiloader
|
12
|
+
|
13
|
+
Wouldn't it be awesome if ActiveRecord didn't make you think about eager loading and it just did the "right" thing by default? With Goldiloader it can!
|
14
|
+
|
15
|
+
Consider the following models:
|
16
|
+
|
17
|
+
```
|
18
|
+
class Blog < ActiveRecord::Base
|
19
|
+
has_many :posts
|
20
|
+
end
|
21
|
+
|
22
|
+
class Post < ActiveRecord::Base
|
23
|
+
belongs_to :blog
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Here are some sample queries without the Goldiloader:
|
28
|
+
|
29
|
+
```
|
30
|
+
> blogs = Blogs.limit(5).to_a
|
31
|
+
# SELECT * FROM blogs LIMIT 5
|
32
|
+
|
33
|
+
> blogs.each { |blog| blog.posts.to_a }
|
34
|
+
# SELECT * FROM posts WHERE blog_id = 1
|
35
|
+
# SELECT * FROM posts WHERE blog_id = 2
|
36
|
+
# SELECT * FROM posts WHERE blog_id = 3
|
37
|
+
# SELECT * FROM posts WHERE blog_id = 4
|
38
|
+
# SELECT * FROM posts WHERE blog_id = 5
|
39
|
+
```
|
40
|
+
|
41
|
+
Here are the same queries with the Goldiloader:
|
42
|
+
|
43
|
+
```
|
44
|
+
> blogs = Blogs.limit(5).to_a
|
45
|
+
# SELECT * FROM blogs LIMIT 5
|
46
|
+
|
47
|
+
> blogs.each { |blog| blog.posts.to_a }
|
48
|
+
# SELECT * FROM posts WHERE blog_id IN (1,2,3,4,5)
|
49
|
+
```
|
50
|
+
|
51
|
+
Whoa! It automatically loaded all of the posts for our five blogs in a single database query without specifying any eager loads! Goldiloader assumes that you'll access all models loaded from a query in a uniform way. The first time you traverse an association on any of the models it will eager load the association for all the models. It even works with arbitrary nesting of associations.
|
52
|
+
|
53
|
+
Read more about the motivation for the Goliloader in this [blog post](http://www.salsify.com/blog/automatic-eager-loading-rails/1869).
|
54
|
+
|
55
|
+
## Installation
|
56
|
+
|
57
|
+
Add this line to your application's Gemfile:
|
58
|
+
|
59
|
+
gem 'goldiloader'
|
60
|
+
|
61
|
+
And then execute:
|
62
|
+
|
63
|
+
$ bundle
|
64
|
+
|
65
|
+
Or install it yourself as:
|
66
|
+
|
67
|
+
$ gem install goldiloader
|
68
|
+
|
69
|
+
## Usage
|
70
|
+
|
71
|
+
By default all associations will be automatically eager loaded when they are first accessed so hopefully most use cases should require no additional configuration.
|
72
|
+
|
73
|
+
### Association Options
|
74
|
+
|
75
|
+
Goldiloader supports a few options on ActiveRecord associations to customize its behavior.
|
76
|
+
|
77
|
+
#### auto_include
|
78
|
+
|
79
|
+
You can disable automatic eager loading on specific associations with the `auto_include` option:
|
80
|
+
|
81
|
+
```
|
82
|
+
class Blog < ActiveRecord::Base
|
83
|
+
has_many :posts, auto_include: false
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
#### fully_load
|
88
|
+
|
89
|
+
There are several association methods that ActiveRecord can either execute on in memory models or push down into SQL depending on whether or not the association is loaded. This includes the following methods:
|
90
|
+
|
91
|
+
* `first`
|
92
|
+
* `second`
|
93
|
+
* `third`
|
94
|
+
* `fourth`
|
95
|
+
* `fifth`
|
96
|
+
* `forty_two` (one of the hidden gems in Rails 4.1)
|
97
|
+
* `last`
|
98
|
+
* `size`
|
99
|
+
* `ids_reader`
|
100
|
+
* `empty?`
|
101
|
+
* `exists?`
|
102
|
+
|
103
|
+
This can cause problems for certain usage patterns if we're no longer specifying eager loads:
|
104
|
+
|
105
|
+
```
|
106
|
+
> blogs = Blogs.limit(5).to_a
|
107
|
+
# SELECT * FROM blogs LIMIT 5
|
108
|
+
|
109
|
+
> blogs.each do |blog|
|
110
|
+
if blog.posts.exists?
|
111
|
+
puts blog.posts
|
112
|
+
else
|
113
|
+
puts 'No posts'
|
114
|
+
end
|
115
|
+
# SELECT 1 AS one FROM posts WHERE blog_id = 1 LIMIT 1
|
116
|
+
# SELECT * FROM posts WHERE blog_id IN (1,2,3,4,5)
|
117
|
+
```
|
118
|
+
|
119
|
+
Notice the first call to `blog.posts.exists?` was executed via SQL because the `posts` association wasn't yet loaded. The `fully_load` option can be used to force ActiveRecord to fully load the association (and do any necessary automatic eager loading) when evaluating methods like `exists?`:
|
120
|
+
|
121
|
+
```
|
122
|
+
class Blog < ActiveRecord::Base
|
123
|
+
has_many :posts, fully_load: true
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
## Status
|
128
|
+
|
129
|
+
This gem is tested with Rails 3.2, 4.0, and 4.1 using MRI 1.9.3, 2.0.0, 2.1.0 and JRuby in 1.9 mode. [Salsify](http://salsify.com) is not yet using this gem in production so proceed with caution. Let us know if you find any issues or have any other feedback.
|
130
|
+
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
1. Fork it
|
134
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
135
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
136
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
137
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/goldiloader.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'goldiloader/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'goldiloader'
|
8
|
+
spec.version = Goldiloader::VERSION
|
9
|
+
spec.authors = ['Joel Turkel']
|
10
|
+
spec.email = ['jturkel@salsify.com']
|
11
|
+
spec.description = %q{Automatically eager loads Rails associations as associations are traversed}
|
12
|
+
spec.summary = %q{Automatic Rails association eager loading}
|
13
|
+
spec.homepage = 'https://github.com/salsify/goldiloader'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.test_files = Dir.glob('spec/**/*')
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_dependency 'activerecord', ENV.fetch('RAILS_VERSION', ['>= 3.2', '<= 4.1'])
|
21
|
+
spec.add_dependency 'activesupport', ENV.fetch('RAILS_VERSION', ['>= 3.2', '<= 4.1'])
|
22
|
+
|
23
|
+
spec.add_development_dependency 'coveralls'
|
24
|
+
spec.add_development_dependency 'database_cleaner', '>= 1.2'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 2'
|
27
|
+
spec.add_development_dependency 'simplecov', '~> 0.7.1'
|
28
|
+
|
29
|
+
if RUBY_PLATFORM == 'java'
|
30
|
+
spec.add_development_dependency 'jdbc-sqlite3'
|
31
|
+
spec.add_development_dependency 'activerecord-jdbcsqlite3-adapter'
|
32
|
+
else
|
33
|
+
spec.add_development_dependency 'sqlite3'
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
data/lib/goldiloader.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'active_record'
|
5
|
+
require 'goldiloader/auto_include_context'
|
6
|
+
require 'goldiloader/association_options'
|
7
|
+
require 'goldiloader/association_loader'
|
8
|
+
require 'goldiloader/model_registry'
|
9
|
+
require 'goldiloader/active_record_patches'
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Goldiloader
|
4
|
+
module AutoIncludableModel
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
attr_writer :auto_include_context
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_copy(other)
|
12
|
+
super
|
13
|
+
@auto_include_context = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def auto_include_context
|
17
|
+
@auto_include_context ||= Goldiloader::AutoIncludeContext.create_empty.register_model(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::Base.send(:include, Goldiloader::AutoIncludableModel)
|
23
|
+
|
24
|
+
ActiveRecord::Relation.class_eval do
|
25
|
+
|
26
|
+
def exec_queries_with_auto_include
|
27
|
+
return exec_queries_without_auto_include if loaded?
|
28
|
+
|
29
|
+
models = exec_queries_without_auto_include
|
30
|
+
# Add all loaded models to the same AutoIncludeContext
|
31
|
+
auto_include_context = Goldiloader::AutoIncludeContext.create_empty
|
32
|
+
auto_include_context.register_models(models)
|
33
|
+
models
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method_chain :exec_queries, :auto_include
|
37
|
+
end
|
38
|
+
|
39
|
+
ActiveRecord::Associations::Association.class_eval do
|
40
|
+
|
41
|
+
class_attribute :default_auto_include, :default_fully_load
|
42
|
+
self.default_auto_include = true
|
43
|
+
self.default_fully_load = false
|
44
|
+
|
45
|
+
def auto_include?
|
46
|
+
# We only auto include associations that don't have in-memory changes since the
|
47
|
+
# Rails association Preloader clobbers any in-memory changes
|
48
|
+
!loaded? && target.blank? && options.fetch(:auto_include) { self.class.default_auto_include }
|
49
|
+
end
|
50
|
+
|
51
|
+
def fully_load?
|
52
|
+
!loaded? && options.fetch(:fully_load) { self.class.default_fully_load }
|
53
|
+
end
|
54
|
+
|
55
|
+
def auto_include_context
|
56
|
+
@auto_include_context ||= Goldiloader::AutoIncludeContext.new(owner.auto_include_context.model_registry,
|
57
|
+
owner.auto_include_context.association_path + [reflection.name])
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def load_with_auto_include(load_method, *args)
|
63
|
+
if loaded?
|
64
|
+
target
|
65
|
+
elsif auto_include?
|
66
|
+
Goldiloader::AssociationLoader.load(auto_include_context.model_registry, owner,
|
67
|
+
auto_include_context.association_path)
|
68
|
+
target
|
69
|
+
else
|
70
|
+
send("#{load_method}_without_auto_include", *args)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
ActiveRecord::Associations::SingularAssociation.class_eval do
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def find_target_with_auto_include(*args)
|
81
|
+
load_with_auto_include(:find_target, *args)
|
82
|
+
end
|
83
|
+
|
84
|
+
alias_method_chain :find_target, :auto_include
|
85
|
+
end
|
86
|
+
|
87
|
+
ActiveRecord::Associations::CollectionAssociation.class_eval do
|
88
|
+
# Force these methods to load the entire association for fully_load associations
|
89
|
+
[:first, :second, :third, :fourth, :fifth, :last, :size, :ids_reader, :empty?].each do |method|
|
90
|
+
# Some of these methods were added in Rails 4
|
91
|
+
next unless method_defined?(method)
|
92
|
+
|
93
|
+
aliased_target, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
|
94
|
+
define_method("#{aliased_target}_with_fully_load#{punctuation}") do |*args, &block|
|
95
|
+
load_target if fully_load?
|
96
|
+
send("#{aliased_target}_without_fully_load#{punctuation}", *args, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
alias_method_chain method, :fully_load
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def load_target_with_auto_include(*args)
|
105
|
+
load_with_auto_include(:load_target, *args)
|
106
|
+
end
|
107
|
+
|
108
|
+
alias_method_chain :load_target, :auto_include
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
[ActiveRecord::Associations::HasManyThroughAssociation, ActiveRecord::Associations::HasOneThroughAssociation].each do |klass|
|
113
|
+
klass.class_eval do
|
114
|
+
def auto_include?
|
115
|
+
# Only auto include through associations if the target association is auto-loadable
|
116
|
+
through_association = owner.association(through_reflection.name)
|
117
|
+
through_association.auto_include? && super
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# The CollectionProxy just forwards exists? to the underlying scope so we need to intercept this and
|
123
|
+
# force it to use size which handles fully_load properly.
|
124
|
+
ActiveRecord::Associations::CollectionProxy.class_eval do
|
125
|
+
def exists?
|
126
|
+
@association.fully_load? ? size > 0 : super
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Goldiloader
|
4
|
+
module AssociationLoader
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def load(model_registry, model, association_path)
|
8
|
+
*model_path, association_name = *association_path
|
9
|
+
models = model_registry.peers(model, model_path).select do |model|
|
10
|
+
load?(model, association_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
if Gem::Version.new(::ActiveRecord::VERSION::STRING) >= Gem::Version.new('4.1')
|
14
|
+
::ActiveRecord::Associations::Preloader.new.preload(models, [association_name])
|
15
|
+
else
|
16
|
+
::ActiveRecord::Associations::Preloader.new(models, [association_name]).run
|
17
|
+
end
|
18
|
+
|
19
|
+
associated_models = models.map { |model| model.send(association_name) }.flatten.compact.uniq
|
20
|
+
auto_include_context = Goldiloader::AutoIncludeContext.new(model_registry, association_path)
|
21
|
+
auto_include_context.register_models(associated_models)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def load?(model, association_name)
|
27
|
+
# Need to make sure the model actually has the association which won't always
|
28
|
+
# be the case in STI hierarchies e.g. only a subclass might have the association
|
29
|
+
!model.destroyed? &&
|
30
|
+
model.class.reflect_on_association(association_name).present? &&
|
31
|
+
model.association(association_name).auto_include?
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Goldiloader
|
4
|
+
module AssociationOptions
|
5
|
+
extend self
|
6
|
+
|
7
|
+
OPTIONS = [:auto_include, :fully_load]
|
8
|
+
|
9
|
+
def register
|
10
|
+
if ::ActiveRecord::VERSION::MAJOR >= 4
|
11
|
+
ActiveRecord::Associations::Builder::Association.valid_options.concat(OPTIONS)
|
12
|
+
else
|
13
|
+
# Each subclass of CollectionAssociation will have its own copy of valid_options so we need
|
14
|
+
# to register the valid option for each one.
|
15
|
+
collection_association_classes.each do |assoc_class|
|
16
|
+
assoc_class.valid_options.concat(OPTIONS)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def collection_association_classes
|
24
|
+
# Association.descendants doesn't work well with lazy classloading :(
|
25
|
+
[
|
26
|
+
ActiveRecord::Associations::Builder::Association,
|
27
|
+
ActiveRecord::Associations::Builder::BelongsTo,
|
28
|
+
ActiveRecord::Associations::Builder::HasAndBelongsToMany,
|
29
|
+
ActiveRecord::Associations::Builder::HasMany,
|
30
|
+
ActiveRecord::Associations::Builder::HasOne,
|
31
|
+
ActiveRecord::Associations::Builder::SingularAssociation
|
32
|
+
]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Goldiloader::AssociationOptions.register
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Goldiloader
|
4
|
+
class AutoIncludeContext < Struct.new(:model_registry, :association_path)
|
5
|
+
def self.create_empty
|
6
|
+
Goldiloader::AutoIncludeContext.new(Goldiloader::ModelRegistry.new, [])
|
7
|
+
end
|
8
|
+
|
9
|
+
def register_models(models)
|
10
|
+
Array.wrap(models).each do |model|
|
11
|
+
model.auto_include_context = self
|
12
|
+
model_registry.register(model, association_path)
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :register_model, :register_models
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Goldiloader
|
4
|
+
class ModelRegistry
|
5
|
+
def initialize
|
6
|
+
@registry = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(record, association_path)
|
10
|
+
key = registry_key(record, association_path)
|
11
|
+
@registry[key] ||= []
|
12
|
+
@registry[key] << record
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns all models with the same base class loaded from the same association path
|
16
|
+
def peers(record, association_path)
|
17
|
+
@registry.fetch(registry_key(record, association_path), [])
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def registry_key(record, association_path)
|
23
|
+
[record.class.base_class, association_path]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/db/schema.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define(:version => 0) do
|
4
|
+
|
5
|
+
create_table(:blogs, force: true) do |t|
|
6
|
+
t.string :name
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table(:posts, force: true) do |t|
|
10
|
+
t.string :title
|
11
|
+
t.integer :blog_id
|
12
|
+
t.integer :author_id
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table(:users, force: true) do |t|
|
16
|
+
t.string :name
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table(:addresses, force: true) do |t|
|
20
|
+
t.string :city
|
21
|
+
t.integer :user_id
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table(:groups, force: true) do |t|
|
25
|
+
t.string :name
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table(:tags, force: true) do |t|
|
29
|
+
t.string :name
|
30
|
+
t.integer :parent_id
|
31
|
+
t.string :owner_type
|
32
|
+
t.integer :owner_id
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table(:post_tags, force: true) do |t|
|
36
|
+
t.integer :post_id
|
37
|
+
t.integer :tag_id
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Tag < ActiveRecord::Base
|
42
|
+
belongs_to :parent, class_name: 'Tag'
|
43
|
+
has_many :children, class_name: 'Tag', foreign_key: :parent_id
|
44
|
+
|
45
|
+
belongs_to :owner, polymorphic: true
|
46
|
+
has_many :post_tags
|
47
|
+
has_many :posts, through: :post_tags
|
48
|
+
|
49
|
+
if Goldiloader::Compatibility.mass_assignment_security_enabled?
|
50
|
+
attr_accessible :name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class PostTag < ActiveRecord::Base
|
55
|
+
belongs_to :post
|
56
|
+
belongs_to :tag
|
57
|
+
end
|
58
|
+
|
59
|
+
class Blog < ActiveRecord::Base
|
60
|
+
has_many :posts
|
61
|
+
has_many :posts_without_auto_include, auto_include: false, class_name: 'Post'
|
62
|
+
has_many :posts_fully_load, fully_load: true, class_name: 'Post'
|
63
|
+
has_many :authors, through: :posts
|
64
|
+
|
65
|
+
if Goldiloader::Compatibility.mass_assignment_security_enabled?
|
66
|
+
attr_accessible :name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Post < ActiveRecord::Base
|
71
|
+
belongs_to :blog
|
72
|
+
belongs_to :blog_without_auto_include, auto_include: false, class_name: 'Blog', foreign_key: :blog_id
|
73
|
+
belongs_to :author, class_name: 'User'
|
74
|
+
has_many :post_tags
|
75
|
+
has_many :tags, through: :post_tags
|
76
|
+
|
77
|
+
if Goldiloader::Compatibility.mass_assignment_security_enabled?
|
78
|
+
attr_accessible :title
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class User < ActiveRecord::Base
|
83
|
+
has_many :posts, foreign_key: :author_id
|
84
|
+
has_many :tags, as: :owner
|
85
|
+
has_one :address
|
86
|
+
has_one :address_without_auto_include, auto_include: false, class_name: 'Address'
|
87
|
+
|
88
|
+
if Goldiloader::Compatibility.mass_assignment_security_enabled?
|
89
|
+
attr_accessible :name
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Address < ActiveRecord::Base
|
94
|
+
belongs_to :user
|
95
|
+
|
96
|
+
if Goldiloader::Compatibility.mass_assignment_security_enabled?
|
97
|
+
attr_accessible :city
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Group < ActiveRecord::Base
|
102
|
+
has_many :tags, as: :owner
|
103
|
+
|
104
|
+
if Goldiloader::Compatibility.mass_assignment_security_enabled?
|
105
|
+
attr_accessible :name
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,399 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Goldiloader do
|
6
|
+
let!(:author1) do
|
7
|
+
User.create!(name: 'author1') { |u| u.address = Address.new(city: 'author1-city') }
|
8
|
+
end
|
9
|
+
|
10
|
+
let!(:author2) do
|
11
|
+
User.create!(name: 'author2') { |u| u.address = Address.new(city: 'author2-city') }
|
12
|
+
end
|
13
|
+
|
14
|
+
let!(:author3) do
|
15
|
+
User.create!(name: 'author3') { |u| u.address = Address.new(city: 'author3-city') }
|
16
|
+
end
|
17
|
+
|
18
|
+
let!(:group1) { Group.create!(name: 'group1') }
|
19
|
+
|
20
|
+
let!(:parent_tag1) { Tag.create!(name: 'parent1') { |t| t.owner = group1 } }
|
21
|
+
let!(:child_tag1) { parent_tag1.children.create!(name: 'parent1-child1') { |t| t.owner = author1 } }
|
22
|
+
let!(:child_tag2) { parent_tag1.children.create!(name: 'parent1-child2') { |t| t.owner = group1 } }
|
23
|
+
let!(:parent_tag2) { Tag.create!(name: 'parent2') { |t| t.owner = group1 } }
|
24
|
+
let!(:child_tag3) { parent_tag2.children.create!(name: 'parent2-child1') { |t| t.owner = author2 } }
|
25
|
+
|
26
|
+
let!(:blog1) do
|
27
|
+
blog1 = Blog.create!(name: 'blog1')
|
28
|
+
|
29
|
+
blog1.posts.create!(title: 'blog1-post1') do |post|
|
30
|
+
post.author = author1
|
31
|
+
post.tags << child_tag1 << child_tag2
|
32
|
+
end
|
33
|
+
|
34
|
+
blog1.posts.create!(title: 'blog1-post2') do |post|
|
35
|
+
post.author = author2
|
36
|
+
post.tags << child_tag1
|
37
|
+
end
|
38
|
+
|
39
|
+
blog1
|
40
|
+
end
|
41
|
+
|
42
|
+
let!(:blog2) do
|
43
|
+
blog2 = Blog.create!(name: 'blog2')
|
44
|
+
|
45
|
+
blog2.posts.create!(title: 'blog2-post1') do |post|
|
46
|
+
post.author = author3
|
47
|
+
post.tags << child_tag1
|
48
|
+
end
|
49
|
+
|
50
|
+
blog2.posts.create!(title: 'blog2-post2') do |post|
|
51
|
+
post.author = author1
|
52
|
+
post.tags << child_tag3
|
53
|
+
end
|
54
|
+
|
55
|
+
blog2
|
56
|
+
end
|
57
|
+
|
58
|
+
before do
|
59
|
+
[Address, Blog, Post, Tag, User, Group].each do |klass|
|
60
|
+
allow(klass).to receive(:find_by_sql).and_call_original
|
61
|
+
end
|
62
|
+
|
63
|
+
ActiveRecord::Base.logger.info('Test setup complete')
|
64
|
+
end
|
65
|
+
|
66
|
+
it "auto eager loads has_many associations" do
|
67
|
+
blogs = Blog.order(:name).to_a
|
68
|
+
|
69
|
+
# Sanity check that associations aren't loaded yet
|
70
|
+
blogs.each do |blog|
|
71
|
+
expect(blog.association(:posts)).to_not be_loaded
|
72
|
+
end
|
73
|
+
|
74
|
+
# Force the first blogs first post to load
|
75
|
+
blogs.first.posts.to_a
|
76
|
+
|
77
|
+
blogs.each do |blog|
|
78
|
+
expect(blog.association(:posts)).to be_loaded
|
79
|
+
end
|
80
|
+
|
81
|
+
expect(blogs.first.posts.map(&:title)).to match_array(['blog1-post1', 'blog1-post2'])
|
82
|
+
expect(blogs.second.posts.map(&:title)).to match_array(['blog2-post1', 'blog2-post2'])
|
83
|
+
|
84
|
+
expect(Post).to have_received(:find_by_sql).once
|
85
|
+
end
|
86
|
+
|
87
|
+
it "auto eager loads belongs_to associations" do
|
88
|
+
posts = Post.order(:title).to_a
|
89
|
+
|
90
|
+
# Sanity check that associations aren't loaded yet
|
91
|
+
posts.each do |blog|
|
92
|
+
expect(blog.association(:blog)).to_not be_loaded
|
93
|
+
end
|
94
|
+
|
95
|
+
# Force the first post's blog to load
|
96
|
+
posts.first.blog
|
97
|
+
|
98
|
+
posts.each do |blog|
|
99
|
+
expect(blog.association(:blog)).to be_loaded
|
100
|
+
end
|
101
|
+
|
102
|
+
expect(posts.map(&:blog).map(&:name)).to eq(['blog1', 'blog1', 'blog2', 'blog2'])
|
103
|
+
expect(Blog).to have_received(:find_by_sql).once
|
104
|
+
end
|
105
|
+
|
106
|
+
it "auto eager loads has_one associations" do
|
107
|
+
users = User.order(:name).to_a
|
108
|
+
|
109
|
+
# Sanity check that associations aren't loaded yet
|
110
|
+
users.each do |user|
|
111
|
+
expect(user.association(:address)).to_not be_loaded
|
112
|
+
end
|
113
|
+
|
114
|
+
# Force the first user's address to load
|
115
|
+
users.first.address
|
116
|
+
|
117
|
+
users.each do |blog|
|
118
|
+
expect(blog.association(:address)).to be_loaded
|
119
|
+
end
|
120
|
+
|
121
|
+
expect(users.map(&:address).map(&:city)).to match_array(['author1-city', 'author2-city', 'author3-city'])
|
122
|
+
expect(Address).to have_received(:find_by_sql).once
|
123
|
+
end
|
124
|
+
|
125
|
+
it "auto eager loads nested associations" do
|
126
|
+
blogs = Blog.order(:name).to_a
|
127
|
+
blogs.first.posts.to_a.first.author
|
128
|
+
|
129
|
+
blogs.flat_map(&:posts).each do |blog|
|
130
|
+
expect(blog.association(:author)).to be_loaded
|
131
|
+
end
|
132
|
+
|
133
|
+
expect(blogs.first.posts.first.author).to eq author1
|
134
|
+
expect(blogs.first.posts.second.author).to eq author2
|
135
|
+
expect(blogs.second.posts.first.author).to eq author3
|
136
|
+
expect(blogs.second.posts.second.author).to eq author1
|
137
|
+
expect(Post).to have_received(:find_by_sql).once
|
138
|
+
end
|
139
|
+
|
140
|
+
it "auto eager loads has_many through associations" do
|
141
|
+
blogs = Blog.order(:name).to_a
|
142
|
+
blogs.first.authors.to_a
|
143
|
+
|
144
|
+
blogs.each do |blog|
|
145
|
+
expect(blog.association(:authors)).to be_loaded
|
146
|
+
end
|
147
|
+
|
148
|
+
expect(blogs.first.authors).to match_array([author1, author2])
|
149
|
+
expect(blogs.second.authors).to match_array([author3, author1])
|
150
|
+
expect(User).to have_received(:find_by_sql).once
|
151
|
+
end
|
152
|
+
|
153
|
+
it "auto eager loads associations when the model is loaded via find" do
|
154
|
+
blog = Blog.find(blog1.id)
|
155
|
+
blog.posts.to_a.first.author
|
156
|
+
|
157
|
+
blog.posts.each do |blog|
|
158
|
+
expect(blog.association(:author)).to be_loaded
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it "auto eager loads polymorphic associations" do
|
163
|
+
tags = Tag.where('parent_id IS NOT NULL').order(:name).to_a
|
164
|
+
tags.first.owner
|
165
|
+
|
166
|
+
tags.each do |tag|
|
167
|
+
expect(tag.association(:owner)).to be_loaded
|
168
|
+
end
|
169
|
+
|
170
|
+
expect(tags.first.owner).to eq author1
|
171
|
+
expect(tags.second.owner).to eq group1
|
172
|
+
expect(tags.third.owner).to eq author2
|
173
|
+
end
|
174
|
+
|
175
|
+
it "auto eager loads associations of polymorphic associations" do
|
176
|
+
tags = Tag.where('parent_id IS NOT NULL').order(:name).to_a
|
177
|
+
users = tags.map(&:owner).select {|owner| owner.is_a?(User) }.sort_by(&:name)
|
178
|
+
users.first.posts.to_a
|
179
|
+
|
180
|
+
users.each do |user|
|
181
|
+
expect(user.association(:posts)).to be_loaded
|
182
|
+
end
|
183
|
+
|
184
|
+
expect(users.first.posts).to eq Post.where(author_id: author1.id)
|
185
|
+
expect(users.second.posts).to eq Post.where(author_id: author2.id)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "only auto eager loads associations loaded through the same path" do
|
189
|
+
root_tags = Tag.where(parent_id: nil).order(:name).to_a
|
190
|
+
root_tags.first.children.to_a
|
191
|
+
|
192
|
+
# Make sure we loaded all child tags
|
193
|
+
root_tags.each do |tag|
|
194
|
+
expect(tag.association(:children)).to be_loaded
|
195
|
+
end
|
196
|
+
|
197
|
+
# Force a load of a root tag's owner
|
198
|
+
root_tags.first.owner
|
199
|
+
|
200
|
+
# All root tag owners should be loaded
|
201
|
+
root_tags.each do |tag|
|
202
|
+
expect(tag.association(:owner)).to be_loaded
|
203
|
+
end
|
204
|
+
|
205
|
+
# Child tag owners should not be loaded
|
206
|
+
child_tags = root_tags.flat_map(&:children)
|
207
|
+
child_tags.each do |tag|
|
208
|
+
expect(tag.association(:owner)).to_not be_loaded
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context "when a has_many association has in-memory changes" do
|
213
|
+
let!(:blogs) { Blog.order(:name).to_a }
|
214
|
+
let(:blog) { blogs.first }
|
215
|
+
let(:other_blog) { blogs.last }
|
216
|
+
|
217
|
+
before do
|
218
|
+
blog.posts.create(title: 'blog1-new-post')
|
219
|
+
end
|
220
|
+
|
221
|
+
it "returns the correct models for the modified has_many association" do
|
222
|
+
expect(blog.posts).to match_array Post.where(blog_id: blog.id)
|
223
|
+
end
|
224
|
+
|
225
|
+
it "doesn't auto eager load peers when accessing the modified has_many association" do
|
226
|
+
blog.posts.to_a
|
227
|
+
expect(other_blog.association(:posts)).to_not be_loaded
|
228
|
+
end
|
229
|
+
|
230
|
+
it "returns the correct models for the modified has_many association when accessing a peer" do
|
231
|
+
other_blog.posts.to_a
|
232
|
+
expect(blog.posts).to match_array Post.where(blog_id: blog.id)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context "when a has_many through association has in-memory changes" do
|
237
|
+
let!(:posts) { Post.order(:title).to_a }
|
238
|
+
let(:post) { posts.first }
|
239
|
+
let(:other_post) { posts.last }
|
240
|
+
|
241
|
+
before do
|
242
|
+
tag = Tag.create(name: 'new-tag')
|
243
|
+
post.post_tags.create(tag: tag)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "returns the correct models for the modified has_many through association" do
|
247
|
+
expect(post.tags).to match_array PostTag.where(post_id: post.id).includes(:tag).map(&:tag)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "doesn't auto eager load peers when accessing the modified has_many through association" do
|
251
|
+
post.tags.to_a
|
252
|
+
expect(other_post.association(:tags)).to_not be_loaded
|
253
|
+
end
|
254
|
+
|
255
|
+
it "returns the correct models for the modified has_many through association when accessing a peer" do
|
256
|
+
other_post.tags.to_a
|
257
|
+
expect(post.tags).to match_array PostTag.where(post_id: post.id).includes(:tag).map(&:tag)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
context "with fully_load false" do
|
262
|
+
|
263
|
+
it "doesn't auto eager loads a has_many association when size is called" do
|
264
|
+
blogs = Blog.order(:name).to_a
|
265
|
+
blogs.first.posts.size
|
266
|
+
|
267
|
+
blogs.each do |blog|
|
268
|
+
expect(blog.association(:posts)).to_not be_loaded
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
it "doesn't auto eager loads a has_many association when exists? is called" do
|
273
|
+
blogs = Blog.order(:name).to_a
|
274
|
+
blogs.first.posts.exists?
|
275
|
+
|
276
|
+
blogs.each do |blog|
|
277
|
+
expect(blog.association(:posts)).to_not be_loaded
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
it "doesn't auto eager loads a has_many association when last is called" do
|
282
|
+
blogs = Blog.order(:name).to_a
|
283
|
+
blogs.first.posts.last
|
284
|
+
|
285
|
+
blogs.each do |blog|
|
286
|
+
expect(blog.association(:posts)).to_not be_loaded
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
it "doesn't auto eager loads a has_many association when ids is called" do
|
291
|
+
blogs = Blog.order(:name).to_a
|
292
|
+
blogs.first.post_ids
|
293
|
+
|
294
|
+
blogs.each do |blog|
|
295
|
+
expect(blog.association(:posts)).to_not be_loaded
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context "with fully_load true" do
|
301
|
+
|
302
|
+
it "auto eager loads a has_many association when size is called" do
|
303
|
+
blogs = Blog.order(:name).to_a
|
304
|
+
blogs.first.posts_fully_load.size
|
305
|
+
|
306
|
+
blogs.each do |blog|
|
307
|
+
expect(blog.association(:posts_fully_load)).to be_loaded
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
it "auto eager loads a has_many association when exists? is called" do
|
312
|
+
blogs = Blog.order(:name).to_a
|
313
|
+
blogs.first.posts_fully_load.exists?
|
314
|
+
|
315
|
+
blogs.each do |blog|
|
316
|
+
expect(blog.association(:posts_fully_load)).to be_loaded
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
it "auto eager loads a has_many association when last is called" do
|
321
|
+
blogs = Blog.order(:name).to_a
|
322
|
+
blogs.first.posts_fully_load.last
|
323
|
+
|
324
|
+
blogs.each do |blog|
|
325
|
+
expect(blog.association(:posts_fully_load)).to be_loaded
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
it "auto eager loads a has_many association when ids is called" do
|
330
|
+
blogs = Blog.order(:name).to_a
|
331
|
+
blogs.first.posts_fully_load_ids
|
332
|
+
|
333
|
+
blogs.each do |blog|
|
334
|
+
expect(blog.association(:posts_fully_load)).to be_loaded
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
context "with auto_include disabled" do
|
341
|
+
|
342
|
+
it "doesn't auto eager load has_many associations" do
|
343
|
+
blogs = Blog.order(:name).to_a
|
344
|
+
|
345
|
+
# Force the first blogs first post to load
|
346
|
+
posts = blogs.first.posts_without_auto_include.to_a
|
347
|
+
expect(posts).to match_array Post.where(blog_id: blogs.first.id)
|
348
|
+
|
349
|
+
blogs.drop(1).each do |blog|
|
350
|
+
expect(blog.association(:posts_without_auto_include)).to_not be_loaded
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
it "doesn't auto eager load has_one associations" do
|
355
|
+
users = User.order(:name).to_a
|
356
|
+
|
357
|
+
# Force the first user's address to load
|
358
|
+
user = users.first
|
359
|
+
address = user.address_without_auto_include
|
360
|
+
expect(address).to eq Address.where(user_id: user.id).first
|
361
|
+
|
362
|
+
users.drop(1).each do |blog|
|
363
|
+
expect(blog.association(:address_without_auto_include)).to_not be_loaded
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
it "doesn't auto eager load belongs_to associations" do
|
368
|
+
posts = Post.order(:title).to_a
|
369
|
+
# Force the first post's blog to load
|
370
|
+
post = posts.first
|
371
|
+
blog = post.blog_without_auto_include
|
372
|
+
expect(blog).to eq Blog.where(id: post.blog_id).first
|
373
|
+
|
374
|
+
posts.drop(1).each do |blog|
|
375
|
+
expect(blog.association(:blog_without_auto_include)).to_not be_loaded
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
it "still auto eager loads nested associations" do
|
380
|
+
posts = Post.order(:title).to_a
|
381
|
+
# Force the first post's blog to load
|
382
|
+
blog = posts.first.blog_without_auto_include
|
383
|
+
|
384
|
+
# Load another blogs posts
|
385
|
+
other_blog = posts.last.blog_without_auto_include
|
386
|
+
other_blog.posts.to_a
|
387
|
+
|
388
|
+
blog.posts.to_a.first.tags.to_a
|
389
|
+
|
390
|
+
blog.posts.each do |post|
|
391
|
+
expect(post.association(:tags)).to be_loaded
|
392
|
+
end
|
393
|
+
|
394
|
+
other_blog.posts.each do |post|
|
395
|
+
expect(post.association(:tags)).to_not be_loaded
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
require 'coveralls'
|
5
|
+
|
6
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
7
|
+
SimpleCov::Formatter::HTMLFormatter,
|
8
|
+
Coveralls::SimpleCov::Formatter
|
9
|
+
]
|
10
|
+
SimpleCov.start do
|
11
|
+
add_filter 'spec'
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'logger'
|
15
|
+
require 'database_cleaner'
|
16
|
+
require 'goldiloader'
|
17
|
+
require 'yaml'
|
18
|
+
|
19
|
+
spec_dir = File.dirname(__FILE__)
|
20
|
+
Dir["#{spec_dir}/support/**/*.rb"].sort.each { |f| require f }
|
21
|
+
|
22
|
+
FileUtils.makedirs('log')
|
23
|
+
|
24
|
+
ActiveRecord::Base.logger = Logger.new('log/test.log')
|
25
|
+
ActiveRecord::Base.logger.level = Logger::DEBUG
|
26
|
+
ActiveRecord::Migration.verbose = false
|
27
|
+
|
28
|
+
db_adapter = ENV.fetch('ADAPTER', 'sqlite3')
|
29
|
+
config = YAML.load(File.read('spec/db/database.yml'))
|
30
|
+
ActiveRecord::Base.establish_connection(config[db_adapter])
|
31
|
+
require 'db/schema'
|
32
|
+
|
33
|
+
RSpec.configure do |config|
|
34
|
+
config.order = 'random'
|
35
|
+
|
36
|
+
config.before(:suite) do
|
37
|
+
DatabaseCleaner.clean_with(:truncation)
|
38
|
+
end
|
39
|
+
|
40
|
+
config.before(:each) do
|
41
|
+
DatabaseCleaner.strategy = :transaction
|
42
|
+
end
|
43
|
+
|
44
|
+
config.before(:each) do
|
45
|
+
DatabaseCleaner.start
|
46
|
+
end
|
47
|
+
|
48
|
+
config.after(:each) do
|
49
|
+
DatabaseCleaner.clean
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'active_support/version'
|
4
|
+
require 'active_record/version'
|
5
|
+
|
6
|
+
module Goldiloader
|
7
|
+
module Compatibility
|
8
|
+
|
9
|
+
def self.mass_assignment_security_enabled?
|
10
|
+
::ActiveRecord::VERSION::MAJOR < 4 || defined?(::ActiveRecord::MassAssignmentSecurity)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: goldiloader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joel Turkel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-17 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: '3.2'
|
20
|
+
- - <=
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '4.1'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- - <=
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '4.1'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activesupport
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.2'
|
40
|
+
- - <=
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '4.1'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.2'
|
50
|
+
- - <=
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '4.1'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: coveralls
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: database_cleaner
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '1.2'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '1.2'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rake
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rspec
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '2'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ~>
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '2'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: simplecov
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ~>
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: 0.7.1
|
116
|
+
type: :development
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ~>
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 0.7.1
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: sqlite3
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
type: :development
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
description: Automatically eager loads Rails associations as associations are traversed
|
138
|
+
email:
|
139
|
+
- jturkel@salsify.com
|
140
|
+
executables: []
|
141
|
+
extensions: []
|
142
|
+
extra_rdoc_files: []
|
143
|
+
files:
|
144
|
+
- .gitignore
|
145
|
+
- .rspec
|
146
|
+
- .travis.yml
|
147
|
+
- Gemfile
|
148
|
+
- LICENSE.txt
|
149
|
+
- README.md
|
150
|
+
- Rakefile
|
151
|
+
- goldiloader.gemspec
|
152
|
+
- lib/goldiloader.rb
|
153
|
+
- lib/goldiloader/active_record_patches.rb
|
154
|
+
- lib/goldiloader/association_loader.rb
|
155
|
+
- lib/goldiloader/association_options.rb
|
156
|
+
- lib/goldiloader/auto_include_context.rb
|
157
|
+
- lib/goldiloader/model_registry.rb
|
158
|
+
- lib/goldiloader/version.rb
|
159
|
+
- spec/db/database.yml
|
160
|
+
- spec/db/schema.rb
|
161
|
+
- spec/goldiloader/goldiloader_spec.rb
|
162
|
+
- spec/spec_helper.rb
|
163
|
+
- spec/support/compatibility.rb
|
164
|
+
homepage: https://github.com/salsify/goldiloader
|
165
|
+
licenses:
|
166
|
+
- MIT
|
167
|
+
metadata: {}
|
168
|
+
post_install_message:
|
169
|
+
rdoc_options: []
|
170
|
+
require_paths:
|
171
|
+
- lib
|
172
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - '>='
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
177
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
requirements: []
|
183
|
+
rubyforge_project:
|
184
|
+
rubygems_version: 2.0.14
|
185
|
+
signing_key:
|
186
|
+
specification_version: 4
|
187
|
+
summary: Automatic Rails association eager loading
|
188
|
+
test_files:
|
189
|
+
- spec/db/database.yml
|
190
|
+
- spec/db/schema.rb
|
191
|
+
- spec/goldiloader/goldiloader_spec.rb
|
192
|
+
- spec/spec_helper.rb
|
193
|
+
- spec/support/compatibility.rb
|