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/deprecated.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/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: Deprecated, 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 default ].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
-
.[](val) ⇒ Object
Satisfies the respond_to?(:[]) check in Grape::DryTypes (>= 3.2) so Entity subclasses can be used as param types.
-
.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
- #arity_requirement_for(method_name) ⇒ Object
- #delegate_attribute(attribute) ⇒ Object
- #documentation ⇒ Object
- #ensure_block_arity!(block) ⇒ 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
- #required_arguments_summary(required_positional_arg_count, required_keyword_arg_count, variadic_positional) ⇒ Object
- #root_exposure ⇒ Object
- #root_exposures ⇒ Object
-
#serializable_hash(runtime_options = {}) ⇒ Object
(also: #as_json)
The serializable hash is the Entity’s primary output.
- #symbol_to_proc_wrapper?(block) ⇒ Boolean
- #to_json(options = {}) ⇒ Object
- #to_xml(options = {}) ⇒ Object
- #value_for(key, options = Options.new) ⇒ Object
Constructor Details
Class Attribute Details
.formatters ⇒ Hash
Returns all formatters that are registered for this and it’s ancestors
109 110 111 |
# File 'lib/grape_entity/entity.rb', line 109 def formatters @formatters ||= {} end |
Instance Attribute Details
#delegator ⇒ Object (readonly)
Returns the value of attribute delegator.
44 45 46 |
# File 'lib/grape_entity/entity.rb', line 44 def delegator @delegator end |
#object ⇒ Object (readonly)
Returns the value of attribute object.
44 45 46 |
# File 'lib/grape_entity/entity.rb', line 44 def object @object end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
44 45 46 |
# File 'lib/grape_entity/entity.rb', line 44 def @options end |
Class Method Details
.[](val) ⇒ Object
Satisfies the respond_to?(:[]) check in Grape::DryTypes (>= 3.2) so Entity subclasses can be used as param types.
133 134 135 |
# File 'lib/grape_entity/entity.rb', line 133 def [](val) val end |
.build_exposure_for_attribute(attribute, nesting_stack, options, block) ⇒ Object
rubocop:enable Layout/LineLength
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/grape_entity/entity.rb', line 222 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
261 262 263 |
# File 'lib/grape_entity/entity.rb', line 261 def self.can_unexpose? (@nesting_stack ||= []).empty? end |
.cannot_unexpose! ⇒ Object
265 266 267 |
# File 'lib/grape_entity/entity.rb', line 265 def self.cannot_unexpose! raise "You cannot call 'unexpose` inside of nesting exposure!" end |
.delegation_opts ⇒ Object
127 128 129 |
# File 'lib/grape_entity/entity.rb', line 127 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.
287 288 289 290 291 |
# File 'lib/grape_entity/entity.rb', line 287 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
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 |
# File 'lib/grape_entity/entity.rb', line 194 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
245 246 247 |
# File 'lib/grape_entity/entity.rb', line 245 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 takes a block with a single argument which is passed as the value of the exposed attribute.
319 320 321 322 323 |
# File 'lib/grape_entity/entity.rb', line 319 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
113 114 115 |
# File 'lib/grape_entity/entity.rb', line 113 def hash_access @hash_access ||= :to_sym end |
.hash_access=(value) ⇒ Object
117 118 119 120 121 122 123 124 125 |
# File 'lib/grape_entity/entity.rb', line 117 def hash_access=(value) @hash_access = case value when :to_s, :str, :string :to_s else :to_sym end end |
.inherited(subclass) ⇒ Object
140 141 142 143 144 145 |
# File 'lib/grape_entity/entity.rb', line 140 def self.inherited(subclass) subclass.root_exposure = root_exposure.dup subclass.formatters = formatters.dup super end |
.merge_options(options) ⇒ Object
Merges the given options with current block options.
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 |
# File 'lib/grape_entity/entity.rb', line 654 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"] ||= []) << existing_val new_val else (opts[:"#{key}_extras"] ||= []) << 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.
420 421 422 423 |
# File 'lib/grape_entity/entity.rb', line 420 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.
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 |
# File 'lib/grape_entity/entity.rb', line 442 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.
364 365 366 367 |
# File 'lib/grape_entity/entity.rb', line 364 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
460 461 462 463 464 465 466 467 |
# File 'lib/grape_entity/entity.rb', line 460 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.
241 242 243 |
# File 'lib/grape_entity/entity.rb', line 241 def self.root_exposures root_exposure.nested_exposures end |
.unexpose(*attributes) ⇒ Object
249 250 251 252 253 |
# File 'lib/grape_entity/entity.rb', line 249 def self.unexpose(*attributes) cannot_unexpose! unless can_unexpose? @documentation = nil root_exposures.delete_by(*attributes) end |
.unexpose_all ⇒ Object
255 256 257 258 259 |
# File 'lib/grape_entity/entity.rb', line 255 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.
683 684 685 686 687 688 689 690 |
# File 'lib/grape_entity/entity.rb', line 683 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.
278 279 280 281 282 |
# File 'lib/grape_entity/entity.rb', line 278 def self.() (@block_options ||= []).push(()) yield @block_options.pop end |
Instance Method Details
#arity_requirement_for(method_name) ⇒ Object
555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'lib/grape_entity/entity.rb', line 555 def arity_requirement_for(method_name) origin_method = object.method(method_name) parameters = origin_method.parameters required_positional_arg_count = parameters.count { |type, _| type == :req } required_keyword_arg_count = parameters.count { |type, _| type == :keyreq } return nil if required_positional_arg_count.zero? && required_keyword_arg_count.zero? [required_positional_arg_count, required_keyword_arg_count, parameters.any? { |type, _| type == :rest }] rescue NameError # Delegation wrappers and method_missing proxies may not expose a Method; let Ruby raise natively at call time. nil end |
#delegate_attribute(attribute) ⇒ Object
601 602 603 604 605 606 607 608 609 |
# File 'lib/grape_entity/entity.rb', line 601 def delegate_attribute(attribute) if is_defined_in_entity?(attribute) send(attribute) elsif delegator. delegator.delegate(attribute, **self.class.delegation_opts) else delegator.delegate(attribute) end end |
#documentation ⇒ Object
502 503 504 |
# File 'lib/grape_entity/entity.rb', line 502 def documentation self.class.documentation end |
#ensure_block_arity!(block) ⇒ Object
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/grape_entity/entity.rb', line 536 def ensure_block_arity!(block) # Strict anchor to match MRI Proc#to_s format for symbol-to-proc: #<Proc:0x0...(&:method_name) (lambda)> match = block.to_s.match(/\A#<Proc:(?:0x)?\h+\(&:(?<name>.+)\) \(lambda\)>\z/) return unless match # Unrecognized format -> bail safe rather than misidentify origin_method_name = match[:name].to_sym required_positional_arg_count, required_keyword_arg_count, variadic_positional = arity_requirement_for(origin_method_name) return unless required_positional_arg_count required_arguments = required_arguments_summary(required_positional_arg_count, required_keyword_arg_count, variadic_positional) raise ArgumentError, <<~MSG Cannot use `&:#{origin_method_name}` because that method expects #{required_arguments}. Symbol-to-proc shorthand only works for methods that can be called with no arguments. MSG end |
#exec_with_attribute(attribute, &block) ⇒ Object
593 594 595 |
# File 'lib/grape_entity/entity.rb', line 593 def exec_with_attribute(attribute, &block) instance_exec(delegate_attribute(attribute), &block) end |
#exec_with_object(options, &block) ⇒ Object
525 526 527 528 529 530 531 532 533 534 |
# File 'lib/grape_entity/entity.rb', line 525 def exec_with_object(, &block) if symbol_to_proc_wrapper?(block) ensure_block_arity!(block) instance_exec(object, &block) elsif block.arity == 1 instance_exec(object, &block) else instance_exec(object, , &block) end end |
#formatters ⇒ Object
506 507 508 |
# File 'lib/grape_entity/entity.rb', line 506 def formatters self.class.formatters end |
#inspect ⇒ Object
Prevent default serialization of :options or :delegator.
478 479 480 481 482 483 484 485 486 |
# File 'lib/grape_entity/entity.rb', line 478 def inspect object = serializable_hash if object.nil? "#<#{self.class.name}:#{object_id} nil>" else fields = object.map { |k, v| "#{k}=#{v}" } "#<#{self.class.name}:#{object_id} #{fields.join(' ')}>" end end |
#is_defined_in_entity?(attribute) ⇒ Boolean
611 612 613 614 615 616 |
# File 'lib/grape_entity/entity.rb', line 611 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
469 470 471 472 473 474 475 |
# File 'lib/grape_entity/entity.rb', line 469 def presented if [:serializable] serializable_hash else self end end |
#required_arguments_summary(required_positional_arg_count, required_keyword_arg_count, variadic_positional) ⇒ Object
569 570 571 572 573 574 575 576 577 578 579 580 581 582 |
# File 'lib/grape_entity/entity.rb', line 569 def required_arguments_summary(required_positional_arg_count, required_keyword_arg_count, variadic_positional) parts = [] unless required_positional_arg_count.zero? suffix = required_positional_arg_count == 1 ? 'argument' : 'arguments' suffix += ' or more' if variadic_positional parts << "#{required_positional_arg_count} #{suffix}" end unless required_keyword_arg_count.zero? suffix = required_keyword_arg_count == 1 ? 'keyword argument' : 'keyword arguments' parts << "#{required_keyword_arg_count} #{suffix}" end parts.join(' and ') end |
#root_exposure ⇒ Object
498 499 500 |
# File 'lib/grape_entity/entity.rb', line 498 def root_exposure self.class.root_exposure end |
#root_exposures ⇒ Object
494 495 496 |
# File 'lib/grape_entity/entity.rb', line 494 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.
517 518 519 520 521 522 523 |
# File 'lib/grape_entity/entity.rb', line 517 def serializable_hash( = {}) return nil if object.nil? opts = .merge( || {}) root_exposure.serializable_value(self, opts) end |
#symbol_to_proc_wrapper?(block) ⇒ Boolean
584 585 586 587 588 589 590 591 |
# File 'lib/grape_entity/entity.rb', line 584 def symbol_to_proc_wrapper?(block) params = block.parameters return false unless block.lambda? && block.source_location.nil? return false unless params.size >= 2 params[0].first == :req && params[1].first == :rest end |
#to_json(options = {}) ⇒ Object
620 621 622 623 |
# File 'lib/grape_entity/entity.rb', line 620 def to_json( = {}) = .to_h if &.respond_to?(:to_h) serializable_hash().to_json end |
#to_xml(options = {}) ⇒ Object
625 626 627 628 |
# File 'lib/grape_entity/entity.rb', line 625 def to_xml( = {}) = .to_h if &.respond_to?(:to_h) serializable_hash().to_xml() end |