lurch 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|