Module: Eipiai::Resource

Includes:
Objectifiable, Representable
Included in:
ApiResource, HealthResource
Defined in:
lib/eipiai/resources/base.rb,
lib/eipiai/resources/concerns/objectifiable.rb,
lib/eipiai/resources/concerns/representable.rb

Overview

Resource

The base resource which can be included in regular Webmachine::Resource objects. It provides sensible defaults for a full-features REST API endpoint.

Defined Under Namespace

Modules: Objectifiable, Representable

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Includes the correct resource into the class, depending on its name.

If the resource is called ‘ItemResource`, the `Item` part is considered singular, and thus the `SingularResource` is included into the class.



24
25
26
27
28
29
30
31
32
# File 'lib/eipiai/resources/base.rb', line 24

def self.included(base)
  subject_class_name = base.name.demodulize.chomp('Resource')

  if subject_class_name == subject_class_name.singularize
    base.send(:include, SingularResource)
  else
    base.send(:include, CollectionResource)
  end
end

Instance Method Details

#base_uriObject



236
237
238
# File 'lib/eipiai/resources/base.rb', line 236

def base_uri
  request.base_uri
end

#content_types_acceptedObject



147
148
149
# File 'lib/eipiai/resources/base.rb', line 147

def content_types_accepted
  [['application/json', :from_json]]
end

#content_types_providedObject



143
144
145
# File 'lib/eipiai/resources/base.rb', line 143

def content_types_provided
  [['application/hal+json', :to_hal_json], ['application/json', :to_json]]
end

#create_uriObject



240
241
242
# File 'lib/eipiai/resources/base.rb', line 240

def create_uri
  Addressable::URI.join(base_uri, create_path)
end

#finish_requestObject



155
156
157
# File 'lib/eipiai/resources/base.rb', line 155

def finish_request
  response.headers['Cache-Control'] = cache_control_header_value
end

#malformed_request?true, false

malformed_request?

return ‘true` if content_type is of type `application/json`, and the JSON request body cannot be parsed.

Examples:

valid JSON payload

post('/items', '{ "uid": "hello" }', 'Content-Type': 'application/json')
resource.malformed_request? # => false

invalid JSON payload

post('/items', '{ invalid! }', 'Content-Type': 'application/json')
resource.malformed_request? # => true

Returns:

  • (true, false)


114
115
116
117
118
119
120
# File 'lib/eipiai/resources/base.rb', line 114

def malformed_request?
  return false unless request.json? && request.body.to_s.present?

  JSON.parse(request.body.to_s) && false
rescue JSON::ParserError
  json_error_body(:invalid_json)
end

#new_objectObject?

new_object

New object, instantiated by passing the provided ‘params` into the object’s ‘#from_hash` method.

The only requirement is that the object responds to ‘#from_hash`, that method accepts a hash of parameters, and it returns the object itself.

Examples:

resource.new_object.class # => Item

Returns:

  • (Object, nil)

    instantiated object, or nil if not found



230
231
232
233
234
# File 'lib/eipiai/resources/base.rb', line 230

def new_object
  return if object_class.nil?

  @new_object ||= object_class.new.from_hash(params)
end

#params(body = request.body.to_s) ⇒ Hash

params

Given a string in JSON format, returns the hash representation of that object.

If the input is invalid JSON, an empty hash is returned.

If no argument is given, ‘request.body` is used as the JSON input.

Examples:

Parse valid JSON request

resource.params('{ "hello": "world" }') # => { 'hello' => 'world' }

Parse invalid JSON request

resource.params('invalid') #=> {}

Parameters:

  • body (String) (defaults to: request.body.to_s)

    JSON provided as a string

Returns:

  • (Hash)

    JSON string, converted to a hash



177
178
179
180
181
# File 'lib/eipiai/resources/base.rb', line 177

def params(body = request.body.to_s)
  @params ||= JSON.parse(body)
rescue JSON::ParserError
  {}
end

#post_is_create?Boolean

Returns:

  • (Boolean)


151
152
153
# File 'lib/eipiai/resources/base.rb', line 151

def post_is_create?
  true
end

#query_keysArray<String>

query_keys

Returns an array of optional query component keys this resource accepts. The keys are added to the link relation as templated variables.

Defaults to an empty array, not exposing any optional query keys.

Returns:

  • (Array<String>)

    array of query keys



74
75
76
# File 'lib/eipiai/resources/base.rb', line 74

def query_keys
  []
end

#resource_relationString

resource_relation

The name to be used when linking to this resource as a relation.

The string returned from this method will become the name of the link in the HAL+JSON representation of this resource.

Examples:

resource.resource_relation # => 'items'

Returns:

  • (String)

    resource relation name



46
47
48
# File 'lib/eipiai/resources/base.rb', line 46

def resource_relation
  resource_name.demodulize.underscore
end

#service_available?true, false

service_available?

If the service is unavailable, this method should return false. By default, this method calls the ‘HealthCheck#healthy?` method, which in turn returns `true` or `false`, depending on the state of the service and its dependencies.

When the service is unavailable, the ‘Cache-Control` header is set to not allow any caching by proxies/clients, and sets the `Retry-After` header to tell the client to retry again in 60 seconds.

Returns:

  • (true, false)


91
92
93
94
95
96
97
# File 'lib/eipiai/resources/base.rb', line 91

def service_available?
  return true if Eipiai::HealthCheck.new.healthy?

  response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, private'
  response.headers['Retry-After'] = '60'
  false
end

#to_h(obj = object) ⇒ Hash

to_h

Given an object, calls ‘#to_h` on that object,

If the object’s ‘to_h` implementation accepts any arguments, the hash `{ request: request }` is sent as its first argument.

In practice, this method is used without any parameters, causing the method to call ‘represented`, which represents a Roar representer. This in turn converts the represented object to a HAL/JSON compatible hash representation.

Examples:

item = Item.new(uid: 'hello')
get('/item/hello')
resource.to_h(item)['uid'] # => 'hello'

Parameters:

  • obj (Object) (defaults to: object)

    to call #to_h on

Returns:

  • (Hash)

    hash representation of the object



203
204
205
206
207
# File 'lib/eipiai/resources/base.rb', line 203

def to_h(obj = object)
  obj.method(:to_h).arity.zero? ? obj.to_h : obj.to_h(request: request)
rescue
  obj
end

#to_hal_jsonObject



213
214
215
# File 'lib/eipiai/resources/base.rb', line 213

def to_hal_json
  to_h.to_json
end

#to_jsonObject



209
210
211
# File 'lib/eipiai/resources/base.rb', line 209

def to_json
  to_h.to_json
end

#top_level_relation?true, false

top_level_relation?

return ‘true` if the link relation to this resource should be added to the top-level API entrypoint.

It is good practice to keep most link relations out of the top-level entrypoint, instead opting for nesting the resource within its parent resource links relations.

Returns:

  • (true, false)


61
62
63
# File 'lib/eipiai/resources/base.rb', line 61

def top_level_relation?
  false
end

#unprocessable_entity?true, false

unprocessable_entity?

return ‘true` if content_type is of type `application/json`, and the newly generated object reports back as “invalid”.

Examples:

invalid object

post('/users', '{}', 'Content-Type': 'application/json')
resource.unprocessable_entity? # => true

valid object

post('/users', '{ "uid": "bartsimpson" }', 'Content-Type': 'application/json')
resource.unprocessable_entity? # => false

Returns:

  • (true, false)


137
138
139
140
141
# File 'lib/eipiai/resources/base.rb', line 137

def unprocessable_entity?
  return false unless request.json? && new_object.respond_to?(:invalid?)

  new_object.invalid? && json_error_body(*new_object.errors)
end