Class: Grape::Entity
- Inherits:
-
Object
- Object
- Grape::Entity
- Defined in:
- lib/grape_entity/entity.rb,
lib/grape_entity/options.rb,
lib/grape_entity/exposure.rb,
lib/grape_entity/condition.rb,
lib/grape_entity/delegator.rb,
lib/grape_entity/exposure/base.rb,
lib/grape_entity/condition/base.rb,
lib/grape_entity/delegator/base.rb,
lib/grape_entity/delegator/hash_object.rb,
lib/grape_entity/delegator/plain_object.rb,
lib/grape_entity/exposure/block_exposure.rb,
lib/grape_entity/condition/hash_condition.rb,
lib/grape_entity/condition/block_condition.rb,
lib/grape_entity/exposure/nesting_exposure.rb,
lib/grape_entity/condition/symbol_condition.rb,
lib/grape_entity/delegator/fetchable_object.rb,
lib/grape_entity/delegator/openstruct_object.rb,
lib/grape_entity/exposure/delegator_exposure.rb,
lib/grape_entity/exposure/formatter_exposure.rb,
lib/grape_entity/exposure/represent_exposure.rb,
lib/grape_entity/exposure/formatter_block_exposure.rb,
lib/grape_entity/exposure/nesting_exposure/output_builder.rb,
lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb
Overview
An Entity is a lightweight structure that allows you to easily represent data from your application in a consistent and abstracted way in your API. Entities can also provide documentation for the fields exposed.
Entities are not independent structures, rather, they create representations of other Ruby objects using a number of methods that are convenient for use in an API. Once you've defined an Entity, you can use it in your API like this:
Defined Under Namespace
Modules: Condition, DSL, Delegator, Exposure Classes: Options
Constant Summary collapse
- OPTIONS =
All supported options.
%i[ rewrite as if unless using with proc documentation format_with safe attr_path if_extras unless_extras merge expose_nil override ].to_set.freeze
Class Attribute Summary collapse
-
.formatters ⇒ Hash
Returns all formatters that are registered for this and it's ancestors.
- .root_exposure ⇒ Object
Instance Attribute Summary collapse
-
#delegator ⇒ Object
readonly
Returns the value of attribute delegator.
-
#object ⇒ Object
readonly
Returns the value of attribute object.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Class Method Summary collapse
-
.build_exposure_for_attribute(attribute, nesting_stack, options, block) ⇒ Object
rubocop:enable Layout/LineLength.
- .can_unexpose? ⇒ Boolean
- .cannot_unexpose! ⇒ Object
- .delegation_opts ⇒ Object
-
.documentation ⇒ Object
Returns a hash, the keys are symbolized references to fields in the entity, the values are document keys in the entity's documentation key.
-
.expose(*args, &block) ⇒ Object
This method is the primary means by which you will declare what attributes should be exposed by the entity.
- .find_exposure(attribute) ⇒ Object
-
.format_with(name, &block) ⇒ Object
This allows you to declare a Proc in which exposures can be formatted with.
- .hash_access ⇒ Object
- .hash_access=(value) ⇒ Object
- .inherited(subclass) ⇒ Object
-
.merge_options(options) ⇒ Object
Merges the given options with current block options.
-
.present_collection(present_collection = false, collection_name = :items) ⇒ Object
This allows you to present a collection of objects.
-
.represent(objects, options = {}) ⇒ Object
This convenience method allows you to instantiate one or more entities by passing either a singular or collection of objects.
-
.root(plural, singular = nil) ⇒ Object
This allows you to set a root element name for your representation.
-
.root_element(root_type) ⇒ Object
This method returns the entity's root or collection root node, or its parent's.
-
.root_exposures ⇒ Array
Returns exposures that have been declared for this Entity on the top level.
- .unexpose(*attributes) ⇒ Object
- .unexpose_all ⇒ Object
-
.valid_options(options) ⇒ Object
Raises an error if the given options include unknown keys.
-
.with_options(options) ⇒ Object
Set options that will be applied to any exposures declared inside the block.
Instance Method Summary collapse
- #delegate_attribute(attribute) ⇒ Object
- #documentation ⇒ Object
- #exec_with_attribute(attribute, &block) ⇒ Object
- #exec_with_object(options, &block) ⇒ Object
- #formatters ⇒ Object
-
#initialize(object, options = {}) ⇒ Entity
constructor
A new instance of Entity.
-
#inspect ⇒ Object
Prevent default serialization of :options or :delegator.
- #is_defined_in_entity?(attribute) ⇒ Boolean
- #presented ⇒ Object
- #root_exposure ⇒ Object
- #root_exposures ⇒ Object
-
#serializable_hash(runtime_options = {}) ⇒ Object
(also: #as_json)
The serializable hash is the Entity's primary output.
- #to_json(options = {}) ⇒ Object
- #to_xml(options = {}) ⇒ Object
- #value_for(key, options = Options.new) ⇒ Object
Constructor Details
#initialize(object, options = {}) ⇒ Entity
Returns a new instance of Entity.
480 481 482 483 484 485 486 487 |
# File 'lib/grape_entity/entity.rb', line 480 def initialize(object, = {}) @object = object @options = .is_a?(Options) ? : Options.new() @delegator = Delegator.new(object) # Why not `arity > 1`? It might be negative https://ruby-doc.org/core-2.6.6/Method.html#method-i-arity @delegator_accepts_opts = @delegator.method(:delegate).arity != 1 end |
Class Attribute Details
.formatters ⇒ Hash
Returns all formatters that are registered for this and it's ancestors
112 113 114 |
# File 'lib/grape_entity/entity.rb', line 112 def formatters @formatters ||= {} end |
Instance Attribute Details
#delegator ⇒ Object (readonly)
Returns the value of attribute delegator
47 48 49 |
# File 'lib/grape_entity/entity.rb', line 47 def delegator @delegator end |
#object ⇒ Object (readonly)
Returns the value of attribute object
47 48 49 |
# File 'lib/grape_entity/entity.rb', line 47 def object @object end |
#options ⇒ Object (readonly)
Returns the value of attribute options
47 48 49 |
# File 'lib/grape_entity/entity.rb', line 47 def @options end |
Class Method Details
.build_exposure_for_attribute(attribute, nesting_stack, options, block) ⇒ Object
rubocop:enable Layout/LineLength
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/grape_entity/entity.rb', line 219 def self.build_exposure_for_attribute(attribute, nesting_stack, , block) exposure_list = nesting_stack.empty? ? root_exposures : nesting_stack.last.nested_exposures exposure = Exposure.new(attribute, ) exposure_list.delete_by(attribute) if exposure.override? exposure_list << exposure # Nested exposures are given in a block with no parameters. return unless exposure.nesting? nesting_stack << exposure block.call nesting_stack.pop end |
.can_unexpose? ⇒ Boolean
258 259 260 |
# File 'lib/grape_entity/entity.rb', line 258 def self.can_unexpose? (@nesting_stack ||= []).empty? end |
.cannot_unexpose! ⇒ Object
262 263 264 |
# File 'lib/grape_entity/entity.rb', line 262 def self.cannot_unexpose! raise "You cannot call 'unexpose` inside of nesting exposure!" end |
.delegation_opts ⇒ Object
132 133 134 |
# File 'lib/grape_entity/entity.rb', line 132 def delegation_opts @delegation_opts ||= { hash_access: hash_access } end |
.documentation ⇒ Object
Returns a hash, the keys are symbolized references to fields in the entity, the values are document keys in the entity's documentation key. When calling
docmentation, any exposure without a documentation key will be ignored.
284 285 286 287 288 |
# File 'lib/grape_entity/entity.rb', line 284 def self.documentation @documentation ||= root_exposures.each_with_object({}) do |exposure, memo| memo[exposure.key] = exposure.documentation if exposure.documentation && !exposure.documentation.empty? end end |
.expose(*args, &block) ⇒ Object
This method is the primary means by which you will declare what attributes should be exposed by the entity.
Note the parameters passed in via the lambda syntax.
rubocop:disable Layout/LineLength
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/grape_entity/entity.rb', line 191 def self.expose(*args, &block) = (args.last.is_a?(Hash) ? args.pop : {}) if args.size > 1 raise ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if [:as] raise ArgumentError, 'You may not use the :expose_nil on multi-attribute exposures.' if .key?(:expose_nil) raise ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given? end if block_given? if [:format_with].respond_to?(:call) raise ArgumentError, 'You may not use block-setting when also using format_with' end if block.parameters.any? [:proc] = block else [:nesting] = true end end @documentation = nil @nesting_stack ||= [] args.each { |attribute| build_exposure_for_attribute(attribute, @nesting_stack, , block) } end |
.find_exposure(attribute) ⇒ Object
242 243 244 |
# File 'lib/grape_entity/entity.rb', line 242 def self.find_exposure(attribute) root_exposures.find_by(attribute) end |
.format_with(name, &block) ⇒ Object
This allows you to declare a Proc in which exposures can be formatted with. It take a block with an arity of 1 which is passed as the value of the exposed attribute.
316 317 318 319 320 |
# File 'lib/grape_entity/entity.rb', line 316 def self.format_with(name, &block) raise ArgumentError, 'You must pass a block for formatters' unless block_given? formatters[name.to_sym] = block end |
.hash_access ⇒ Object
116 117 118 |
# File 'lib/grape_entity/entity.rb', line 116 def hash_access @hash_access ||= :to_sym end |
.hash_access=(value) ⇒ Object
120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/grape_entity/entity.rb', line 120 def hash_access=(value) @hash_access = case value when :to_s, :str, :string :to_s when :to_sym, :sym, :symbol :to_sym else :to_sym end end |
.inherited(subclass) ⇒ Object
139 140 141 142 |
# File 'lib/grape_entity/entity.rb', line 139 def self.inherited(subclass) subclass.root_exposure = root_exposure.dup subclass.formatters = formatters.dup end |
.merge_options(options) ⇒ Object
Merges the given options with current block options.
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 |
# File 'lib/grape_entity/entity.rb', line 588 def self.() opts = {} merge_logic = proc do |key, existing_val, new_val| if %i[if unless].include?(key) if existing_val.is_a?(Hash) && new_val.is_a?(Hash) existing_val.merge(new_val) elsif new_val.is_a?(Hash) (opts["#{key}_extras".to_sym] ||= []) << existing_val new_val else (opts["#{key}_extras".to_sym] ||= []) << new_val existing_val end else new_val end end @block_options ||= [] opts.merge @block_options.inject({}) { |final, step| final.merge(step, &merge_logic) }.merge((), &merge_logic) end |
.present_collection(present_collection = false, collection_name = :items) ⇒ Object
This allows you to present a collection of objects.
When false (default) every object in a collection to present will be wrapped separately into an instance of your presenter.
417 418 419 420 |
# File 'lib/grape_entity/entity.rb', line 417 def self.present_collection(present_collection = false, collection_name = :items) @present_collection = present_collection @collection_name = collection_name end |
.represent(objects, options = {}) ⇒ Object
This convenience method allows you to instantiate one or more entities by passing either a singular or collection of objects. Each object will be initialized with the same options. If an array of objects is passed in, an array of entities will be returned. If a single object is passed in, a single entity will be returned.
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 |
# File 'lib/grape_entity/entity.rb', line 439 def self.represent(objects, = {}) @present_collection ||= nil if objects.respond_to?(:to_ary) && !@present_collection root_element = root_element(:collection_root) inner = objects.to_ary.map { |object| new(object, .reverse_merge(collection: true)).presented } else objects = { @collection_name => objects } if @present_collection root_element = root_element(:root) inner = new(objects, ).presented end root_element = [:root] if .key?(:root) root_element ? { root_element => inner } : inner end |
.root(plural, singular = nil) ⇒ Object
This allows you to set a root element name for your representation.
361 362 363 364 |
# File 'lib/grape_entity/entity.rb', line 361 def self.root(plural, singular = nil) @collection_root = plural @root = singular end |
.root_element(root_type) ⇒ Object
This method returns the entity's root or collection root node, or its parent's
457 458 459 460 461 462 463 464 |
# File 'lib/grape_entity/entity.rb', line 457 def self.root_element(root_type) instance_variable = "@#{root_type}" if instance_variable_defined?(instance_variable) && instance_variable_get(instance_variable) instance_variable_get(instance_variable) elsif superclass.respond_to? :root_element superclass.root_element(root_type) end end |
.root_exposures ⇒ Array
Returns exposures that have been declared for this Entity on the top level.
238 239 240 |
# File 'lib/grape_entity/entity.rb', line 238 def self.root_exposures root_exposure.nested_exposures end |
.unexpose(*attributes) ⇒ Object
246 247 248 249 250 |
# File 'lib/grape_entity/entity.rb', line 246 def self.unexpose(*attributes) cannot_unexpose! unless can_unexpose? @documentation = nil root_exposures.delete_by(*attributes) end |
.unexpose_all ⇒ Object
252 253 254 255 256 |
# File 'lib/grape_entity/entity.rb', line 252 def self.unexpose_all cannot_unexpose! unless can_unexpose? @documentation = nil root_exposures.clear end |
.valid_options(options) ⇒ Object
Raises an error if the given options include unknown keys. Renames aliased options.
617 618 619 620 621 622 623 624 |
# File 'lib/grape_entity/entity.rb', line 617 def self.() .each_key do |key| raise ArgumentError, "#{key.inspect} is not a valid option." unless OPTIONS.include?(key) end [:using] = .delete(:with) if .key?(:with) end |
.with_options(options) ⇒ Object
Set options that will be applied to any exposures declared inside the block.
275 276 277 278 279 |
# File 'lib/grape_entity/entity.rb', line 275 def self.() (@block_options ||= []).push(()) yield @block_options.pop end |
Instance Method Details
#delegate_attribute(attribute) ⇒ Object
536 537 538 539 540 541 542 543 544 |
# File 'lib/grape_entity/entity.rb', line 536 def delegate_attribute(attribute) if is_defined_in_entity?(attribute) send(attribute) elsif @delegator_accepts_opts delegator.delegate(attribute, self.class.delegation_opts) else delegator.delegate(attribute) end end |
#documentation ⇒ Object
497 498 499 |
# File 'lib/grape_entity/entity.rb', line 497 def documentation self.class.documentation end |
#exec_with_attribute(attribute, &block) ⇒ Object
528 529 530 |
# File 'lib/grape_entity/entity.rb', line 528 def exec_with_attribute(attribute, &block) instance_exec(delegate_attribute(attribute), &block) end |
#exec_with_object(options, &block) ⇒ Object
520 521 522 523 524 525 526 |
# File 'lib/grape_entity/entity.rb', line 520 def exec_with_object(, &block) if block.parameters.count == 1 instance_exec(object, &block) else instance_exec(object, , &block) end end |
#formatters ⇒ Object
501 502 503 |
# File 'lib/grape_entity/entity.rb', line 501 def formatters self.class.formatters end |
#inspect ⇒ Object
Prevent default serialization of :options or :delegator.
475 476 477 478 |
# File 'lib/grape_entity/entity.rb', line 475 def inspect fields = serializable_hash.map { |k, v| "#{k}=#{v}" } "#<#{self.class.name}:#{object_id} #{fields.join(' ')}>" end |
#is_defined_in_entity?(attribute) ⇒ Boolean
546 547 548 549 550 551 |
# File 'lib/grape_entity/entity.rb', line 546 def is_defined_in_entity?(attribute) return false unless respond_to?(attribute, true) ancestors = self.class.ancestors ancestors.index(Grape::Entity) > ancestors.index(method(attribute).owner) end |
#presented ⇒ Object
466 467 468 469 470 471 472 |
# File 'lib/grape_entity/entity.rb', line 466 def presented if [:serializable] serializable_hash else self end end |
#root_exposure ⇒ Object
493 494 495 |
# File 'lib/grape_entity/entity.rb', line 493 def root_exposure self.class.root_exposure end |
#root_exposures ⇒ Object
489 490 491 |
# File 'lib/grape_entity/entity.rb', line 489 def root_exposures self.class.root_exposures end |
#serializable_hash(runtime_options = {}) ⇒ Object Also known as: as_json
The serializable hash is the Entity's primary output. It is the transformed hash for the given data model and is used as the basis for serialization to JSON and other formats.
512 513 514 515 516 517 518 |
# File 'lib/grape_entity/entity.rb', line 512 def serializable_hash( = {}) return nil if object.nil? opts = .merge( || {}) root_exposure.serializable_value(self, opts) end |
#to_json(options = {}) ⇒ Object
555 556 557 558 |
# File 'lib/grape_entity/entity.rb', line 555 def to_json( = {}) = .to_h if &.respond_to?(:to_h) MultiJson.dump(serializable_hash()) end |
#to_xml(options = {}) ⇒ Object
560 561 562 563 |
# File 'lib/grape_entity/entity.rb', line 560 def to_xml( = {}) = .to_h if &.respond_to?(:to_h) serializable_hash().to_xml() end |