Class: Sequel::Packer
- Inherits:
-
Object
- Object
- Sequel::Packer
- Defined in:
- lib/sequel/packer.rb,
lib/sequel/packer/version.rb,
lib/sequel/packer/eager_hash.rb,
lib/sequel/packer/validation.rb,
lib/sequel/packer/eager_loading.rb
Defined Under Namespace
Modules: EagerHash, EagerLoading, Validation Classes: AssociationDoesNotExistError, FieldArgumentError, InvalidAssociationPackerError, ModelNotYetDeclaredError, NoAssociationSubpackerDefinedError, UnknownTraitError, UnnecessaryWithContextError
Constant Summary collapse
- METHOD_FIELD =
field(:foo)
:method_field
- BLOCK_FIELD =
field(:foo, &block)
:block_field
- ASSOCIATION_FIELD =
field(:association, subpacker)
:association_field
- ARBITRARY_MODIFICATION_FIELD =
field(&block)
:arbitrary_modification_field
- VERSION =
"1.0.2"
Class Method Summary collapse
-
.eager(*associations) ⇒ Object
Specify additional eager loading that should take place when fetching data to be packed.
-
.field(field_name = nil, subpacker = nil, *traits, &block) ⇒ Object
Declare a field to be packed in the output hash.
-
.inherited(subclass) ⇒ Object
Think of this method as the “initialize” method for a Packer class.
-
.model(klass) ⇒ Object
Declare the type of Sequel::Model this Packer will be used for.
-
.pack(data, *traits, **context) ⇒ Object
Pack the given data with the specified traits and additional context.
-
.precompute(&block) ⇒ Object
Declare an arbitrary operation to be performed one all the data has been fetched.
-
.set_association_packer(association, subpacker, *traits) ⇒ Object
Register that nested models related to the packed model by association should be packed using the given subpacker with the specified traits.
-
.trait(name, &block) ⇒ Object
Define a trait, a set of optional fields that can be packed in certain situations.
-
.with_context(&block) ⇒ Object
Declare a block to be called after a Packer has been initialized with context.
Instance Method Summary collapse
-
#initialize(*traits, **context) ⇒ Packer
constructor
Initialize a Packer instance with the given traits and additional context.
-
#pack(data) ⇒ Object
Pack the given data with the traits and additional context specified when the Packer instance was created.
Constructor Details
#initialize(*traits, **context) ⇒ Packer
Initialize a Packer instance with the given traits and additional context. This Packer can then pack multiple datasets or models via the pack method.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/sequel/packer.rb', line 193 def initialize(*traits, **context) @context = context @subpackers = {} # Technically we only need to duplicate these fields if we modify any of # them, but manually implementing some sort of copy-on-write functionality # is messy and error prone. @instance_fields = class_fields.dup @instance_packers = class_packers.dup @instance_eager_hash = EagerHash.deep_dup(class_eager_hash) @instance_precomputations = class_precomputations.dup class_with_contexts.each do |with_context_block| self.instance_exec(&with_context_block) end # Evaluate trait blocks, which might add new fields to @instance_fields, # new packers to @instance_packers, new associations to # @instance_eager_hash, and/or new precomputations to # @instance_precomputations. traits.each do |trait| trait_block = class_traits[trait] if !trait_block raise UnknownTraitError, "Unknown trait for #{self.class}: :#{trait}" end self.instance_exec(&trait_block) end # Create all the subpackers, and merge in their eager hashes. @instance_packers.each do |association, (subpacker, traits)| association_packer = subpacker.new(*traits, **@context) @subpackers[association] = association_packer @instance_eager_hash = EagerHash.merge!( @instance_eager_hash, {association => association_packer.send(:eager_hash)}, ) end end |
Class Method Details
.eager(*associations) ⇒ Object
Specify additional eager loading that should take place when fetching data to be packed. Commonly used to add filters to association datasets via eager procs.
Users should not assume when using eager procs that the proc actually gets executed. If models with their associations already loaded are passed to pack then the proc will never get processed. Any filtering logic should be duplicated within a field block.
145 146 147 148 149 150 |
# File 'lib/sequel/packer.rb', line 145 def self.eager(*associations) @class_eager_hash = EagerHash.merge!( @class_eager_hash, EagerHash.normalize_eager_args(*associations), ) end |
.field(field_name = nil, subpacker = nil, *traits, &block) ⇒ Object
Declare a field to be packed in the output hash. This method can be called in multiple ways:
field(:field_name)
-
Calls the method :field_name on a model and stores the result under the key :field_name in the packed hash.
field(:field_name, &block)
-
Yields the model to the block and stores the result under the key :field_name in the packed hash.
field(:association, subpacker, *traits)
-
Packs model.association using the designated subpacker with the specified traits.
field(&block)
-
Yields the model and the partially packed hash to the block, allowing for arbitrary modification of the output hash.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/sequel/packer.rb', line 79 def self.field(field_name=nil, subpacker=nil, *traits, &block) Validation.check_field_arguments( @model, field_name, subpacker, traits, &block) field_type = determine_field_type(field_name, subpacker, block) if field_type == ASSOCIATION_FIELD set_association_packer(field_name, subpacker, *traits) end @class_fields << { type: field_type, name: field_name, block: block, } end |
.inherited(subclass) ⇒ Object
Think of this method as the “initialize” method for a Packer class. Every Packer class keeps track of the fields, traits, and other various operations defined using the DSL internally.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/sequel/packer.rb', line 18 def self.inherited(subclass) subclass.instance_variable_set(:@model, @model) subclass.instance_variable_set(:@class_fields, @class_fields&.dup || []) subclass.instance_variable_set(:@class_traits, @class_traits&.dup || {}) subclass.instance_variable_set(:@class_packers, @class_packers&.dup || {}) subclass.instance_variable_set( :@class_eager_hash, EagerHash.deep_dup(@class_eager_hash), ) subclass.instance_variable_set( :@class_precomputations, @class_precomputations&.dup || [], ) subclass.instance_variable_set( :@class_with_contexts, @class_with_contexts&.dup || [], ) end |
.model(klass) ⇒ Object
Declare the type of Sequel::Model this Packer will be used for. Used to validate associations at declaration time.
39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/sequel/packer.rb', line 39 def self.model(klass) if !(klass < Sequel::Model) fail( ArgumentError, 'model declaration must be a subclass of Sequel::Model', ) end fail ArgumentError, 'model already declared' if @model @model = klass end |
.pack(data, *traits, **context) ⇒ Object
Pack the given data with the specified traits and additional context. Context is automatically passed down to any subpackers.
Data can be provided as a Sequel::Dataset, an array of Sequel::Models, a single Sequel::Model, or nil. Even when passing models that have already been materialized, eager loading will be used to efficiently fetch associations.
Returns an array of packed hashes, or a single packed hash if a single model was passed in. Returns nil if nil was passed in.
186 187 188 189 |
# File 'lib/sequel/packer.rb', line 186 def self.pack(data, *traits, **context) return nil if !data new(*traits, **context).pack(data) end |
.precompute(&block) ⇒ Object
Declare an arbitrary operation to be performed one all the data has been fetched. The block will be executed once and be passed all of the models that will be packed by this Packer, even if this Packer is nested as a subpacker of other packers. The block can save the result of the computation in an instance variable which can then be accessed in the blocks passed to field.
158 159 160 161 162 163 |
# File 'lib/sequel/packer.rb', line 158 def self.precompute(&block) if !block raise ArgumentError, 'Sequel::Packer.precompute must be passed a block' end @class_precomputations << block end |
.set_association_packer(association, subpacker, *traits) ⇒ Object
Register that nested models related to the packed model by association should be packed using the given subpacker with the specified traits.
118 119 120 121 122 |
# File 'lib/sequel/packer.rb', line 118 def self.set_association_packer(association, subpacker, *traits) Validation.check_association_packer( @model, association, subpacker, traits) @class_packers[association] = [subpacker, traits] end |
.trait(name, &block) ⇒ Object
Define a trait, a set of optional fields that can be packed in certain situations. The block can call main Packer DSL methods: field, set_association_packer, eager, or precompute.
127 128 129 130 131 132 133 134 135 |
# File 'lib/sequel/packer.rb', line 127 def self.trait(name, &block) if @class_traits.key?(name) raise ArgumentError, "Trait :#{name} already defined" end if !block_given? raise ArgumentError, 'Must give a block when defining a trait' end @class_traits[name] = block end |
.with_context(&block) ⇒ Object
Declare a block to be called after a Packer has been initialized with context. The block can call the common Packer DSL methods. It is most commonly used to pass eager procs that depend on the Packer context to eager.
169 170 171 172 173 174 |
# File 'lib/sequel/packer.rb', line 169 def self.with_context(&block) if !block raise ArgumentError, 'Sequel::Packer.with_context must be passed a block' end @class_with_contexts << block end |
Instance Method Details
#pack(data) ⇒ Object
Pack the given data with the traits and additional context specified when the Packer instance was created.
Data can be provided as a Sequel::Dataset, an array of Sequel::Models, a single Sequel::Model, or nil. Even when passing models that have already been materialized, eager loading will be used to efficiently fetch associations.
Returns an array of packed hashes, or a single packed hash if a single model was passed in. Returns nil if nil was passed in.
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/sequel/packer.rb', line 246 def pack(data) case data when Sequel::Dataset data = data.eager(@instance_eager_hash) if @instance_eager_hash models = data.all run_precomputations(models) pack_models(models) when Sequel::Model if @instance_eager_hash EagerLoading.eager_load(class_model, [data], @instance_eager_hash) end run_precomputations([data]) pack_model(data) when Array if @instance_eager_hash EagerLoading.eager_load(class_model, data, @instance_eager_hash) end run_precomputations(data) pack_models(data) when NilClass nil end end |