README.md in n1_loader-1.1.0 vs README.md in n1_loader-1.2.0

- old
+ new

@@ -12,10 +12,11 @@ - it can be [isolated](#isolated-loaders) - it loads data [lazily](#lazy-loading) - it supports [shareable loaders](#shareable-loaders) between multiple classes - it supports [reloading](#reloading) - it supports optimized [single object loading](#optimized-single-case) +- it supports [arguments](#arguments) - it has an integration with [ActiveRecord][5] which makes it brilliant ([example](#activerecord)) - it has an integration with [ArLazyPreload][6] which makes it excellent ([example](#arlazypreload)) ... and even more features to come! Stay tuned! @@ -37,171 +38,213 @@ gem 'n1_loader', require: 'n1_loader/ar_lazy_preload' ``` ## Usage -**Supported Ruby version:** 2.5, 2.6, 2.7, 3.0, and latest. - ```ruby -class Example +class User include N1Loader::Loadable # with inline loader - n1_loader :anything do |elements| - elements.each { |element| fulfill(element, [element]) } + n1_loader :orders_count do |users| + orders_per_user = Order.where(user: users).group(:user_id).count + + users.each { |user| fulfill(user, orders_per_user[user.id]) } end - - # with custom loader - n1_loader :something, MyLoader end -# Custom loader that can be shared with many classes -class MyLoader < N1Loader::Loader - def perform(elements) - elements.each { |element| fulfill(element, [element]) } - end -end - # For single object -ex = Example.new -ex.anything +user = User.new +user.orders_count # For multiple objects without N+1 -objects = [Example.new, Example.new] -N1Loader::Preloader.new(objects).preload(:anything) -objects.map(&:anything) +users = [User.new, User.new] +N1Loader::Preloader.new(users).preload(:orders_count) +users.map(&:orders_count) ``` ### Lazy loading ```ruby -class Example +class User include N1Loader::Loadable - - n1_loader :anything do |elements| - elements.each { |element| fulfill(element, [element]) } + + # with inline loader + n1_loader :orders_count do |users| + orders_per_user = Order.where(user: users).group(:user_id).count + + users.each { |user| fulfill(user, orders_per_user[user.id]) } end end -object = Example.new # => nothing was done for loading -object.anything # => first time loading +user = User.new # => nothing was done for loading +user.orders_count # => first time loading -objects = [Example.new, Example.new] # => nothing was done for loading -N1Loader::Preloader.new([objects]).preload(:anything) # => we only initial loader but didn't perform it yet -objects.map(&:anything) # => loading happen for the first time (without N+1) +users = [User.new, User.new] # => nothing was done for loading +N1Loader::Preloader.new([users]).preload(:orders_count) # => we only initialized loader but didn't perform it yet +users.map(&:orders_count) # => loading has happen for the first time (without N+1) ``` ### Shareable loaders ```ruby -class MyLoader < N1Loader::Loader - def perform(elements) - elements.each { |element| fulfill(element, [element]) } +class OrdersCountLoader < N1Loader::Loader + def perform(users) + orders_per_user = Order.where(user: users).group(:user_id).count + + users.each { |user| fulfill(user, orders_per_user[user.id]) } end end -class A +class User include N1Loader::Loadable - n1_loader :anything, MyLoader + n1_loader :orders_count, OrdersCountLoader end -class B +class Customer include N1Loader::Loadable - n1_loader :something, MyLoader + n1_loader :orders_count, OrdersCountLoader end -A.new.anything # => works -B.new.something # => works +User.new.orders_count # => works +Customer.new.orders_count # => works ``` ### Reloading ```ruby -class Example +class User include N1Loader::Loadable # with inline loader - n1_loader :anything do |elements| - elements.each { |element| fulfill(element, [element]) } + n1_loader :orders_count do |users| + orders_per_user = Order.where(user: users).group(:user_id).count + + users.each { |user| fulfill(user, orders_per_user[user.id]) } end end -object = Example.new -object.anything # => loader is executed first time and value was cached -object.anything(reload: true) # => loader is executed again and a new value was cached +user = User.new +user.orders_count # => loader is executed first time and value was cached +user.orders_count(reload: true) # => loader is executed again and a new value was cached -objects = [Example.new, Example.new] +users = [User.new, User.new] +N1Loader::Preloader.new(users).preload(:orders_count) # => loader was initialized but not yet executed +users.map(&:orders_count) # => loader was executed first time without N+1 issue and values were cached -N1Loader::Preloader.new(objects).preload(:anything) # => loader was initialized but not yet executed -objects.map(&:anything) # => loader was executed first time without N+1 issue and values were cached - -N1Loader::Preloader.new(objects).preload(:anything) # => loader was initialized again but not yet executed -objects.map(&:anything) # => new loader was executed first time without N+1 issue and new values were cached +N1Loader::Preloader.new(users).preload(:orders_count) # => loader was initialized again but not yet executed +users.map(&:orders_count) # => new loader was executed first time without N+1 issue and new values were cached ``` ### Isolated loaders ```ruby -class MyLoader < N1Loader::Loader +class IsolatedLoader < N1Loader::Loader def perform(elements) elements.each { |element| fulfill(element, [element]) } end end objects = [1, 2, 3, 4] -loader = MyLoader.new(objects) +loader = IsolatedLoader.new(objects) objects.each do |object| loader.for(object) # => it has no N+1 and it doesn't require to be injected in the class end ``` ### Optimized single case ```ruby -class Example +class User include N1Loader::Loadable - n1_loader :something do # no arguments passed to the block, so we can override both perform and single. - def perform(elements) - elements.each { |element| fulfill(element, [element]) } + n1_loader :orders_count do # no arguments passed to the block, so we can override both perform and single. + def perform(users) + orders_per_user = Order.where(user: users).group(:user_id).count + + users.each { |user| fulfill(user, orders_per_user[user.id]) } end # Optimized for single object loading - def single(element) - # Just return a value you want to have for this element - [element] + def single(user) + user.orders.count end end end -object = Example.new -object.something # single will be used here +user = User.new +user.orders_count # single will be used here -objects = [Example.new, Example.new] -N1Loader::Preloader.new(objects).preload(:something) -objects.map(&:something) # perform will be used once without N+1 +users = [User.new, User.new] +N1Loader::Preloader.new(users).preload(:orders_count) +users.map(&:orders_count) # perform will be used once without N+1 ``` -## Integrations +### Arguments -### [ActiveRecord][5] +```ruby +class User + include N1Loader::Loadable -**Supported versions**: 5, 6. + n1_loader :orders_count do |users, type| + orders_per_user = Order.where(type: type, user: users).group(:user_id).count -_Note_: Please open an issue if you interested in support of version 7 or other. + users.each { |user| fulfill(user, orders_per_user[user.id]) } + end +end +user = User.new +user.orders_count(:gifts) # The loader will be performed first time for this argument +user.orders_count(:sales) # The loader will be performed first time for this argument +user.orders_count(:gifts) # The cached value will be used + +users = [User.new, User.new] +N1Loader::Preloader.new(users).preload(:orders_count) +users.map { |user| user.orders_count(:gifts) } # No N+1 here +``` + +_Note_: By default, we use `arguments.map(&:object_id)` to identify arguments but in some cases, +you may want to override it, for example: + ```ruby +class User + include N1Loader::Loadable + + n1_loader :orders_count do + def perform(users, sale) + orders_per_user = Order.where(sale: sale, user: users).group(:user_id).count + + users.each { |user| fulfill(user, orders_per_user[user.id]) } + end + + def self.arguments_key(sale) + sale.id + end + end +end + +user = User.new +user.orders_count(Sale.first) # perform will be executed and value will be cached +user.orders_count(Sale.first) # the cached value will be returned +``` + + +## Integrations + +### [ActiveRecord][5] + +```ruby class User < ActiveRecord::Base include N1Loader::Loadable n1_loader :orders_count do |users| - hash = Order.where(user: users).group(:user_id).count - - users.each { |user| fulfill(user, hash[user.id]) } + orders_per_user = Order.where(user: users).group(:user_id).count + + users.each { |user| fulfill(user, orders_per_user[user.id]) } end end # For single user user = User.first @@ -223,13 +266,13 @@ ```ruby class User < ActiveRecord::Base include N1Loader::Loadable n1_loader :orders_count do |users| - hash = Order.where(user: users).group(:user_id).count + orders_per_user = Order.where(user: users).group(:user_id).count - users.each { |user| fulfill(user, hash[user.id]) } + users.each { |user| fulfill(user, orders_per_user[user.id]) } end end # For single user user = User.first @@ -241,15 +284,9 @@ User.preload_associations_lazily.all.map(&:orders_count) # or ArLazyPreload.config.auto_preload = true User.all.map(:orders_count) ``` - -## Development - -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. - -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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/djezzzl/n1_loader. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md). \ No newline at end of file