mongoid-activerecord-eagerloadable 0.0.1

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.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ log
@@ -0,0 +1 @@
1
+ 1.9.3-p392
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mongoid-activerecord-eagerloadable.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Articulate Global, Inc., Patrick Klingemann
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,52 @@
1
+ # Mongoid::Activerecord::Eagerloadable
2
+
3
+ Belongs to relationships between Mongoid and ActiveRecord models. With
4
+ eager loading.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'mongoid-activerecord-eagerloadable'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install mongoid-activerecord-eagerloadable
19
+
20
+ ## Usage
21
+
22
+ Consider the following Mongoid document:
23
+
24
+ class Pet
25
+ include Mongoid::Document
26
+ include Mongoid::ActiveRecord::EagerLoadable
27
+
28
+ belongs_to_active_record :person
29
+ end
30
+
31
+ And the following ActiveRecord class:
32
+
33
+ class Person < ActiveRecord::Base
34
+ attr_accessible :name
35
+ end
36
+
37
+ The `belongs_to_active_record :person` statement enables the following
38
+ to execute a single query against your mongodb instance and a single
39
+ query against your ActiveRecord connection:
40
+
41
+ Pet.scoped.includes_active_record(:person).all.map(&:person)
42
+
43
+ Also, the `person` method is added to each instance of `Pet` regardless
44
+ of whether or not the `includes_active_record` method is invoked
45
+
46
+ ## Contributing
47
+
48
+ 1. Fork it
49
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
50
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
51
+ 4. Push to the branch (`git push origin my-new-feature`)
52
+ 5. Create new Pull Request
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
10
+
11
+ desc 'Run Devise unit tests.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.libs << 'test'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = true
17
+ t.warning = false
18
+ end
@@ -0,0 +1,8 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: eager_loading_test
5
+ hosts:
6
+ - localhost:17017
7
+ options:
8
+ identity_map_enabled: true
@@ -0,0 +1 @@
1
+ require 'mongoid/active_record/eager_loadable.rb'
@@ -0,0 +1,44 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'mongoid/active_record/eager_loadable/version'
3
+ require 'mongoid/active_record/eager_loadable/eager'
4
+ require 'mongoid/active_record/eager_loadable/relation_metadata'
5
+ require 'mongoid/active_record/eager_loadable/criteria'
6
+
7
+ module Mongoid
8
+ module ActiveRecord
9
+ module EagerLoadable
10
+
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ Mongoid::Contextual::Mongo.send(:include, Eager)
15
+ Mongoid::Contextual::Memory.send(:include, Eager)
16
+ Mongoid::Criteria.send(:include, Criteria)
17
+
18
+ @active_record_relations = {}
19
+ end
20
+
21
+ module ClassMethods
22
+ # todo: put this in Accessors similar to mongoid
23
+ def active_record_getter(name, metadata)
24
+ re_define_method name do
25
+ record_id = self.send(metadata.foreign_key)
26
+ IdentityMap.get(metadata.relation_class, record_id) || metadata.relation_class.find(record_id)
27
+ end
28
+ self
29
+ end
30
+
31
+ def active_record_relate(name, metadata)
32
+ @active_record_relations[name] = metadata
33
+ active_record_getter(name, metadata)
34
+ end
35
+
36
+ def belongs_to_active_record(association_id)
37
+ metadata = RelationMetadata.new(association_id)
38
+ active_record_relate(association_id, metadata)
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ module Mongoid
2
+ module ActiveRecord
3
+ module EagerLoadable
4
+
5
+ module Criteria
6
+ extend ActiveSupport::Concern
7
+
8
+ def includes_active_record(*relations)
9
+ relations.each do |relation|
10
+ metadata = RelationMetadata.new(relation)
11
+ active_record_inclusions.push(metadata) unless active_record_inclusions.include?(metadata)
12
+ end
13
+ clone
14
+ end
15
+
16
+ def active_record_inclusions
17
+ @active_record_inclusions ||= []
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ module Mongoid
2
+ module ActiveRecord
3
+ module EagerLoadable
4
+
5
+ module Eager
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ alias_method :original_eager_load, :eager_load
10
+ alias_method :original_eager_loadable?, :eager_loadable?
11
+
12
+ def eager_load(docs)
13
+ load_active_record_inclusions(docs)
14
+ original_eager_load(docs)
15
+ end
16
+
17
+ def eager_loadable?(document = nil)
18
+ return false if criteria.inclusions.empty? && criteria.active_record_inclusions.empty?
19
+ document ? !inclusions_loaded?(document) : !eager_loaded
20
+ end
21
+ end
22
+
23
+ def load_active_record_inclusions(docs)
24
+ criteria.active_record_inclusions.each do |metadata|
25
+ metadata.eager_load(eager_loaded_ids(docs, metadata)) if !docs.empty?
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ module Mongoid
2
+ module ActiveRecord
3
+ module EagerLoadable
4
+
5
+ class RelationMetadata
6
+ attr_reader :relation
7
+
8
+ def initialize(relation)
9
+ @relation = relation
10
+ relation_class.define_singleton_method(:collection_name) { table_name }
11
+ end
12
+
13
+ def eager_load(ids)
14
+ relation_class.where(id: ids.uniq).each do |record|
15
+ IdentityMap.set(record)
16
+ end
17
+ end
18
+
19
+ def relation_class
20
+ relation.to_s.classify.constantize
21
+ end
22
+
23
+ def foreign_key
24
+ "#{relation}_id"
25
+ end
26
+
27
+ def stores_foreign_key?
28
+ true
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ module Mongoid
2
+ module ActiveRecord
3
+ module EagerLoadable
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -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 'mongoid/active_record/eager_loadable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mongoid-activerecord-eagerloadable"
8
+ spec.version = Mongoid::ActiveRecord::EagerLoadable::VERSION
9
+ spec.authors = ["pklingem"]
10
+ spec.email = ["pklingemann@articulate.com"]
11
+ spec.description = %q{Mongoid to ActiveRecord belongs to assocations with eager-loading}
12
+ spec.summary = %q{A Mongoid::Document mixin for creating "belongs to" associations with ActiveRecord models with eager-loading}
13
+ spec.homepage = "http://github.com/articulate/mongoid-activerecord-eagerloadable"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
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 "mongoid", "~> 3.1"
22
+ spec.add_dependency "activerecord", "~> 3.2"
23
+ spec.add_dependency "activesupport", "~> 3.2"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "sqlite3"
29
+ end
@@ -0,0 +1,79 @@
1
+ require 'active_record'
2
+ require 'test/unit'
3
+ require 'mongoid'
4
+ require './lib/mongoid/active_record/eager_loadable.rb'
5
+
6
+ ENV['MONGOID_ENV'] = 'test'
7
+ Mongoid.load!('./config/mongoid.yml', :test)
8
+ ActiveRecord::Base.establish_connection(
9
+ adapter: 'sqlite3',
10
+ database: ':memory:'
11
+ )
12
+
13
+ require './test/support/models'
14
+ require './test/support/active_record/sql_counter.rb'
15
+
16
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "../log/debug.log"))
17
+ ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::SQLCounter.new)
18
+
19
+ class EagerLoadableTest < ActiveRecord::TestCase
20
+ def setup
21
+ @jim = Person.create!(name: 'Jim')
22
+ @bill = Person.create!(name: 'Bill')
23
+ @bob = Person.create!(name: 'Bob')
24
+ @joe = Person.create!(name: 'Joe')
25
+ @jack = Person.create!(name: 'Jack')
26
+
27
+ @fletch = Pet.create!(name: 'Fletch', person_id: @jim.id)
28
+ @wilson = Pet.create!(name: 'Wilson', person_id: @bill.id)
29
+ @mocha = Pet.create!(name: 'Mocha', person_id: @bob.id)
30
+ @tanner = Pet.create!(name: 'Tanner', person_id: @joe.id)
31
+ @freckles = Pet.create!(name: 'Freckles', person_id: @jack.id)
32
+ end
33
+
34
+ def teardown
35
+ Person.delete_all
36
+ Pet.delete_all
37
+ end
38
+
39
+ def test_respond_to_belongs_to_active_record
40
+ assert_respond_to Pet, :belongs_to_active_record
41
+ end
42
+
43
+ def test_respond_to_includes_active_record
44
+ assert_respond_to Pet.scoped, :includes_active_record
45
+ end
46
+
47
+ def test_mongoid_model_responds_to_association_method
48
+ assert_respond_to Pet.first, :person
49
+ end
50
+
51
+ def test_mongoid_model_without_includes_active_record_finds_associated_model_instance
52
+ assert_kind_of Person, Pet.first.person
53
+ end
54
+
55
+ def test_mongoid_model_with_includes_active_record_finds_associated_model_instance
56
+ assert_kind_of Person, Pet.scoped.includes_active_record(:person).first.person
57
+ end
58
+
59
+ def test_getter_method_returns_proper_associatied_object_without_eager_loading
60
+ assert_equal Pet.first.person, Person.find(Pet.first.person_id)
61
+ end
62
+
63
+ def test_getter_method_returns_proper_associatied_object_with_eager_loading
64
+ assert_equal Pet.scoped.includes_active_record(:person).first.person,
65
+ Person.find(Pet.scoped.includes_active_record(:person).first.person_id)
66
+ end
67
+
68
+ def test_multiple_queries_execute_without_eager_loading
69
+ assert_queries(5) do
70
+ Pet.all.map(&:person)
71
+ end
72
+ end
73
+
74
+ def test_one_query_executes_with_eager_loading
75
+ assert_queries(1) do
76
+ Pet.scoped.includes_active_record(:person).map(&:person)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,42 @@
1
+ module ActiveRecord
2
+ class SQLCounter
3
+ class << self
4
+ attr_accessor :ignored_sql, :log, :log_all
5
+ def clear_log; self.log = []; self.log_all = []; end
6
+ end
7
+
8
+ self.clear_log
9
+
10
+ self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
11
+
12
+ # FIXME: this needs to be refactored so specific database can add their own
13
+ # ignored SQL, or better yet, use a different notification for the queries
14
+ # instead examining the SQL content.
15
+ oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
16
+ mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/]
17
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
18
+ sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im]
19
+
20
+ [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
21
+ ignored_sql.concat db_ignored_sql
22
+ end
23
+
24
+ attr_reader :ignore
25
+
26
+ def initialize(ignore = Regexp.union(self.class.ignored_sql))
27
+ @ignore = ignore
28
+ end
29
+
30
+ def call(name, start, finish, message_id, values)
31
+ sql = values[:sql]
32
+
33
+ # FIXME: this seems bad. we should probably have a better way to indicate
34
+ # the query was cached
35
+ return if 'CACHE' == values[:name]
36
+
37
+ self.class.log_all << sql
38
+ self.class.log << sql unless ignore =~ sql
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,3 @@
1
+ load './test/support/schema.rb'
2
+ require './test/support/models/person.rb'
3
+ require './test/support/models/pet.rb'
@@ -0,0 +1,3 @@
1
+ class Person < ActiveRecord::Base
2
+ attr_accessible :name
3
+ end
@@ -0,0 +1,6 @@
1
+ class Pet
2
+ include Mongoid::Document
3
+ include Mongoid::ActiveRecord::EagerLoadable
4
+
5
+ belongs_to_active_record :person
6
+ end
@@ -0,0 +1,5 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :people, force: true do |t|
3
+ t.string :name
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-activerecord-eagerloadable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - pklingem
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.2'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activesupport
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.3'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.3'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sqlite3
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
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
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Mongoid to ActiveRecord belongs to assocations with eager-loading
127
+ email:
128
+ - pklingemann@articulate.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - .ruby-version
135
+ - Gemfile
136
+ - LICENSE.txt
137
+ - README.md
138
+ - Rakefile
139
+ - config/mongoid.yml
140
+ - lib/mongoid-activerecord-eagerloadable.rb
141
+ - lib/mongoid/active_record/eager_loadable.rb
142
+ - lib/mongoid/active_record/eager_loadable/criteria.rb
143
+ - lib/mongoid/active_record/eager_loadable/eager.rb
144
+ - lib/mongoid/active_record/eager_loadable/relation_metadata.rb
145
+ - lib/mongoid/active_record/eager_loadable/version.rb
146
+ - mongoid-activerecord-eagerloadable.gemspec
147
+ - test/eager_loadable_test.rb
148
+ - test/support/active_record/sql_counter.rb
149
+ - test/support/models.rb
150
+ - test/support/models/person.rb
151
+ - test/support/models/pet.rb
152
+ - test/support/schema.rb
153
+ homepage: http://github.com/articulate/mongoid-activerecord-eagerloadable
154
+ licenses:
155
+ - MIT
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ none: false
168
+ requirements:
169
+ - - ! '>='
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 1.8.23
175
+ signing_key:
176
+ specification_version: 3
177
+ summary: A Mongoid::Document mixin for creating "belongs to" associations with ActiveRecord
178
+ models with eager-loading
179
+ test_files:
180
+ - test/eager_loadable_test.rb
181
+ - test/support/active_record/sql_counter.rb
182
+ - test/support/models.rb
183
+ - test/support/models/person.rb
184
+ - test/support/models/pet.rb
185
+ - test/support/schema.rb