rest_model 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.
- data/.gitignore +5 -0
- data/Gemfile +2 -0
- data/Guardfile +7 -0
- data/README.md +237 -0
- data/Rakefile +2 -0
- data/examples/all.rb +12 -0
- data/examples/embeds_many/invisible.rb +16 -0
- data/examples/embeds_many/simple.rb +13 -0
- data/examples/embeds_many/with_array.rb +10 -0
- data/examples/embeds_many/with_class_name.rb +13 -0
- data/examples/embeds_many/with_fields.rb +9 -0
- data/examples/embeds_many/with_if.rb +16 -0
- data/examples/embeds_many/with_nil_value.rb +12 -0
- data/examples/embeds_many/with_start_key.rb +13 -0
- data/examples/embeds_one/flattened.rb +12 -0
- data/examples/embeds_one/simple.rb +12 -0
- data/examples/embeds_one/with_class_name.rb +12 -0
- data/examples/embeds_one/with_if.rb +16 -0
- data/examples/embeds_one/with_start_key.rb +12 -0
- data/examples/has_many/simple.rb +28 -0
- data/examples/helper.rb +9 -0
- data/examples/initialize/embeds_many.rb +25 -0
- data/examples/initialize/embeds_many_array.rb +9 -0
- data/examples/initialize/embeds_one.rb +19 -0
- data/examples/initialize/simple.rb +8 -0
- data/examples/properties/array_serialization.rb +8 -0
- data/examples/properties/collections.rb +11 -0
- data/examples/properties/simple.rb +8 -0
- data/examples/properties/with_field.rb +8 -0
- data/examples/properties/with_field_path.rb +8 -0
- data/examples/properties/with_id.rb +8 -0
- data/examples/properties/with_if.rb +21 -0
- data/examples/properties/with_key_converter.rb +18 -0
- data/examples/properties/with_two_key_converters.rb +27 -0
- data/examples/properties/with_values.rb +10 -0
- data/examples/summarization/simple.rb +21 -0
- data/examples/to_input/embeds_many.rb +25 -0
- data/examples/to_input/embeds_many_without_key.rb +31 -0
- data/examples/to_input/embeds_one.rb +13 -0
- data/examples/to_input/embeds_one_without_key.rb +23 -0
- data/examples/to_input/flattened.rb +12 -0
- data/examples/to_input/serializables.rb +19 -0
- data/examples/to_input/simple.rb +8 -0
- data/examples/to_input/with_field.rb +8 -0
- data/examples/to_input/with_field_path.rb +8 -0
- data/examples/to_input/with_fields.rb +8 -0
- data/examples/to_input/without_key.rb +11 -0
- data/examples/to_input/without_nil.rb +11 -0
- data/lib/rest_model/configuration.rb +45 -0
- data/lib/rest_model/key/association.rb +42 -0
- data/lib/rest_model/key/builder.rb +30 -0
- data/lib/rest_model/key/embeddable/builder.rb +19 -0
- data/lib/rest_model/key/embeddable/response.rb +25 -0
- data/lib/rest_model/key/embeddable/retriever.rb +20 -0
- data/lib/rest_model/key/embeddable/sender.rb +41 -0
- data/lib/rest_model/key/embeddable.rb +29 -0
- data/lib/rest_model/key/href/response.rb +10 -0
- data/lib/rest_model/key/href.rb +10 -0
- data/lib/rest_model/key/property/builder.rb +27 -0
- data/lib/rest_model/key/property/response.rb +9 -0
- data/lib/rest_model/key/property/retriever.rb +24 -0
- data/lib/rest_model/key/property/sender.rb +41 -0
- data/lib/rest_model/key/property.rb +22 -0
- data/lib/rest_model/key/relation/builder.rb +24 -0
- data/lib/rest_model/key/relation/response.rb +38 -0
- data/lib/rest_model/key/relation.rb +23 -0
- data/lib/rest_model/key.rb +44 -0
- data/lib/rest_model/response.rb +42 -0
- data/lib/rest_model/serialization/boolean.rb +48 -0
- data/lib/rest_model/serialization/date.rb +19 -0
- data/lib/rest_model/serialization/enumerable.rb +15 -0
- data/lib/rest_model/serialization/float.rb +17 -0
- data/lib/rest_model/serialization/integer.rb +17 -0
- data/lib/rest_model/serialization/string.rb +13 -0
- data/lib/rest_model/source/path.rb +25 -0
- data/lib/rest_model/source/retriever.rb +48 -0
- data/lib/rest_model/source/sender.rb +38 -0
- data/lib/rest_model/version.rb +3 -0
- data/lib/rest_model.rb +101 -0
- data/rest_model.gemspec +26 -0
- data/spec/.DS_Store +0 -0
- data/spec/integration/embeds_many_spec.rb +57 -0
- data/spec/integration/embeds_one_spec.rb +41 -0
- data/spec/integration/has_many_spec.rb +27 -0
- data/spec/integration/property_spec.rb +69 -0
- data/spec/integration/summarization_spec.rb +12 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/examples.rb +29 -0
- data/spec/support/out.rb +17 -0
- data/spec/support/shared_examples.rb +20 -0
- data/spec/unit/configuration_spec.rb +24 -0
- data/spec/unit/key/association_spec.rb +69 -0
- data/spec/unit/key/embeddable/builder_spec.rb +26 -0
- data/spec/unit/key/embeddable/response_spec.rb +55 -0
- data/spec/unit/key/embeddable/retriever_spec.rb +38 -0
- data/spec/unit/key/embeddable_spec.rb +7 -0
- data/spec/unit/key/property/builder_spec.rb +154 -0
- data/spec/unit/key/property/response_spec.rb +22 -0
- data/spec/unit/key/property/retriever_spec.rb +31 -0
- data/spec/unit/key/property_spec.rb +39 -0
- data/spec/unit/key/relation/builder_spec.rb +53 -0
- data/spec/unit/key/relation/response_spec.rb +105 -0
- data/spec/unit/key/relation_spec.rb +71 -0
- data/spec/unit/key_spec.rb +101 -0
- data/spec/unit/response_spec.rb +44 -0
- data/spec/unit/rest_model_spec.rb +46 -0
- data/spec/unit/serialization/boolean_spec.rb +25 -0
- data/spec/unit/serialization/date_spec.rb +13 -0
- data/spec/unit/serialization/float_spec.rb +13 -0
- data/spec/unit/serialization/integer_spec.rb +13 -0
- data/spec/unit/serialization/string_spec.rb +11 -0
- data/spec/unit/source/path_spec.rb +62 -0
- data/spec/unit/source/retriever_spec.rb +85 -0
- metadata +260 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
class RestModel
|
2
|
+
class Relation
|
3
|
+
module Builder
|
4
|
+
def relation(name, options = {})
|
5
|
+
key Relation.new(name, options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def has_one(name, options = {})
|
9
|
+
options.merge!(many: false, has: true)
|
10
|
+
relation(name, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_many(name, options = {})
|
14
|
+
options.merge!(many: true, has: true)
|
15
|
+
relation(name, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def belongs_to(name, options = {})
|
19
|
+
options.merge!(many: false, has: false)
|
20
|
+
relation(name, options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class RestModel
|
2
|
+
class Relation
|
3
|
+
module Response
|
4
|
+
def to_resource(parent)
|
5
|
+
included = parent.__send__(name)
|
6
|
+
return {} unless visible?(parent) and included
|
7
|
+
{name => resource_from_included(included)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_relation(parent)
|
11
|
+
{rel: name, href: href(parent)}
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def relation_name(parent)
|
17
|
+
relation = parent.class.relations.find do |key|
|
18
|
+
key.resource_class == self.resource_class
|
19
|
+
end
|
20
|
+
relation.name or fail
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource_from_included(included)
|
24
|
+
options = {root: false}
|
25
|
+
one? ?
|
26
|
+
included.resource(options)
|
27
|
+
: included.map {|r| r.resource(options)}
|
28
|
+
end
|
29
|
+
|
30
|
+
def href(parent)
|
31
|
+
id_key = parent.class.id_key.name
|
32
|
+
has? ?
|
33
|
+
"#{RestModel::Configuration.host}/#{parent.class.resource_name}/#{parent.send(id_key)}/#{relation_name(parent)}"
|
34
|
+
: "#{RestModel::Configuration.host}/#{relation_name(parent).to_s.pluralize}/#{parent.send(id_key)}/#{parent.class.resource_name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class RestModel
|
2
|
+
class Relation < Association
|
3
|
+
autoload :Response, "rest_model/key/relation/response"
|
4
|
+
include Response
|
5
|
+
|
6
|
+
def initialize(name, options = {})
|
7
|
+
super
|
8
|
+
@has = options.fetch(:has, false)
|
9
|
+
end
|
10
|
+
|
11
|
+
def has?
|
12
|
+
@has
|
13
|
+
end
|
14
|
+
|
15
|
+
def belongs?
|
16
|
+
!has?
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(value, resource = nil)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class RestModel
|
2
|
+
class Key
|
3
|
+
attr_accessor :name, :model, :options, :summarize
|
4
|
+
|
5
|
+
def initialize(name, options = {})
|
6
|
+
@name = name
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def convert_input_keys
|
11
|
+
options.fetch(:convert_input_keys, model.try(:convert_input_keys) ||
|
12
|
+
RestModel::Configuration.convert_input_keys)
|
13
|
+
end
|
14
|
+
|
15
|
+
def source_path
|
16
|
+
return @source_path.clone if @source_path
|
17
|
+
|
18
|
+
@source_path = Source::Path.resolve(options, convert_input_keys)
|
19
|
+
|
20
|
+
if @source_path.empty? and !root_path?
|
21
|
+
@source_path = convert_input_keys.call([name])
|
22
|
+
end
|
23
|
+
|
24
|
+
@source_path.clone
|
25
|
+
end
|
26
|
+
|
27
|
+
def root_path?
|
28
|
+
path_definition = options.slice(:start_key, :field)
|
29
|
+
path_definition.any? and path_definition.first[1].empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def present?(resource)
|
33
|
+
!options[:if] ? true : resource.instance_eval(&options[:if])
|
34
|
+
end
|
35
|
+
|
36
|
+
def visible?(resource)
|
37
|
+
!present?(resource) ? false : case visible = options[:visible]
|
38
|
+
when nil then true
|
39
|
+
when Proc then resource.instance_eval(&visible)
|
40
|
+
else visible
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Response
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def resource(options = {})
|
6
|
+
root = options.fetch(:root, true)
|
7
|
+
|
8
|
+
{}.tap do |resource|
|
9
|
+
resource_keys(options).inject(resource) do |buffer, key|
|
10
|
+
buffer.merge!(key.to_resource(self))
|
11
|
+
end
|
12
|
+
|
13
|
+
if root and self.class.relations.any? and !options[:summarize]
|
14
|
+
resource.merge!({link: link})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def link
|
20
|
+
self.class.relations.map {|key| key.to_relation(self)}
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource_keys(options)
|
24
|
+
summarize?(options) ? self.class.summarized_keys + [Href.new]
|
25
|
+
: self.class.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def summarize?(options)
|
29
|
+
options[:summarize] and self.class.summarized_keys.any?
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def normalize(model, options = {})
|
34
|
+
model.kind_of?(Enumerable) ?
|
35
|
+
{entries: model.map {|m| m.resource(options.merge(summarize: true))}}
|
36
|
+
: model.resource(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
alias :resources :normalize
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Serialization
|
3
|
+
class Boolean
|
4
|
+
MAPPINGS = {
|
5
|
+
true => true,
|
6
|
+
"true" => true,
|
7
|
+
"TRUE" => true,
|
8
|
+
"1" => true,
|
9
|
+
1 => true,
|
10
|
+
1.0 => true,
|
11
|
+
"x" => true,
|
12
|
+
"X" => true,
|
13
|
+
"t" => true,
|
14
|
+
"T" => true,
|
15
|
+
false => false,
|
16
|
+
"false" => false,
|
17
|
+
"FALSE" => false,
|
18
|
+
"0" => false,
|
19
|
+
0 => false,
|
20
|
+
0.0 => false,
|
21
|
+
"" => false,
|
22
|
+
" " => false,
|
23
|
+
"f" => false,
|
24
|
+
"F" => false,
|
25
|
+
nil => false
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
def self.serialize(value)
|
29
|
+
MAPPINGS[value].tap do |bool|
|
30
|
+
fail "value not serializable: #{value}" if bool.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.desserialize(value)
|
35
|
+
fail unless boolean?(value)
|
36
|
+
|
37
|
+
value ? RestModel.configuration.true_value
|
38
|
+
: RestModel.configuration.false_value
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def self.boolean?(value)
|
44
|
+
!!value == value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Serialization
|
3
|
+
class Date
|
4
|
+
def self.serialize(value)
|
5
|
+
::Date.parse value
|
6
|
+
rescue ArgumentError
|
7
|
+
raise "value not serializable: #{value}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.desserialize(value)
|
11
|
+
date = value.kind_of?(::Date) ? value : ::Date.parse(value)
|
12
|
+
format = RestModel.configuration.date_format
|
13
|
+
format ? date.strftime(format) : date.to_s
|
14
|
+
rescue ArgumentError
|
15
|
+
raise "value not desserializable: #{value}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Serialization
|
3
|
+
class Enumerable
|
4
|
+
def self.serialize(value)
|
5
|
+
fail "value not serializable: #{value}" unless value.kind_of?(::Enumerable)
|
6
|
+
value
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.desserialize(value)
|
10
|
+
fail "value not desserializable: #{value}" unless value.kind_of?(::Enumerable)
|
11
|
+
value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Serialization
|
3
|
+
class Float
|
4
|
+
def self.serialize(value)
|
5
|
+
Float(value)
|
6
|
+
rescue ArgumentError
|
7
|
+
raise "value not serializable: #{value}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.desserialize(value)
|
11
|
+
Float(value)
|
12
|
+
rescue ArgumentError
|
13
|
+
raise "value not desserializable: #{value}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Serialization
|
3
|
+
class Integer
|
4
|
+
def self.serialize(value)
|
5
|
+
Integer(value)
|
6
|
+
rescue ArgumentError
|
7
|
+
raise "value not serializable: #{value}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.desserialize(value)
|
11
|
+
Integer(value)
|
12
|
+
rescue ArgumentError
|
13
|
+
raise "value not desserializable: #{value}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Source
|
3
|
+
module Path
|
4
|
+
def self.resolve(options, convert_input_keys = Key::Converter::DefaultHandler)
|
5
|
+
convert_input_keys.call keys_for_path(custom_path(options))
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def self.custom_path(options)
|
11
|
+
options.slice(:start_key, :field)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.keys_for_path(path)
|
15
|
+
return [] if path.empty?
|
16
|
+
|
17
|
+
if path.values.first.kind_of?(Symbol)
|
18
|
+
[path.values.first]
|
19
|
+
else
|
20
|
+
path.values.first.to_s.split('.')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Source
|
3
|
+
module Retriever
|
4
|
+
def from_source!(input, options = {})
|
5
|
+
from_source(input, options.merge(fail: true))
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_source(input, options = {})
|
9
|
+
entries = prepare_entries(input, options)
|
10
|
+
|
11
|
+
if entries.any? {|e| !e.kind_of?(Hash)}
|
12
|
+
fail "invalid input" if options[:fail]
|
13
|
+
return []
|
14
|
+
end
|
15
|
+
|
16
|
+
entries.collect do |item|
|
17
|
+
handle_item(item.with_indifferent_access, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
alias :parse! :from_source!
|
22
|
+
alias :parse :from_source
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def prepare_entries(input, options)
|
27
|
+
path = Source::Path.resolve(options, RestModel::Configuration.convert_input_keys)
|
28
|
+
Array.wrap digg(input, path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def handle_item(item, options)
|
32
|
+
self.new.tap do |resource|
|
33
|
+
keys.each do |key|
|
34
|
+
begin
|
35
|
+
resource.__send__("#{key.name}=", key.from_source(item, resource)) if key.present?(resource)
|
36
|
+
rescue => exception
|
37
|
+
raise exception if options[:fail]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def digg(input, path)
|
44
|
+
path.inject(input) {|buffer, key| buffer = buffer[key]}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class RestModel
|
2
|
+
module Source
|
3
|
+
module Sender
|
4
|
+
def to_source!(options = {})
|
5
|
+
to_source(options.merge(fail: true))
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_source(options = {})
|
9
|
+
source = {}
|
10
|
+
root_options = {without_nil: options[:without_nil], fail: options[:fail]}
|
11
|
+
|
12
|
+
keys_to_source(options).each do |key|
|
13
|
+
value = __send__(key.name)
|
14
|
+
key_options = options.fetch(key.name, {}).merge(root_options)
|
15
|
+
source.merge! key.to_source!(value, self, key_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
source.with_indifferent_access
|
19
|
+
end
|
20
|
+
|
21
|
+
alias :to_input! :to_source!
|
22
|
+
alias :to_input :to_source
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def keys_to_source(options)
|
27
|
+
self.class.keys.clone.tap do |keys|
|
28
|
+
keys.reject! {|k| k.kind_of?(Relation)}
|
29
|
+
keys.reject! {|k| Array(options[:without]).include?(k.name)}
|
30
|
+
|
31
|
+
keys.reject! do |key|
|
32
|
+
__send__(key.name).nil? and options[:without_nil]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/rest_model.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require "date"
|
2
|
+
require "active_support/inflector"
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute_accessors"
|
5
|
+
require "active_support/core_ext/object/try"
|
6
|
+
require "active_support/core_ext/array/wrap"
|
7
|
+
require "active_support/core_ext/hash/slice"
|
8
|
+
require "active_support/core_ext/hash/indifferent_access"
|
9
|
+
|
10
|
+
require "rest_model/source/path"
|
11
|
+
require "rest_model/source/retriever"
|
12
|
+
require "rest_model/source/sender"
|
13
|
+
require "rest_model/response"
|
14
|
+
require "rest_model/serialization/boolean"
|
15
|
+
require "rest_model/serialization/date"
|
16
|
+
require "rest_model/serialization/enumerable"
|
17
|
+
require "rest_model/serialization/float"
|
18
|
+
require "rest_model/serialization/integer"
|
19
|
+
require "rest_model/serialization/string"
|
20
|
+
require "rest_model/key"
|
21
|
+
require "rest_model/key/property"
|
22
|
+
require "rest_model/key/property/builder"
|
23
|
+
require "rest_model/key/association"
|
24
|
+
require "rest_model/key/relation"
|
25
|
+
require "rest_model/key/relation/builder"
|
26
|
+
require "rest_model/key/embeddable"
|
27
|
+
require "rest_model/key/embeddable/builder"
|
28
|
+
require "rest_model/key/href"
|
29
|
+
require "rest_model/key/builder"
|
30
|
+
require "rest_model/configuration"
|
31
|
+
|
32
|
+
class RestModel
|
33
|
+
extend Key::Builder
|
34
|
+
extend Source::Retriever
|
35
|
+
include Source::Sender
|
36
|
+
include Response
|
37
|
+
include Serialization
|
38
|
+
|
39
|
+
cattr_accessor :id_key
|
40
|
+
cattr_writer :resource_name
|
41
|
+
|
42
|
+
def initialize(attrs = {})
|
43
|
+
return if attrs.nil? or attrs.empty?
|
44
|
+
|
45
|
+
attrs = attrs.with_indifferent_access
|
46
|
+
|
47
|
+
self.class.keys.each do |key|
|
48
|
+
__send__("#{key.name}=", key.from_hash(attrs[key.name])) if key.present?(self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_attributes(attrs = {})
|
53
|
+
return if attrs.nil? or attrs.empty?
|
54
|
+
|
55
|
+
attrs = attrs.with_indifferent_access
|
56
|
+
|
57
|
+
self.class.keys.each do |key|
|
58
|
+
value = attrs[key.name]
|
59
|
+
|
60
|
+
if value and key.present?(self)
|
61
|
+
__send__("#{key.name}=", key.from_hash(value, __send__(key.name)))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def resource_id
|
69
|
+
__send__(id_key.name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.keys
|
73
|
+
@keys ||= []
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.summarized_keys
|
77
|
+
@summarized_keys ||= []
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.relations
|
81
|
+
@keys.find_all {|k| k.kind_of?(Relation)}
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.resource_name(custom_name = nil)
|
85
|
+
@resource_name ||= custom_name or to_s.demodulize.tableize
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.convert_input_keys(converter = nil)
|
89
|
+
@convert_input_keys = converter if converter
|
90
|
+
@convert_input_keys
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.method_added(method_name)
|
94
|
+
return unless not_allowed_names.include?(method_name.to_s)
|
95
|
+
puts "warning: redefining '#{method_name}' may cause serious problems"
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.not_allowed_names
|
99
|
+
%w(resource_id resource link)
|
100
|
+
end
|
101
|
+
end
|
data/rest_model.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rest_model/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rest_model"
|
7
|
+
s.version = RestModel::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Victor Rodrigues", "Vinicius Higa", "William Yokoi"]
|
10
|
+
s.email = ["victorcrodrigues@gmail.com", "vinicius.higa@locaweb.com.br", "thekina@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/rodrigues/rest_model"
|
12
|
+
s.summary = %q{}
|
13
|
+
s.description = %q{}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency "activesupport", "~> 3.0"
|
21
|
+
s.add_dependency "i18n", ">= 0.5"
|
22
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
23
|
+
s.add_development_dependency "guard", "~> 0.5"
|
24
|
+
s.add_development_dependency "guard-rspec", "~> 0.4"
|
25
|
+
s.add_development_dependency "growl", "~> 1.0"
|
26
|
+
end
|
data/spec/.DS_Store
ADDED
Binary file
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe_example "embeds_many/simple" do
|
6
|
+
it "parses first embedded item" do
|
7
|
+
root.items[0].id.should == 2000
|
8
|
+
end
|
9
|
+
|
10
|
+
it "parses second embedded item" do
|
11
|
+
root.items[1].id.should == 2001
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe_example "embeds_many/invisible" do
|
16
|
+
it "parses invisible items correctly" do
|
17
|
+
root.locale.should == "pt-BR"
|
18
|
+
root.names.should == {"en"=>"Woot", "pt-BR"=>"Úia", "es"=>"Me gusta"}
|
19
|
+
end
|
20
|
+
|
21
|
+
it "doesn't show embedded items in resource" do
|
22
|
+
root.resource.should_not have_key("locale")
|
23
|
+
root.resource.should_not have_key("names")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe_example "embeds_many/with_array" do
|
28
|
+
it ""
|
29
|
+
end
|
30
|
+
|
31
|
+
describe_example "embeds_many/with_class_name" do
|
32
|
+
it "parses embedded item" do
|
33
|
+
root.items[0].id.should == 2000
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe_example "embeds_many/with_if" do
|
38
|
+
context "when an embeddable has a conditional proc (:if)" do
|
39
|
+
context "and it evaluates to true" do
|
40
|
+
it "parses embedded item" do
|
41
|
+
root_with_items.items.first.id.should == "2000"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "and it evaluates to false" do
|
46
|
+
it "doesn't parse embedded item" do
|
47
|
+
root_without_items.items.should_not be
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe_example "embeds_many/with_start_key" do
|
54
|
+
it "parses embedded items" do
|
55
|
+
root.items.first.id.should == "2000"
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe_example "embeds_one/simple" do
|
4
|
+
it 'parses embedded item' do
|
5
|
+
root.item.id.should == "2000"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe_example "embeds_one/with_class_name" do
|
10
|
+
context 'when a different class name is used for embeddable' do
|
11
|
+
it 'parses embedded item' do
|
12
|
+
root.item.id.should == "2000"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe_example "embeds_one/with_if" do
|
18
|
+
context 'when an embeddable has a conditional proc (:if)' do
|
19
|
+
context 'and it evaluates to true' do
|
20
|
+
it 'parses embedded item' do
|
21
|
+
root_with_item.id.should == "1"
|
22
|
+
root_with_item.item.id.should == "2000"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'and it evaluates to false' do
|
27
|
+
it "doesn't parse embedded item" do
|
28
|
+
root_without_item.id.should == "100"
|
29
|
+
root_without_item.item.should_not be
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe_example "embeds_one/with_start_key" do
|
36
|
+
context 'when there is a start key to parse input' do
|
37
|
+
it 'parses embedded item' do
|
38
|
+
root.item.id.should == "2000"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe_example "has_many/simple" do
|
4
|
+
it 'parses properly' do
|
5
|
+
root.id.should == 123
|
6
|
+
root.login.should == 'jackiechan2010'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#resource" do
|
10
|
+
subject {root.resource}
|
11
|
+
|
12
|
+
it 'has a link' do
|
13
|
+
subject[:link].should be_an(Array)
|
14
|
+
end
|
15
|
+
|
16
|
+
[:services, :billing, :devops].each do |rel|
|
17
|
+
it "has a #{rel} relation" do
|
18
|
+
subject[:link].any? {|l| l[:rel] == rel}.should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has a href for #{rel} relation" do
|
22
|
+
link = subject[:link].find {|l| l[:rel] == rel}
|
23
|
+
link[:href].should =~ %r{/customers/123/#{rel}}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|