diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e46bb..bbb8676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ that you can set version constraints properly. #### [v1.0.4](https://github.com/exAspArk/batch-loader/compare/v1.0.3...v1.0.4) * `Fixed`: Fix arity bug in `respond_to?` [#3](https://github.com/exAspArk/batch-loader/pull/3) +* `Added`: `default_value` override option. +* `Added`: `loader.call {}` block syntax, for memoizing repeat calls to the same item. #### [v1.0.3](https://github.com/exAspArk/batch-loader/compare/v1.0.2...v1.0.3) – 2017-09-18 diff --git a/README.md b/README.md index e0897ba..fd155cb 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ puts users # Users But the problem here is that `load_posts` now depends on the child association and knows that it has to preload data for future use. And it'll do it every time, even if it's not necessary. Can we do better? Sure! -### Basic example +### Basic examples With `BatchLoader` we can rewrite the code above: @@ -125,6 +125,51 @@ puts users # Users SELECT * FROM users WHERE id IN As we can see, batching is isolated and described right in a place where it's needed. +For batches where there is no item in response to a call, we normally return nil. However, you can use `default_value:` to return something else instead. This is particularly useful for 1:Many relationships, where you + +```ruby +def load_posts(ids) + Post.where(id: ids) +end + +def load_user(post) + BatchLoader.for(post.user_id).batch(default_value: NullUser.new) do |user_ids, loader| + User.where(id: user_ids).each { |user| loader.call(user.id, user) } + end +end + +posts = load_posts([1, 2, 3]) + + +users = posts.map do |post| + load_user(post) +end + +puts users +``` + +For batches where the value is some kind of collection, such as an Array or Hash, `loader` also supports being called with a block, which yields the _current_ value, and returns the _next_ value. This is extremely useful for 1:Many relationships: + +```ruby +def load_users(ids) + User.where(id: ids) +end + +def load_comments(user) + BatchLoader.for(user.id).batch(default_value: []) do |comment_ids, loader| + Comment.where(user_id: user_ids).each do |comment| + loader.call(user.id) { |memo| memo.push(comment) } + end + end +end + +users = load_users([1, 2, 3]) + +comments = users.map do |user| + load_comments(user) +end +``` + ### How it works In general, `BatchLoader` returns a lazy object. Each lazy object knows which data it needs to load and how to batch the query. As soon as you need to use the lazy objects, they will be automatically loaded once without N+1 queries.