Skip to content
  • Nony Dutton's avatar
    77cf5e6d
    Add `.shard_keys` & `.connected_to_all_shards` · 77cf5e6d
    Nony Dutton authored
    Currently, there is no (simple) way to ask a model if it connects to a
    single database or to multiple shards. Furthermore, without looping
    through a model's connections, I don't believe there's an easy way to
    return a list of shards a model can connect to.
    
    This commit adds a `@shard_keys` ivar that's set whenever `.connects_to`
    is called. It sets the ivar to the result of `shards.keys`. `shards` in
    `.connects_to` defaults to an empty hash and therefore when calling
    `connects_to database: {...}` `@shard_keys` will be set to an empty array.
    
    `@shard_keys` is set _before_ the following lines:
    
    ```
    if shards.empty?
      shards[:default] = database
    end
    ```
    
    This conditional sets the one and only shard (`:default`) to the value of `database`
    that we pass to `.connects_to`. This allows for calling
    `connected_to(shard: :default)` on models configured to only connect to
    a database e.g.:
    
    ```ruby
    class UnshardedBase < ActiveRecord::Base
      self.abstract_class = true
    
      connects_to database: { writing: :primary }
    end
    
    class UnshardedModel < UnshardedBase
    end
    
    UnshardedBase.connected_to(shard: :default) {
    UnshardedBase.connection_pool.db_config.name } => primary
    ```
    
    This is ultimately still an _unsharded_ model which is why `@shard_keys`
    gets set before the conditional.
    
    With the new `@shard_keys` ivar we need a way for descendants of the
    abstract AR model to return that same value. For that we leverage the
    existing `.connection_class_for_self` method. That method returns the
    ancestor of the model where `.connects_to` was called, or returns self if
    it's the connection class:
    
    ```ruby
    class UnshardedBase < ActiveRecord::Base
      self.abstract_class = true
    
      connects_to database: { writing: :primary }
    end
    
    class UnshardedModel < UnshardedBase
    end
    
    ActiveRecord::Base.connection_class_for_self => ActiveRecord::Base
    
    UnshardedBase.connection_class_for_self => UnshardedBase(abstract)
    
    UnshardedModel.connection_class_for_self => UnshardedBase(abstract)
    ```
    
    The new `.shard_keys` method is a getter which returns the value of
    `@shard_keys` from the connection class or it returns an empty array.
    The empty array is necessary in cases where `connects_to` was never
    called.
    
    Finally, I've added an `.connected_to_all_shards` method which takes all of the
    arguments for `.connected_to` except for `shard`. Instead, it loops through
    every shard key and then delegates everything else to `.connected_to`. I've
    used `.map` instead of `.each` so that we can collect the results of each block.
    77cf5e6d
    Add `.shard_keys` & `.connected_to_all_shards`
    Nony Dutton authored
    Currently, there is no (simple) way to ask a model if it connects to a
    single database or to multiple shards. Furthermore, without looping
    through a model's connections, I don't believe there's an easy way to
    return a list of shards a model can connect to.
    
    This commit adds a `@shard_keys` ivar that's set whenever `.connects_to`
    is called. It sets the ivar to the result of `shards.keys`. `shards` in
    `.connects_to` defaults to an empty hash and therefore when calling
    `connects_to database: {...}` `@shard_keys` will be set to an empty array.
    
    `@shard_keys` is set _before_ the following lines:
    
    ```
    if shards.empty?
      shards[:default] = database
    end
    ```
    
    This conditional sets the one and only shard (`:default`) to the value of `database`
    that we pass to `.connects_to`. This allows for calling
    `connected_to(shard: :default)` on models configured to only connect to
    a database e.g.:
    
    ```ruby
    class UnshardedBase < ActiveRecord::Base
      self.abstract_class = true
    
      connects_to database: { writing: :primary }
    end
    
    class UnshardedModel < UnshardedBase
    end
    
    UnshardedBase.connected_to(shard: :default) {
    UnshardedBase.connection_pool.db_config.name } => primary
    ```
    
    This is ultimately still an _unsharded_ model which is why `@shard_keys`
    gets set before the conditional.
    
    With the new `@shard_keys` ivar we need a way for descendants of the
    abstract AR model to return that same value. For that we leverage the
    existing `.connection_class_for_self` method. That method returns the
    ancestor of the model where `.connects_to` was called, or returns self if
    it's the connection class:
    
    ```ruby
    class UnshardedBase < ActiveRecord::Base
      self.abstract_class = true
    
      connects_to database: { writing: :primary }
    end
    
    class UnshardedModel < UnshardedBase
    end
    
    ActiveRecord::Base.connection_class_for_self => ActiveRecord::Base
    
    UnshardedBase.connection_class_for_self => UnshardedBase(abstract)
    
    UnshardedModel.connection_class_for_self => UnshardedBase(abstract)
    ```
    
    The new `.shard_keys` method is a getter which returns the value of
    `@shard_keys` from the connection class or it returns an empty array.
    The empty array is necessary in cases where `connects_to` was never
    called.
    
    Finally, I've added an `.connected_to_all_shards` method which takes all of the
    arguments for `.connected_to` except for `shard`. Instead, it loops through
    every shard key and then delegates everything else to `.connected_to`. I've
    used `.map` instead of `.each` so that we can collect the results of each block.
Loading