lurch 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +2 -0
- data/LICENSE.md +9 -0
- data/README.md +229 -0
- data/Rakefile +12 -0
- data/TODO.md +22 -0
- data/lib/lurch/changeset.rb +32 -0
- data/lib/lurch/client.rb +92 -0
- data/lib/lurch/collection.rb +111 -0
- data/lib/lurch/configuration.rb +14 -0
- data/lib/lurch/error.rb +13 -0
- data/lib/lurch/errors/bad_request.rb +6 -0
- data/lib/lurch/errors/conflict.rb +6 -0
- data/lib/lurch/errors/forbidden.rb +6 -0
- data/lib/lurch/errors/json_api_error.rb +29 -0
- data/lib/lurch/errors/not_found.rb +6 -0
- data/lib/lurch/errors/not_loaded.rb +13 -0
- data/lib/lurch/errors/relationship_not_loaded.rb +9 -0
- data/lib/lurch/errors/resource_not_loaded.rb +9 -0
- data/lib/lurch/errors/server_error.rb +6 -0
- data/lib/lurch/errors/unauthorized.rb +6 -0
- data/lib/lurch/errors/unprocessable_entity.rb +6 -0
- data/lib/lurch/inflector.rb +60 -0
- data/lib/lurch/logger.rb +7 -0
- data/lib/lurch/middleware/json_api_request.rb +22 -0
- data/lib/lurch/middleware/json_api_response.rb +24 -0
- data/lib/lurch/paginator.rb +71 -0
- data/lib/lurch/payload_builder.rb +43 -0
- data/lib/lurch/query.rb +143 -0
- data/lib/lurch/query_builder.rb +26 -0
- data/lib/lurch/railtie.rb +9 -0
- data/lib/lurch/relationship/has_many.rb +17 -0
- data/lib/lurch/relationship/has_one.rb +21 -0
- data/lib/lurch/relationship/linked.rb +40 -0
- data/lib/lurch/relationship.rb +26 -0
- data/lib/lurch/resource.rb +82 -0
- data/lib/lurch/store.rb +149 -0
- data/lib/lurch/store_configuration.rb +27 -0
- data/lib/lurch/stored_resource.rb +63 -0
- data/lib/lurch/uri_builder.rb +32 -0
- data/lib/lurch/version.rb +3 -0
- data/lib/lurch.rb +65 -0
- data/lurch.gemspec +26 -0
- data/lurch.gif +0 -0
- data/test/helpers/lurch_test.rb +40 -0
- data/test/helpers/response_factory.rb +193 -0
- data/test/lurch/test_configuration.rb +20 -0
- data/test/lurch/test_create_resources.rb +55 -0
- data/test/lurch/test_delete_resources.rb +27 -0
- data/test/lurch/test_errors.rb +29 -0
- data/test/lurch/test_fetch_relationships.rb +50 -0
- data/test/lurch/test_fetch_resources.rb +77 -0
- data/test/lurch/test_inflector.rb +13 -0
- data/test/lurch/test_paginated_collections.rb +125 -0
- data/test/lurch/test_queries.rb +104 -0
- data/test/lurch/test_relationship.rb +17 -0
- data/test/lurch/test_resource.rb +34 -0
- data/test/lurch/test_update_relationships.rb +53 -0
- data/test/lurch/test_update_resources.rb +56 -0
- data/test/test_helper.rb +15 -0
- metadata +235 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Inflector
|
3
|
+
def initialize(inflection_mode, types_mode)
|
4
|
+
define_encode_key(inflection_mode)
|
5
|
+
define_encode_type(types_mode)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.classify(s)
|
9
|
+
Inflecto.classify(s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.decode_key(key)
|
13
|
+
Inflecto.underscore(key.to_s).to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.decode_type(type)
|
17
|
+
Inflecto.pluralize(decode_key(type)).to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_encode_key(inflection_mode)
|
21
|
+
case inflection_mode
|
22
|
+
when :dasherize
|
23
|
+
define_singleton_method(:encode_key) { |key| Inflecto.dasherize(key.to_s) }
|
24
|
+
when :underscore
|
25
|
+
define_singleton_method(:encode_key) { |key| Inflecto.underscore(key.to_s) }
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Invalid inflection mode: #{inflection_mode}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def define_encode_type(types_mode)
|
32
|
+
case types_mode
|
33
|
+
when :pluralize
|
34
|
+
define_singleton_method(:encode_type) do |type|
|
35
|
+
key = encode_key(type)
|
36
|
+
Inflecto.pluralize(key)
|
37
|
+
end
|
38
|
+
when :singularize
|
39
|
+
define_singleton_method(:encode_type) do |type|
|
40
|
+
key = encode_key(type)
|
41
|
+
Inflecto.singularize(key)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
raise ArgumentError, "Invalid types mode: #{types_mode}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def encode_keys(hash)
|
49
|
+
hash.each_with_object({}) do |(key, value), acc|
|
50
|
+
acc[encode_key(key)] = block_given? ? yield(value) : value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def encode_types(hash)
|
55
|
+
hash.each_with_object({}) do |(key, value), acc|
|
56
|
+
acc[encode_type(key)] = block_given? ? yield(value) : value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/lurch/logger.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Lurch
|
2
|
+
module Middleware
|
3
|
+
class JSONApiRequest < Faraday::Middleware
|
4
|
+
CONTENT_TYPE = "Content-Type".freeze
|
5
|
+
ACCEPT = "Accept".freeze
|
6
|
+
MIME_TYPE = "application/vnd.api+json".freeze
|
7
|
+
|
8
|
+
dependency do
|
9
|
+
require "json" unless defined?(::JSON)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
env[:request_headers][CONTENT_TYPE] = MIME_TYPE
|
14
|
+
env[:request_headers][ACCEPT] = MIME_TYPE
|
15
|
+
env[:body] = JSON.generate(env[:body]) if env[:body].is_a?(Hash)
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Faraday::Request.register_middleware jsonapi: -> { Lurch::Middleware::JSONApiRequest }
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Lurch
|
2
|
+
module Middleware
|
3
|
+
class JSONApiResponse < Faraday::Middleware
|
4
|
+
MIME_TYPE = "application/vnd.api+json".freeze
|
5
|
+
|
6
|
+
dependency do
|
7
|
+
require "json" unless defined?(::JSON)
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(conn)
|
11
|
+
@app.call(conn).on_complete do |env|
|
12
|
+
env[:body_raw] = env[:body]
|
13
|
+
begin
|
14
|
+
env[:body] = JSON.parse(env[:body])
|
15
|
+
rescue StandardError => err
|
16
|
+
env[:parse_error] = err
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Faraday::Response.register_middleware jsonapi: -> { Lurch::Middleware::JSONApiResponse }
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Paginator
|
3
|
+
def initialize(store, document, inflector, config)
|
4
|
+
@store = store
|
5
|
+
@links = document["links"]
|
6
|
+
@meta = document["meta"]
|
7
|
+
@config = config
|
8
|
+
@inflector = inflector
|
9
|
+
end
|
10
|
+
|
11
|
+
def record_count
|
12
|
+
key = @inflector.encode_key(@config.pagination_record_count_key)
|
13
|
+
@meta[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def page_count
|
17
|
+
key = @inflector.encode_key(@config.pagination_page_count_key)
|
18
|
+
@meta[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_collection
|
22
|
+
next_link && @store.load_from_url(next_link)
|
23
|
+
end
|
24
|
+
|
25
|
+
def prev_collection
|
26
|
+
prev_link && @store.load_from_url(prev_link)
|
27
|
+
end
|
28
|
+
|
29
|
+
def first_collection
|
30
|
+
first_link && @store.load_from_url(first_link)
|
31
|
+
end
|
32
|
+
|
33
|
+
def last_collection
|
34
|
+
last_link && @store.load_from_url(last_link)
|
35
|
+
end
|
36
|
+
|
37
|
+
def next?
|
38
|
+
!!next_link
|
39
|
+
end
|
40
|
+
|
41
|
+
def prev?
|
42
|
+
!!prev_link
|
43
|
+
end
|
44
|
+
|
45
|
+
def first?
|
46
|
+
!!first_link
|
47
|
+
end
|
48
|
+
|
49
|
+
def last?
|
50
|
+
!!last_link
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def next_link
|
56
|
+
@links["next"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def prev_link
|
60
|
+
@links["prev"]
|
61
|
+
end
|
62
|
+
|
63
|
+
def first_link
|
64
|
+
@links["first"]
|
65
|
+
end
|
66
|
+
|
67
|
+
def last_link
|
68
|
+
@links["last"]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Lurch
|
2
|
+
class PayloadBuilder
|
3
|
+
def initialize(inflector)
|
4
|
+
@inflector = inflector
|
5
|
+
end
|
6
|
+
|
7
|
+
def build(input, identifier_only = false)
|
8
|
+
{ "data" => data(input, identifier_only) }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def data(input, identifier_only)
|
14
|
+
if input.is_a?(Enumerable)
|
15
|
+
input.map { |resource| resource_object_for(resource, identifier_only) }
|
16
|
+
else
|
17
|
+
resource_object_for(input, identifier_only)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource_object_for(resource, identifier_only)
|
22
|
+
return nil if resource.nil?
|
23
|
+
{
|
24
|
+
"id" => resource.id,
|
25
|
+
"type" => @inflector.encode_type(resource.type),
|
26
|
+
"attributes" => attributes_for(resource, identifier_only),
|
27
|
+
"relationships" => relationships_for(resource, identifier_only)
|
28
|
+
}.reject { |_, v| v.nil? || (v.respond_to?(:empty?) && v.empty?) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def attributes_for(resource, identifier_only)
|
32
|
+
return {} if identifier_only
|
33
|
+
@inflector.encode_keys(resource.attributes)
|
34
|
+
end
|
35
|
+
|
36
|
+
def relationships_for(resource, identifier_only)
|
37
|
+
return {} if identifier_only
|
38
|
+
@inflector.encode_keys(resource.relationships) do |value|
|
39
|
+
PayloadBuilder.new(@inflector).build(value, true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/lurch/query.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Query
|
3
|
+
def initialize(store, inflector)
|
4
|
+
@store = store
|
5
|
+
@inflector = inflector
|
6
|
+
@filter = {}
|
7
|
+
@include = []
|
8
|
+
@fields = Hash.new { [] }
|
9
|
+
@sort = []
|
10
|
+
@page = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def filter(params)
|
14
|
+
@filter.merge!(params)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def include(*relationship_paths)
|
19
|
+
@include += relationship_paths
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def fields(type, fields = nil)
|
24
|
+
type, fields = [@type, type] if type.is_a?(Array) && fields.nil?
|
25
|
+
@fields[type] += fields
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def sort(*sort_keys)
|
30
|
+
@sort += sort_keys.map { |sort_key| sort_key.is_a?(Hash) ? sort_key : { sort_key => :asc } }
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def page(params)
|
35
|
+
@page.merge!(params)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def type(type)
|
40
|
+
@type = Inflector.decode_type(type)
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# def link(uri)
|
45
|
+
# # TODO: fail if type already set
|
46
|
+
# # TODO: set uri and merge in query params from provided uri if any
|
47
|
+
# self
|
48
|
+
# end
|
49
|
+
|
50
|
+
def all
|
51
|
+
raise ArgumentError, "No type specified for query" if @type.nil?
|
52
|
+
@store.load_from_url(uri_builder.resources_uri(@type, to_query))
|
53
|
+
end
|
54
|
+
|
55
|
+
def find(id)
|
56
|
+
raise ArgumentError, "No type specified for query" if @type.nil?
|
57
|
+
raise ArgumentError, "Can't perform find for `nil`" if id.nil?
|
58
|
+
@store.peek(@type, id) || @store.load_from_url(uri_builder.resource_uri(@type, id, to_query))
|
59
|
+
end
|
60
|
+
|
61
|
+
def save(changeset)
|
62
|
+
raise ArgumentError, "No type specified for query" if @type.nil?
|
63
|
+
raise ArgumentError, "Type mismatch" if @type != changeset.type
|
64
|
+
@store.save(changeset, to_query)
|
65
|
+
end
|
66
|
+
|
67
|
+
def insert(changeset)
|
68
|
+
raise ArgumentError, "No type specified for query" if @type.nil?
|
69
|
+
raise ArgumentError, "Type mismatch" if @type != changeset.type
|
70
|
+
@store.insert(changeset, to_query)
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete(resource)
|
74
|
+
raise ArgumentError, "No type specified for query" if @type.nil?
|
75
|
+
raise ArgumentError, "Type mismatch" if @type != resource.type
|
76
|
+
@store.delete(resource, to_query)
|
77
|
+
end
|
78
|
+
|
79
|
+
def inspect
|
80
|
+
type = @type.nil? ? "" : "[#{Inflector.classify(@type)}]"
|
81
|
+
query = to_query
|
82
|
+
query = query.empty? ? "" : " #{query.inspect}"
|
83
|
+
"#<#{self.class}#{type}#{query}>"
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def uri_builder
|
89
|
+
@uri_builder ||= URIBuilder.new(@inflector)
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_query
|
93
|
+
QueryBuilder.new(
|
94
|
+
{
|
95
|
+
filter: filter_query,
|
96
|
+
include: include_query,
|
97
|
+
fields: fields_query,
|
98
|
+
sort: sort_query,
|
99
|
+
page: page_query
|
100
|
+
}.merge(other_uri_params)
|
101
|
+
).encode
|
102
|
+
end
|
103
|
+
|
104
|
+
def other_uri_params
|
105
|
+
# TODO: existing non-standard uri query params from the provided uri (if any)
|
106
|
+
{}
|
107
|
+
end
|
108
|
+
|
109
|
+
def filter_query
|
110
|
+
@inflector.encode_keys(@filter)
|
111
|
+
end
|
112
|
+
|
113
|
+
def include_query
|
114
|
+
@include.map { |path| @inflector.encode_key(path) }.compact.uniq.join(",")
|
115
|
+
end
|
116
|
+
|
117
|
+
def fields_query
|
118
|
+
@inflector.encode_types(@fields) do |fields|
|
119
|
+
fields.map { |field| @inflector.encode_key(field) }.compact.uniq.join(",")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def sort_query
|
124
|
+
@sort.flat_map(&:to_a).map { |(key, direction)| sort_key(key, direction) }.join(",")
|
125
|
+
end
|
126
|
+
|
127
|
+
def sort_key(key, direction)
|
128
|
+
encoded_key = @inflector.encode_key(key)
|
129
|
+
case direction
|
130
|
+
when :asc
|
131
|
+
encoded_key
|
132
|
+
when :desc
|
133
|
+
"-#{encoded_key}"
|
134
|
+
else
|
135
|
+
raise ArgumentError, "Invalid sort direction #{direction}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def page_query
|
140
|
+
@page
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Lurch
|
2
|
+
class QueryBuilder
|
3
|
+
def initialize(params)
|
4
|
+
@params = Hash(params)
|
5
|
+
end
|
6
|
+
|
7
|
+
def encode
|
8
|
+
encode_value(@params)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def encode_value(value, key = nil)
|
14
|
+
case value
|
15
|
+
when Hash then value.map { |k, v| encode_value(v, append_key(key, k)) }.reject(&:empty?).join("&")
|
16
|
+
when Array then value.map { |v| encode_value(v, "#{key}[]") }.reject(&:empty?).join("&")
|
17
|
+
else
|
18
|
+
value.to_s.empty? ? "" : "#{key}=#{CGI.escape(value.to_s)}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def append_key(root_key, key)
|
23
|
+
root_key.nil? ? key : "#{root_key}[#{key}]"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Relationship
|
3
|
+
class HasMany < Relationship
|
4
|
+
def initialize(store, relationship_key, document)
|
5
|
+
@store = store
|
6
|
+
@relationship_key = relationship_key
|
7
|
+
@document = document
|
8
|
+
@data = @document["data"].map { |resource_identifier| Resource.new(@store, resource_identifier["type"], resource_identifier["id"]) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
suffix = @data.first ? "[#{Inflector.classify(@data.first.type)}]" : ""
|
13
|
+
"#<#{self.class}#{suffix} size: #{@data.size}>"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Relationship
|
3
|
+
class HasOne < Relationship
|
4
|
+
def initialize(store, relationship_key, document)
|
5
|
+
@store = store
|
6
|
+
@relationship_key = relationship_key
|
7
|
+
if document["data"].nil?
|
8
|
+
@data = nil
|
9
|
+
else
|
10
|
+
@type = Inflector.decode_type(document["data"]["type"])
|
11
|
+
@id = document["data"]["id"]
|
12
|
+
@data = Resource.new(@store, @type, @id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
@data.nil? ? "#<#{self.class} nil>" : "#<#{self.class}[#{Inflector.classify(@type)}] id: #{@id.inspect}>"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Relationship
|
3
|
+
class Linked < Relationship
|
4
|
+
def initialize(store, relationship_key, document)
|
5
|
+
@store = store
|
6
|
+
@relationship_key = relationship_key
|
7
|
+
@href = document["links"]["related"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch
|
11
|
+
@data = @store.load_from_url(@href)
|
12
|
+
end
|
13
|
+
|
14
|
+
# def filter(*args)
|
15
|
+
# @store.query.link(@href).filter(*args)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def include(*args)
|
19
|
+
# @store.query.link(@href).include(*args)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def fields(*args)
|
23
|
+
# @store.query.link(@href).fields(*args)
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def sort(*args)
|
27
|
+
# @store.query.link(@href).sort(*args)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def page(*args)
|
31
|
+
# @store.query.link(@href).page(*args)
|
32
|
+
# end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
suffix = loaded? ? " \"loaded\"" : ""
|
36
|
+
"#<#{self.class} href: #{@href.inspect}#{suffix}>"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Relationship
|
3
|
+
attr_reader :data
|
4
|
+
|
5
|
+
def self.from_document(store, relationship_key, document)
|
6
|
+
return Relationship::HasMany.new(store, relationship_key, document) if document.key?("data") && document["data"].is_a?(Array)
|
7
|
+
return Relationship::HasOne.new(store, relationship_key, document) if document.key?("data")
|
8
|
+
return Relationship::Linked.new(store, relationship_key, document) if document.key?("links") && document["links"].key?("related")
|
9
|
+
raise ArgumentError, "Invalid relationship document"
|
10
|
+
end
|
11
|
+
|
12
|
+
def loaded?
|
13
|
+
!!defined?(@data)
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to_missing?(method, all)
|
17
|
+
raise Errors::RelationshipNotLoaded, @relationship_key unless loaded?
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(method, *arguments, &block)
|
22
|
+
raise Errors::RelationshipNotLoaded, @relationship_key unless loaded?
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Lurch
|
2
|
+
class Resource
|
3
|
+
attr_reader :id, :type
|
4
|
+
|
5
|
+
def initialize(store, type, id)
|
6
|
+
@store = store
|
7
|
+
@type = Inflector.decode_type(type)
|
8
|
+
@id = id
|
9
|
+
end
|
10
|
+
|
11
|
+
def loaded?
|
12
|
+
!!resource_from_store
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch
|
16
|
+
@store.from(type).find(id)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes
|
21
|
+
raise Errors::ResourceNotLoaded, resource_class_name unless loaded?
|
22
|
+
|
23
|
+
resource_from_store.attributes
|
24
|
+
end
|
25
|
+
|
26
|
+
def relationships
|
27
|
+
raise Errors::ResourceNotLoaded, resource_class_name unless loaded?
|
28
|
+
|
29
|
+
resource_from_store.relationships
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
eql?(other)
|
34
|
+
end
|
35
|
+
|
36
|
+
def eql?(other)
|
37
|
+
id == other.id && type == other.type
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](attribute)
|
41
|
+
raise Errors::ResourceNotLoaded, resource_class_name unless loaded?
|
42
|
+
|
43
|
+
resource_from_store.attribute(attribute)
|
44
|
+
end
|
45
|
+
|
46
|
+
def resource_class_name
|
47
|
+
Inflector.classify(type)
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
inspection = if loaded?
|
52
|
+
attributes.map { |name, value| "#{name}: #{value.inspect}" }.join(", ")
|
53
|
+
else
|
54
|
+
"not loaded"
|
55
|
+
end
|
56
|
+
"#<#{self.class}[#{resource_class_name}] id: #{id.inspect}, #{inspection}>"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def resource_from_store
|
62
|
+
@store.resource_from_store(type, id)
|
63
|
+
end
|
64
|
+
|
65
|
+
def respond_to_missing?(method, all)
|
66
|
+
return super unless loaded?
|
67
|
+
resource_from_store.attribute?(method) || resource_from_store.relationship?(method) || super
|
68
|
+
end
|
69
|
+
|
70
|
+
def method_missing(method, *arguments, &block)
|
71
|
+
raise Errors::ResourceNotLoaded, resource_class_name unless loaded?
|
72
|
+
|
73
|
+
return resource_from_store.attribute(method) if resource_from_store.attribute?(method)
|
74
|
+
if resource_from_store.relationship?(method)
|
75
|
+
rel = resource_from_store.relationship(method)
|
76
|
+
return rel.loaded? ? rel.data : rel
|
77
|
+
end
|
78
|
+
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|