bulk_loader 0.1.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +21 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Guardfile +75 -0
- data/README.md +127 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bulk_loader.gemspec +30 -0
- data/example/sample.rb +89 -0
- data/lib/bulk_loader/attribute.rb +32 -0
- data/lib/bulk_loader/class_attribute.rb +55 -0
- data/lib/bulk_loader/dsl.rb +40 -0
- data/lib/bulk_loader/lazy.rb +33 -0
- data/lib/bulk_loader/loader.rb +67 -0
- data/lib/bulk_loader/version.rb +5 -0
- data/lib/bulk_loader.rb +12 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 291b02e009a7794d095870421c89543837ad2333
|
4
|
+
data.tar.gz: 186c8a0ab04583ccc99bdfe79769372d1fb34321
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a5b8fef4a1dbcc0c01fd242dd6d78bbd52ffd0af490e934fe6b7f868f081f0986b43eac89b994e8ef2b8cc24420f6317d952e60a074468eef6d5ed2bc0846893
|
7
|
+
data.tar.gz: 41d078dca4196b95424f955d30e6da695fe7617dfd64f88e637c2a573e14b717a31ff4c7d73106360589d64f352a706d883499c7c98f655b847fd7558d5f9a11
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# See default settings https://github.com/bbatsov/rubocop/blob/master/config/enabled.yml
|
2
|
+
AllCops:
|
3
|
+
TargetRubyVersion: 2.3
|
4
|
+
DisplayCopNames: true
|
5
|
+
Exclude:
|
6
|
+
- '*.gemspec'
|
7
|
+
- 'Gemfile'
|
8
|
+
- 'Guardfile'
|
9
|
+
- 'bin/*'
|
10
|
+
- 'Rakefile'
|
11
|
+
- 'example/**/*'
|
12
|
+
|
13
|
+
Metrics/BlockLength:
|
14
|
+
Exclude:
|
15
|
+
- 'spec/**/*'
|
16
|
+
|
17
|
+
Metrics/LineLength:
|
18
|
+
Max: 100
|
19
|
+
|
20
|
+
Style/Documentation:
|
21
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
# Rails files
|
44
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
45
|
+
dsl.watch_spec_files_for(rails.app_files)
|
46
|
+
dsl.watch_spec_files_for(rails.views)
|
47
|
+
|
48
|
+
watch(rails.controllers) do |m|
|
49
|
+
[
|
50
|
+
rspec.spec.call("routing/#{m[1]}_routing"),
|
51
|
+
rspec.spec.call("controllers/#{m[1]}_controller"),
|
52
|
+
rspec.spec.call("acceptance/#{m[1]}")
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Rails config changes
|
57
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
58
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
59
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
60
|
+
|
61
|
+
# Capybara features specs
|
62
|
+
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
63
|
+
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
|
64
|
+
|
65
|
+
# Turnip features and steps
|
66
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
67
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
68
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
guard :rubocop do
|
73
|
+
watch(%r{.+\.rb$})
|
74
|
+
watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
|
75
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# BulkLoader
|
2
|
+
|
3
|
+
## Example
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
# app/models/post.rb
|
7
|
+
|
8
|
+
class Post < ApplicationRecord
|
9
|
+
include BulkLoader::DSL
|
10
|
+
|
11
|
+
bulk_loader :comment_count, :id, default: 0 do |ids|
|
12
|
+
Comment.where(id: ids).group(:post_id).count
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
```
|
17
|
+
|
18
|
+
You can use this like followings:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# app/controllers/posts_controller.rb
|
22
|
+
class PostsController < ApplicationController
|
23
|
+
def index
|
24
|
+
@posts = Post.limit(10)
|
25
|
+
|
26
|
+
# load comment_count blocks with mapping by #id
|
27
|
+
# you can avoid N+1 queries.
|
28
|
+
Post.bulk_loader.load(:comment_count, @posts)
|
29
|
+
|
30
|
+
render(json: @posts.map {|post| { id: post.id, comment_count: post.comment_count } })
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
## Description
|
36
|
+
|
37
|
+
BulkLoader::DSL only create bulk\_loader class method and bulk\_loader method.
|
38
|
+
So you can use with any object that is not ActiveRecord.
|
39
|
+
|
40
|
+
### Defining bulk\_loader method
|
41
|
+
|
42
|
+
If you include BulkLoader::DSL, you can use bulk\_loader class method.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class YourModel
|
46
|
+
include BulkLoader::DSL
|
47
|
+
|
48
|
+
bulk_loader :name, :mapped_key, default: nil do |mapped_keys|
|
49
|
+
# something with mapped_keys
|
50
|
+
{
|
51
|
+
mapped_key => value, # you should return Hash that has mapped_key as key.
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
that create a instance method that name is :name.
|
58
|
+
|
59
|
+
#### mapped\_key
|
60
|
+
|
61
|
+
mapped\_key is Symbol or Proc. if you want to use original object, you can pass `->(your_model) { your_model } `.
|
62
|
+
|
63
|
+
#### default option
|
64
|
+
|
65
|
+
If block's result return Hash that cannot mapped to original object, you can return value using default option.
|
66
|
+
If you does not pass default option, default is nil.
|
67
|
+
|
68
|
+
If you want to pass object like Array, you should use with lambda.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class YourModel
|
72
|
+
include BulkLoader::DSL
|
73
|
+
|
74
|
+
bulk_loader :name, :mapped_key, default: -> { [] } do |mapped_keys|
|
75
|
+
# something with mapped_keys
|
76
|
+
{
|
77
|
+
mapped_key => value, # you should return Hash that has mapped_key as key.
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
#### export: false
|
84
|
+
|
85
|
+
:name method is just shorthand for bulk\_loader.public\_send(:name). So if you not want to create :name method, you can pass export: false to bulk\_loader definition.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
class YourModel
|
89
|
+
include BulkLoader::DSL
|
90
|
+
|
91
|
+
bulk_loader :name, :mapped_key, default: nil, export: false do |mapped_keys|
|
92
|
+
# something with mapped_keys
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
then you can use this like followings.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
YourModel.new.bulk_loader.name
|
101
|
+
```
|
102
|
+
|
103
|
+
## Installation
|
104
|
+
|
105
|
+
Add this line to your application's Gemfile:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
gem 'bulk_loader'
|
109
|
+
```
|
110
|
+
|
111
|
+
And then execute:
|
112
|
+
|
113
|
+
$ bundle
|
114
|
+
|
115
|
+
Or install it yourself as:
|
116
|
+
|
117
|
+
$ gem install bulk_loader
|
118
|
+
|
119
|
+
## Development
|
120
|
+
|
121
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
122
|
+
|
123
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
124
|
+
|
125
|
+
## Contributing
|
126
|
+
|
127
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/walf443/bulk_loader.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "bulk_loader"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/bulk_loader.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "bulk_loader/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bulk_loader"
|
8
|
+
spec.version = BulkLoader::VERSION
|
9
|
+
spec.authors = ["Keiji Yoshimi"]
|
10
|
+
spec.email = ["walf443@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{utility to avoid N+1 queries}
|
13
|
+
spec.description = %q{utility to avoid N+1 queries}
|
14
|
+
spec.homepage = "https://github.com/walf443/bulk_loader"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
spec.add_development_dependency "rubocop", "~> 0.49"
|
27
|
+
spec.add_development_dependency "guard", "~> 2.14"
|
28
|
+
spec.add_development_dependency "guard-rspec", "~> 4.7.3"
|
29
|
+
spec.add_development_dependency "guard-rubocop", "~> 1.3.0"
|
30
|
+
end
|
data/example/sample.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.join(File.expand_path(File.join(__dir__, '..')), 'lib', 'bulk_loader')
|
2
|
+
require 'singleton'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class CommentRepositoy
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@comments = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def push(comment)
|
13
|
+
@comments.push(comment)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load
|
17
|
+
@comments
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Comment
|
22
|
+
attr_accessor :id, :post_id, :user_id
|
23
|
+
|
24
|
+
def initialize(id:, post_id:, user_id:)
|
25
|
+
self.id = id
|
26
|
+
self.user_id = user_id
|
27
|
+
self.post_id = post_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Post
|
32
|
+
include BulkLoader::DSL
|
33
|
+
attr_accessor :id
|
34
|
+
|
35
|
+
bulk_loader :comment_count, :id, default: 0 do |ids|
|
36
|
+
p "try loading :comment_count with #{ids.inspect}"
|
37
|
+
CommentRepositoy.instance.load.each_with_object(Hash.new(0)) {|e, a| a[e.post_id] += 1 }
|
38
|
+
end
|
39
|
+
|
40
|
+
bulk_loader :comment_user_count, :id, default: 0 do |ids|
|
41
|
+
p "try loading :comment_user_count with #{ids.inspect}"
|
42
|
+
CommentRepositoy.instance.load
|
43
|
+
.each_with_object({}) {|e, a| a[e.post_id] ||= Set.new; a[e.post_id].add(e.user_id) }
|
44
|
+
.each_with_object({}){ |(key, user_ids), a| a[key] = user_ids.size }
|
45
|
+
end
|
46
|
+
|
47
|
+
bulk_loader :comment_exist?, :id, default: false do |ids|
|
48
|
+
p "try loading :comment_exist? with #{ids.inspect}"
|
49
|
+
CommentRepositoy.instance.load.each_with_object({}) {|e, a| a[e.post_id] = true }
|
50
|
+
end
|
51
|
+
|
52
|
+
bulk_loader :commented?, :id, default: false do |ids, current_user|
|
53
|
+
CommentRepositoy.instance.load.each_with_object({}) {|e, a| a[e.post_id] = true if e.user_id == current_user&.id }
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(id:)
|
57
|
+
self.id = id
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class User
|
62
|
+
attr_accessor :id
|
63
|
+
def initialize(id:)
|
64
|
+
self.id = id
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
repository = CommentRepositoy.instance
|
69
|
+
post1 = Post.new(id: 1)
|
70
|
+
post2 = Post.new(id: 2)
|
71
|
+
post3 = Post.new(id: 3)
|
72
|
+
|
73
|
+
repository.push(Comment.new(id: 1, user_id: 1, post_id: post1.id))
|
74
|
+
repository.push(Comment.new(id: 2, user_id: 1, post_id: post1.id))
|
75
|
+
repository.push(Comment.new(id: 3, user_id: 2, post_id: post1.id))
|
76
|
+
repository.push(Comment.new(id: 4, user_id: 1, post_id: post2.id))
|
77
|
+
repository.push(Comment.new(id: 5, user_id: 2, post_id: post2.id))
|
78
|
+
|
79
|
+
Post.bulk_loader.load([:comment_count, :comment_user_count, :comment_exist?], [post1, post2, post3, post1])
|
80
|
+
# p post1.bulk_loader.comment_count
|
81
|
+
p post1.comment_count
|
82
|
+
p post2.comment_count
|
83
|
+
p post1.comment_exist?
|
84
|
+
p post3.comment_exist?
|
85
|
+
p post1.comment_user_count
|
86
|
+
|
87
|
+
current_user = User.new(id: 1)
|
88
|
+
Post.bulk_loader.load(:commented?, [post1, post2, post3], nil)
|
89
|
+
p post1.commented?
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BulkLoader
|
4
|
+
class Attribute
|
5
|
+
def initialize(cattr, target)
|
6
|
+
@class_attribute = cattr
|
7
|
+
@target = target
|
8
|
+
@lazy_of ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def lazy(name)
|
12
|
+
@lazy_of[name] ||= BulkLoader::Lazy.new(@target)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def method_missing(name, *args)
|
18
|
+
return super unless @class_attribute.include?(name)
|
19
|
+
define_singleton_method(name) do
|
20
|
+
attr = lazy(name)
|
21
|
+
@class_attribute.load([name], [self]) unless attr.loaded?
|
22
|
+
attr.get
|
23
|
+
end
|
24
|
+
public_send(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond_to_missing?(name, include_private)
|
28
|
+
return true if @class_attribute.include?(name)
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BulkLoader
|
4
|
+
class ClassAttribute
|
5
|
+
def initialize
|
6
|
+
@loader_of = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def include?(name)
|
10
|
+
@loader_of.include?(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(loader_names, attributes, *args)
|
14
|
+
attrs = convert_attributes(attributes)
|
15
|
+
|
16
|
+
loader_names = [loader_names] unless loader_names.is_a?(Array)
|
17
|
+
loader_names.each do |name|
|
18
|
+
@loader_of[name].load(attrs.map { |attr| attr.lazy(name) }, *args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def define_loader(name, loader)
|
23
|
+
@loader_of[name] = loader
|
24
|
+
define_singleton_method(name) { loader }
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<BulkLoader::ClassAttribute:#{object_id} #{loader_inspect}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def loader_inspect
|
34
|
+
@loader_of.map { |name, _| name }.join(' ')
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert_attributes(attributes)
|
38
|
+
attrs = []
|
39
|
+
attributes.each do |attr|
|
40
|
+
attrs.push(convert_attribute(attr))
|
41
|
+
end
|
42
|
+
attrs
|
43
|
+
end
|
44
|
+
|
45
|
+
def convert_attribute(attr)
|
46
|
+
if attr.respond_to?(:lazy)
|
47
|
+
attr
|
48
|
+
elsif attr.respond_to?(:bulk_loader)
|
49
|
+
attr.bulk_loader
|
50
|
+
else
|
51
|
+
raise 'attributes should be BulkLoader::Attribute or BulkLoader::DSL included class!!'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'weakref'
|
4
|
+
|
5
|
+
module BulkLoader
|
6
|
+
module DSL
|
7
|
+
module ClassMethods
|
8
|
+
# getter for +BulkLoader::ClassAttribute+
|
9
|
+
# If you pass name, mapping, options argument, you can define loader
|
10
|
+
# if you does not want to export name to object, pass export: false to options.
|
11
|
+
def bulk_loader(*args, &block)
|
12
|
+
@bulk_loader ||= BulkLoader::ClassAttribute.new
|
13
|
+
return @bulk_loader if args.empty?
|
14
|
+
|
15
|
+
name, mapping, options = *args
|
16
|
+
options ||= {}
|
17
|
+
does_export = options.delete(:export)
|
18
|
+
|
19
|
+
@bulk_loader.define_loader(name, BulkLoader::Loader.new(mapping, options, &block))
|
20
|
+
|
21
|
+
return if does_export == false
|
22
|
+
|
23
|
+
define_method name do
|
24
|
+
bulk_loader.public_send(name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.included(mod)
|
30
|
+
mod.extend(ClassMethods)
|
31
|
+
end
|
32
|
+
|
33
|
+
def bulk_loader
|
34
|
+
return @bulk_loader if @bulk_loader
|
35
|
+
|
36
|
+
class_attribute = self.class.bulk_loader
|
37
|
+
@bulk_loader = BulkLoader::Attribute.new(class_attribute, WeakRef.new(self))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BulkLoader
|
4
|
+
# lazy class
|
5
|
+
class Lazy
|
6
|
+
attr_reader :target
|
7
|
+
|
8
|
+
def initialize(target)
|
9
|
+
@loaded = false
|
10
|
+
@value = nil
|
11
|
+
@target = target
|
12
|
+
end
|
13
|
+
|
14
|
+
def get
|
15
|
+
raise 'data is not loaded!!' unless @loaded
|
16
|
+
@value
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(value)
|
20
|
+
@loaded = true
|
21
|
+
@value = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@loaded = false
|
26
|
+
@value = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def loaded?
|
30
|
+
@loaded
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BulkLoader
|
4
|
+
class Loader
|
5
|
+
# +mapping+ is a Symbol or Proc. block's 1st argument mapped using mapping.
|
6
|
+
# and your block's return value's key should be mapped value.
|
7
|
+
def initialize(mapping, default: nil, &block)
|
8
|
+
@mapping = mapping
|
9
|
+
@is_mapping_proc = @mapping.is_a?(Proc)
|
10
|
+
@default = default
|
11
|
+
@is_default_proc = @default.is_a?(Proc)
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(lazy_objs, *args)
|
16
|
+
lazy_obj_of = lazy_objs.each_with_object({}) { |e, h| h[e.target] = e }
|
17
|
+
|
18
|
+
mapping_of = get_mapping(lazy_objs)
|
19
|
+
|
20
|
+
result_of = call_block(mapping_of, *args)
|
21
|
+
|
22
|
+
lazy_objs.each(&:clear)
|
23
|
+
|
24
|
+
set_result_to_lazy_objs(result_of, lazy_obj_of, mapping_of)
|
25
|
+
|
26
|
+
fill_default_to_unloaded_obj(lazy_objs)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def call_block(mapping_of, *args)
|
32
|
+
if args.size < @block.arity - 1
|
33
|
+
message = "block should take #{@block.arity} parameters, but given #{arity.size + 1}"
|
34
|
+
raise ArgumentError, message
|
35
|
+
end
|
36
|
+
result_of = @block.call(mapping_of.keys, *args)
|
37
|
+
raise 'block shuold return Hash' unless result_of.is_a?(Hash)
|
38
|
+
result_of
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_mapping(lazy_objs)
|
42
|
+
mapping_of = {}
|
43
|
+
targets = lazy_objs.map(&:target)
|
44
|
+
targets.each do |target|
|
45
|
+
mapped_target = @is_mapping_proc ? @mapping.call(target) : target.public_send(@mapping)
|
46
|
+
mapping_of[mapped_target] = [] unless mapping_of[mapped_target]
|
47
|
+
mapping_of[mapped_target].push(target)
|
48
|
+
end
|
49
|
+
mapping_of
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_result_to_lazy_objs(result_of, lazy_obj_of, mapping_of)
|
53
|
+
result_of.each do |mapped_target, value|
|
54
|
+
next unless mapping_of[mapped_target]
|
55
|
+
mapping_of[mapped_target].each do |target|
|
56
|
+
lazy_obj_of[target]&.set(value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def fill_default_to_unloaded_obj(lazy_objs)
|
62
|
+
lazy_objs.each do |lazy_obj|
|
63
|
+
lazy_obj.set(@is_default_proc ? @default.call : @default) unless lazy_obj.loaded?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/bulk_loader.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bulk_loader/version'
|
4
|
+
require 'bulk_loader/loader'
|
5
|
+
require 'bulk_loader/lazy'
|
6
|
+
require 'bulk_loader/class_attribute'
|
7
|
+
require 'bulk_loader/attribute'
|
8
|
+
require 'bulk_loader/dsl'
|
9
|
+
|
10
|
+
module BulkLoader
|
11
|
+
# Your code goes here...
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bulk_loader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Keiji Yoshimi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.49'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.49'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.14'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.14'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.7.3
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.7.3
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.3.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.3.0
|
111
|
+
description: utility to avoid N+1 queries
|
112
|
+
email:
|
113
|
+
- walf443@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
121
|
+
- ".travis.yml"
|
122
|
+
- Gemfile
|
123
|
+
- Guardfile
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- bin/console
|
127
|
+
- bin/setup
|
128
|
+
- bulk_loader.gemspec
|
129
|
+
- example/sample.rb
|
130
|
+
- lib/bulk_loader.rb
|
131
|
+
- lib/bulk_loader/attribute.rb
|
132
|
+
- lib/bulk_loader/class_attribute.rb
|
133
|
+
- lib/bulk_loader/dsl.rb
|
134
|
+
- lib/bulk_loader/lazy.rb
|
135
|
+
- lib/bulk_loader/loader.rb
|
136
|
+
- lib/bulk_loader/version.rb
|
137
|
+
homepage: https://github.com/walf443/bulk_loader
|
138
|
+
licenses: []
|
139
|
+
metadata: {}
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 2.5.1
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: utility to avoid N+1 queries
|
160
|
+
test_files: []
|