rest_model 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|