tagcrumbs-tagcrumbs 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/License.txt +22 -0
- data/Manifest.txt +177 -0
- data/PostInstall.txt +2 -0
- data/README.rdoc +45 -0
- data/Rakefile +35 -0
- data/TODO.txt +17 -0
- data/bin/tagcrumbs +5 -0
- data/examples/address_update.rb +15 -0
- data/examples/comment_new.rb +19 -0
- data/examples/favorite_new.rb +17 -0
- data/examples/filter_new.rb +15 -0
- data/examples/friendship_create.rb +19 -0
- data/examples/link_new.rb +16 -0
- data/examples/location_update.rb +16 -0
- data/examples/picture_update.rb +16 -0
- data/examples/placemark_new_simple.rb +24 -0
- data/examples/placemark_new_suggestions.rb +24 -0
- data/examples/profile_link_new.rb +14 -0
- data/examples/profile_update.rb +12 -0
- data/examples/request_authorization.rb +25 -0
- data/examples/settings_update.rb +12 -0
- data/examples/subscription_new.rb +15 -0
- data/examples/tagcrumbs_find.rb +25 -0
- data/examples/tagcrumbs_search.rb +7 -0
- data/examples/user.rb +28 -0
- data/examples/user_recommendation_new.rb +15 -0
- data/lib/tagcrumbs.rb +232 -0
- data/lib/tagcrumbs/builders/builder.rb +45 -0
- data/lib/tagcrumbs/builders/json_builder.rb +38 -0
- data/lib/tagcrumbs/builders/xml_builder.rb +30 -0
- data/lib/tagcrumbs/cli.rb +268 -0
- data/lib/tagcrumbs/config_store.rb +35 -0
- data/lib/tagcrumbs/exceptions.rb +13 -0
- data/lib/tagcrumbs/node.rb +43 -0
- data/lib/tagcrumbs/parsers/json_parser.rb +79 -0
- data/lib/tagcrumbs/parsers/parser.rb +24 -0
- data/lib/tagcrumbs/parsers/xml_parser.rb +54 -0
- data/lib/tagcrumbs/proxy.rb +32 -0
- data/lib/tagcrumbs/requestor.rb +140 -0
- data/lib/tagcrumbs/resources/array.rb +108 -0
- data/lib/tagcrumbs/resources/models/accessors.rb +252 -0
- data/lib/tagcrumbs/resources/models/activity.rb +19 -0
- data/lib/tagcrumbs/resources/models/address.rb +16 -0
- data/lib/tagcrumbs/resources/models/city.rb +13 -0
- data/lib/tagcrumbs/resources/models/comment.rb +12 -0
- data/lib/tagcrumbs/resources/models/country.rb +15 -0
- data/lib/tagcrumbs/resources/models/fanship.rb +12 -0
- data/lib/tagcrumbs/resources/models/favorite.rb +22 -0
- data/lib/tagcrumbs/resources/models/filter.rb +32 -0
- data/lib/tagcrumbs/resources/models/flag.rb +10 -0
- data/lib/tagcrumbs/resources/models/friendship.rb +12 -0
- data/lib/tagcrumbs/resources/models/geoname.rb +8 -0
- data/lib/tagcrumbs/resources/models/link.rb +13 -0
- data/lib/tagcrumbs/resources/models/location.rb +13 -0
- data/lib/tagcrumbs/resources/models/model.rb +148 -0
- data/lib/tagcrumbs/resources/models/picture.rb +28 -0
- data/lib/tagcrumbs/resources/models/place.rb +17 -0
- data/lib/tagcrumbs/resources/models/placemark.rb +30 -0
- data/lib/tagcrumbs/resources/models/profile.rb +16 -0
- data/lib/tagcrumbs/resources/models/profile_link.rb +12 -0
- data/lib/tagcrumbs/resources/models/root.rb +19 -0
- data/lib/tagcrumbs/resources/models/settings.rb +14 -0
- data/lib/tagcrumbs/resources/models/state.rb +14 -0
- data/lib/tagcrumbs/resources/models/subscription.rb +13 -0
- data/lib/tagcrumbs/resources/models/suggestions.rb +11 -0
- data/lib/tagcrumbs/resources/models/tag.rb +6 -0
- data/lib/tagcrumbs/resources/models/tagcrumb.rb +58 -0
- data/lib/tagcrumbs/resources/models/user.rb +101 -0
- data/lib/tagcrumbs/resources/models/user_recommendation.rb +18 -0
- data/lib/tagcrumbs/resources/resource.rb +76 -0
- data/lib/tagcrumbs/validations.rb +301 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/fixtures/activity.json +210 -0
- data/spec/fixtures/activity.xml +66 -0
- data/spec/fixtures/address.json +183 -0
- data/spec/fixtures/address.xml +58 -0
- data/spec/fixtures/city.json +88 -0
- data/spec/fixtures/city.xml +34 -0
- data/spec/fixtures/comment.json +286 -0
- data/spec/fixtures/comment.xml +87 -0
- data/spec/fixtures/country.json +32 -0
- data/spec/fixtures/country.xml +13 -0
- data/spec/fixtures/fanship.json +180 -0
- data/spec/fixtures/fanship.xml +54 -0
- data/spec/fixtures/favorite.json +332 -0
- data/spec/fixtures/favorite.xml +109 -0
- data/spec/fixtures/filter.json +243 -0
- data/spec/fixtures/filter.xml +80 -0
- data/spec/fixtures/friendship.json +180 -0
- data/spec/fixtures/friendship.xml +54 -0
- data/spec/fixtures/geoname.json +18 -0
- data/spec/fixtures/geoname.xml +6 -0
- data/spec/fixtures/json_parser.json +25 -0
- data/spec/fixtures/link.json +288 -0
- data/spec/fixtures/link.xml +88 -0
- data/spec/fixtures/location.json +204 -0
- data/spec/fixtures/location.xml +71 -0
- data/spec/fixtures/picture.json +154 -0
- data/spec/fixtures/picture.png +0 -0
- data/spec/fixtures/picture.xml +49 -0
- data/spec/fixtures/placemark.json +357 -0
- data/spec/fixtures/placemark.xml +58 -0
- data/spec/fixtures/placemarks.json +887 -0
- data/spec/fixtures/placemarks.xml +348 -0
- data/spec/fixtures/profile.json +282 -0
- data/spec/fixtures/profile.xml +100 -0
- data/spec/fixtures/profile_link.json +156 -0
- data/spec/fixtures/profile_link.xml +49 -0
- data/spec/fixtures/root.json +46 -0
- data/spec/fixtures/root.xml +14 -0
- data/spec/fixtures/settings.json +147 -0
- data/spec/fixtures/settings.xml +45 -0
- data/spec/fixtures/state.json +62 -0
- data/spec/fixtures/state.xml +23 -0
- data/spec/fixtures/subscription.json +283 -0
- data/spec/fixtures/subscription.xml +86 -0
- data/spec/fixtures/suggestions.json +1877 -0
- data/spec/fixtures/suggestions.xml +724 -0
- data/spec/fixtures/tag.json +9 -0
- data/spec/fixtures/tag.xml +4 -0
- data/spec/fixtures/user.json +105 -0
- data/spec/fixtures/user.xml +31 -0
- data/spec/fixtures/user_recommendation.json +349 -0
- data/spec/fixtures/user_recommendation.xml +106 -0
- data/spec/fixtures/validation_errors.json +5 -0
- data/spec/fixtures/validation_errors.xml +5 -0
- data/spec/fixtures/xml_parser.xml +13 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +75 -0
- data/spec/tagcrumbs/builders/builder_spec.rb +57 -0
- data/spec/tagcrumbs/builders/json_builder_spec.rb +47 -0
- data/spec/tagcrumbs/builders/xml_builder_spec.rb +34 -0
- data/spec/tagcrumbs/exceptions_spec.rb +16 -0
- data/spec/tagcrumbs/node_spec.rb +42 -0
- data/spec/tagcrumbs/parsers/json_parser_spec.rb +117 -0
- data/spec/tagcrumbs/parsers/parser_spec.rb +25 -0
- data/spec/tagcrumbs/parsers/xml_parser_spec.rb +117 -0
- data/spec/tagcrumbs/proxy_spec.rb +48 -0
- data/spec/tagcrumbs/requestor_spec.rb +62 -0
- data/spec/tagcrumbs/resources/array_spec.rb +62 -0
- data/spec/tagcrumbs/resources/models/accessors_spec.rb +123 -0
- data/spec/tagcrumbs/resources/models/activity_spec.rb +33 -0
- data/spec/tagcrumbs/resources/models/address_spec.rb +24 -0
- data/spec/tagcrumbs/resources/models/city_spec.rb +33 -0
- data/spec/tagcrumbs/resources/models/comment_spec.rb +23 -0
- data/spec/tagcrumbs/resources/models/country_spec.rb +26 -0
- data/spec/tagcrumbs/resources/models/fanship_spec.rb +28 -0
- data/spec/tagcrumbs/resources/models/favorite_spec.rb +28 -0
- data/spec/tagcrumbs/resources/models/filter_spec.rb +43 -0
- data/spec/tagcrumbs/resources/models/flag_spec.rb +5 -0
- data/spec/tagcrumbs/resources/models/friendship_spec.rb +28 -0
- data/spec/tagcrumbs/resources/models/geoname_spec.rb +18 -0
- data/spec/tagcrumbs/resources/models/link_spec.rb +24 -0
- data/spec/tagcrumbs/resources/models/location_spec.rb +28 -0
- data/spec/tagcrumbs/resources/models/model_spec.rb +27 -0
- data/spec/tagcrumbs/resources/models/picture_spec.rb +42 -0
- data/spec/tagcrumbs/resources/models/place_spec.rb +12 -0
- data/spec/tagcrumbs/resources/models/placemark_spec.rb +61 -0
- data/spec/tagcrumbs/resources/models/profile_link_spec.rb +29 -0
- data/spec/tagcrumbs/resources/models/profile_spec.rb +43 -0
- data/spec/tagcrumbs/resources/models/root_spec.rb +57 -0
- data/spec/tagcrumbs/resources/models/settings_spec.rb +32 -0
- data/spec/tagcrumbs/resources/models/state_spec.rb +30 -0
- data/spec/tagcrumbs/resources/models/subscription_spec.rb +28 -0
- data/spec/tagcrumbs/resources/models/suggestions_spec.rb +33 -0
- data/spec/tagcrumbs/resources/models/tag_spec.rb +16 -0
- data/spec/tagcrumbs/resources/models/tagcrumb_spec.rb +21 -0
- data/spec/tagcrumbs/resources/models/user_recommendation_spec.rb +35 -0
- data/spec/tagcrumbs/resources/models/user_spec.rb +128 -0
- data/spec/tagcrumbs/resources/resource_spec.rb +62 -0
- data/spec/tagcrumbs/validations_spec.rb +27 -0
- data/spec/tagcrumbs_spec.rb +103 -0
- data/tagcrumbs.gemspec +59 -0
- data/tasks/rspec.rake +21 -0
- metadata +325 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
module Tagcrumbs
|
2
|
+
# A parser for JSON
|
3
|
+
class JsonParser < Parser
|
4
|
+
attr_accessor :_name # save the name of the json hash
|
5
|
+
|
6
|
+
def initialize(parser_or_document)
|
7
|
+
if parser_or_document.is_a?(String)
|
8
|
+
tmp = JSON.parse(parser_or_document)
|
9
|
+
self._name = tmp.keys.first
|
10
|
+
self.document = tmp[self._name]
|
11
|
+
elsif(parser_or_document.is_a?(Tagcrumbs::JsonParser))
|
12
|
+
self.document = parser_or_document.document
|
13
|
+
else
|
14
|
+
self.document = parser_or_document
|
15
|
+
end
|
16
|
+
|
17
|
+
self.format = :json
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get a node with a specific name from the document
|
21
|
+
def get_node(name)
|
22
|
+
self.document[name.to_s]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Is this a stub and therefore only a "link" in the webservice or is a resource loaded?
|
26
|
+
def node_loaded?
|
27
|
+
badgerfish_json_loaded?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get all child nodes with a specific name
|
31
|
+
def nodes_with_name(name)
|
32
|
+
self.document[name] || []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get all attributes from the badgerfish JSON that (all keys starting with '@')
|
36
|
+
def get_attributes
|
37
|
+
attributes_from_badgerfish_json
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get one attribute from the JSON
|
41
|
+
def get_attribute(attribute)
|
42
|
+
self.document["@#{attribute}"]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the value of an element ('$' key in badgerfish JSON)
|
46
|
+
def get_value
|
47
|
+
self.document['$']
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get the name of the current element
|
51
|
+
def name
|
52
|
+
_name || self.document.keys.select{|k| k.first != '@'}.first
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def attributes_from_badgerfish_json
|
58
|
+
hash = {}
|
59
|
+
|
60
|
+
self.document.each do |k, v|
|
61
|
+
if k.first == '@'
|
62
|
+
hash[k[1..-1]] = v
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
hash
|
67
|
+
end
|
68
|
+
|
69
|
+
# is there anything else besides badgerfish style attributes?
|
70
|
+
def badgerfish_json_loaded?
|
71
|
+
self.document.each do |k, v|
|
72
|
+
return true if k.first != '@'
|
73
|
+
end
|
74
|
+
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Tagcrumbs
|
2
|
+
# Parser is an abstract class that provides common functions for all parsers and that helps to get a correct parser for
|
3
|
+
# a specific format
|
4
|
+
class Parser
|
5
|
+
attr_accessor :document, :format
|
6
|
+
|
7
|
+
# Create a parser for a specific format (xml or json)
|
8
|
+
# +document_or_parser+ can either be a Parser or a document, if it is a Parser it will be returned as it is,
|
9
|
+
# otherwise a parser will be created out of the document
|
10
|
+
def self.new_for_format(document_or_parser, format)
|
11
|
+
if document_or_parser.is_a?(Tagcrumbs::Parser) # allow to also pass a document right into the method
|
12
|
+
document_or_parser
|
13
|
+
else
|
14
|
+
case format
|
15
|
+
when :xml: XmlParser.new(document_or_parser)
|
16
|
+
when :json: JsonParser.new(document_or_parser)
|
17
|
+
else
|
18
|
+
raise UnsupportedFormat
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Tagcrumbs
|
2
|
+
class XmlParser < Parser
|
3
|
+
|
4
|
+
def initialize(parser_or_document)
|
5
|
+
if parser_or_document.is_a?(String)
|
6
|
+
xml = Hpricot.XML(parser_or_document)
|
7
|
+
self.document = xml.root ? xml.root : xml
|
8
|
+
elsif(parser_or_document.is_a?(Tagcrumbs::XmlParser))
|
9
|
+
self.document = parser_or_document.document
|
10
|
+
else
|
11
|
+
self.document = parser_or_document
|
12
|
+
end
|
13
|
+
|
14
|
+
self.format = :xml
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get a node with a specific name from the document
|
18
|
+
def get_node(name)
|
19
|
+
self.document.children_of_type(name.to_s).first
|
20
|
+
end
|
21
|
+
|
22
|
+
# Does the document contain a resource or is it just a "link" in the webservice?
|
23
|
+
def node_loaded?
|
24
|
+
#self.document.innerText.present?
|
25
|
+
!self.document.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get all children with name
|
29
|
+
def nodes_with_name(name)
|
30
|
+
self.document.children_of_type(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get all attributes
|
34
|
+
def get_attributes
|
35
|
+
self.document.attributes
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get one specific attribute
|
39
|
+
def get_attribute(attribute)
|
40
|
+
self.document.get_attribute(attribute)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the value of the tag
|
44
|
+
def get_value
|
45
|
+
self.document.innerHTML
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get the name of the document
|
49
|
+
def name
|
50
|
+
self.document.name
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Tagcrumbs
|
2
|
+
# The Proxy class is used in resource associations to delay loading of a resource for as long as possible.
|
3
|
+
# Some method calls are directly forwarded to the resource, for others the resource is loaded first from the webservice
|
4
|
+
# and afterwards the call is made.
|
5
|
+
# The Proxy tries to be as transparent as possible so that you basically never know that you have a proxy object instead of a real resource.
|
6
|
+
class Proxy
|
7
|
+
UNPROXIED_METHODS = [:reload, :resource_url, :properties, :loaded?, :class] # TODO: figure out what other methods might be needed?
|
8
|
+
|
9
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ } # http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
|
10
|
+
|
11
|
+
def initialize(object)
|
12
|
+
@object = object
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
# Forward all method calls to the proxied object, reload if necessary
|
17
|
+
def method_missing(method_name, *args, &block)
|
18
|
+
#puts "** Proxying #{method_name}. Loaded: #{@object.loaded?}"
|
19
|
+
|
20
|
+
if @object.loaded? || UNPROXIED_METHODS.include?(method_name) #|| !@object.respond_to?(method_name) # having this doesn't allow first etc on arrays
|
21
|
+
@object.send(method_name, *args, &block)
|
22
|
+
else
|
23
|
+
#puts "** Reloading"
|
24
|
+
@object.reload if @object.resource_url.present?
|
25
|
+
@object.send(method_name, *args, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Tagcrumbs
|
2
|
+
# The Requestor class is used to make the actual requests to the Tagcrumbs web service
|
3
|
+
# It uses the OAuth gem to make the requests.
|
4
|
+
|
5
|
+
class Requestor
|
6
|
+
attr_accessor :options, :response
|
7
|
+
|
8
|
+
# A Requestor can have custom options. If no custom options are specified the general Tagcrumbs.options are used.
|
9
|
+
# You can use this to have a Requestor with different consumer tokens for example.
|
10
|
+
def initialize(options = {})
|
11
|
+
options.reverse_merge!(Tagcrumbs.options)
|
12
|
+
self.options = options.with_indifferent_access
|
13
|
+
end
|
14
|
+
|
15
|
+
# ================
|
16
|
+
# = OAuth Tokens =
|
17
|
+
# ================
|
18
|
+
|
19
|
+
# Returns the OAuth consumer token
|
20
|
+
def consumer
|
21
|
+
@consumer ||= ::OAuth::Consumer.new(options[:consumer_token], options[:consumer_secret], options[:consumer_options])
|
22
|
+
end
|
23
|
+
|
24
|
+
# Requests a Request Token from the web service or builds it from the options
|
25
|
+
def request_token
|
26
|
+
return @request_token if defined?(@request_token)
|
27
|
+
|
28
|
+
if options[:request_token] && options[:request_secret]
|
29
|
+
@request_token = ::OAuth::RequestToken.new(consumer, options[:request_token], options[:request_secret])
|
30
|
+
else
|
31
|
+
@request_token = consumer.get_request_token
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Exchanges a Request Token against an Access Token that has to be saved and used for all further calls to the webservice.
|
36
|
+
def exchange_request_token!
|
37
|
+
@access_token = request_token.get_access_token
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the configured access token. Calls should be made with the get, post, put and delete methods, direct access to this
|
41
|
+
# should usually not be needed.
|
42
|
+
def access_token
|
43
|
+
return @access_token if defined?(@access_token)
|
44
|
+
|
45
|
+
if options[:access_token] && options[:access_secret]
|
46
|
+
@access_token = ::OAuth::AccessToken.new(consumer, options[:access_token], options[:access_secret])
|
47
|
+
else
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# ========
|
53
|
+
# = HTTP =
|
54
|
+
# ========
|
55
|
+
|
56
|
+
# Create a custom request to the webservice
|
57
|
+
# If an access token is configured it will be used to make webservice calls (3-legged oauth)
|
58
|
+
# If a consumer is configured and it is a get request only it will make 2-legged oauth calls
|
59
|
+
def request(http_method, path, query_string = {}, body = '', headers = {})
|
60
|
+
url = path_with_query_string(path, query_string)
|
61
|
+
arguments = body.present? ? [body, default_headers.merge(headers)] : [default_headers.merge(headers)]
|
62
|
+
|
63
|
+
if access_token # 3-legged request with access token
|
64
|
+
response = self.access_token.request(http_method, url, *arguments)
|
65
|
+
elsif http_method == :get && consumer # 2-legged consumer only request
|
66
|
+
response = self.consumer.request(:get, url, nil, {}, *arguments)
|
67
|
+
else
|
68
|
+
raise InvalidRequest.new("Setup the Access Token first or only make get requests with a valid consumer.")
|
69
|
+
end
|
70
|
+
|
71
|
+
handle_response(response)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create a get request on path, optionally pass a hash of query parameters
|
75
|
+
# Can also be used with 2-legged oauth to request public resources
|
76
|
+
def get(path, query_string = {})
|
77
|
+
request(:get, path, query_string)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Post body to path, access_token needed
|
81
|
+
def post(path, body='')
|
82
|
+
request(:post, path, {}, body)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Put body to path, access_token needed
|
86
|
+
def put(path, body='')
|
87
|
+
request(:put, path, {}, body)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Delete a resource, access_token needed
|
91
|
+
def delete(path)
|
92
|
+
request(:delete, path)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return the format of the response object
|
96
|
+
def response_format
|
97
|
+
raise 'Request a resource first' unless response
|
98
|
+
|
99
|
+
Tagcrumbs.supported_formats.each do |format, content_types|
|
100
|
+
return format if content_types.include?(response.content_type)
|
101
|
+
end
|
102
|
+
|
103
|
+
:xml
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
# Build a URL from a path and a hash of query string parameters
|
108
|
+
def path_with_query_string(path, arguments = {})
|
109
|
+
if arguments.present?
|
110
|
+
path + '?' + arguments.to_param
|
111
|
+
else
|
112
|
+
path
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Handle responses from the webservice, raise an error on unexpected HTTP response codes
|
117
|
+
def handle_response(response)
|
118
|
+
self.response = response
|
119
|
+
case response.code
|
120
|
+
when '200', '201', '422': # ok, created, invalid
|
121
|
+
response
|
122
|
+
else
|
123
|
+
response.error!
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set the Content-Type and Accept HTTP Headers to the correct values depending on format
|
128
|
+
def default_headers(format = nil)
|
129
|
+
format ||= self.options[:format]
|
130
|
+
|
131
|
+
raise UnsupportedFormat unless Tagcrumbs.supported_formats[format]
|
132
|
+
|
133
|
+
{
|
134
|
+
"Content-Type" => Tagcrumbs.supported_formats[format].first,
|
135
|
+
"Accept" => Tagcrumbs.supported_formats[format].join(', ')
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Tagcrumbs
|
2
|
+
# Basically a regular Array that holds many Resources.
|
3
|
+
# Used to get collections from the webservice.
|
4
|
+
class Array < Resource
|
5
|
+
ATTRIBUTES = [:total_entries, :pages, :per_page, :page, :sort, :order, :next_page, :previous_page]
|
6
|
+
|
7
|
+
attr_accessor :array
|
8
|
+
attr_reader *ATTRIBUTES
|
9
|
+
|
10
|
+
# set instance variables and automatically convert them to real integers if they are an integer
|
11
|
+
ATTRIBUTES.each do |a|
|
12
|
+
define_method "#{a}=" do |value|
|
13
|
+
begin
|
14
|
+
typecasted_value = Integer(value)
|
15
|
+
rescue ArgumentError
|
16
|
+
typecasted_value = value
|
17
|
+
ensure
|
18
|
+
self.instance_variable_set("@#{a}", typecasted_value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
reset!
|
25
|
+
end
|
26
|
+
|
27
|
+
# Reset the state of the Array
|
28
|
+
def reset!
|
29
|
+
super
|
30
|
+
@array = []
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Replace the Array with the next page of elements if there is one
|
36
|
+
def next_page!
|
37
|
+
reload(next_page) if next_page.present?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Replace the Array with the privious page of elements if there is one
|
41
|
+
def previous_page!
|
42
|
+
reload(previous_page) if previous_page.present?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Iterate over all pages
|
46
|
+
def each_page(args={})
|
47
|
+
begin
|
48
|
+
yield(self)
|
49
|
+
end while(args[:reverse] == true ? previous_page! : next_page!)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Iterate over all items with pagination
|
53
|
+
def each_page_each_item(args={})
|
54
|
+
self.each_page(args) do |array|
|
55
|
+
array.each do |item|
|
56
|
+
yield item
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set the properties and typecast some attributes automatically
|
63
|
+
def properties=(properties)
|
64
|
+
super
|
65
|
+
|
66
|
+
self.properties.each do |key, value|
|
67
|
+
send("#{key}=", value) if ATTRIBUTES.include?(key.to_sym)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# delegate unkown methods to the underlying array
|
72
|
+
def method_missing(name, *args, &block)
|
73
|
+
if @array.respond_to?(name)
|
74
|
+
@array.send(name, *args, &block)
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
@array.inspect
|
82
|
+
end
|
83
|
+
|
84
|
+
# Load data into the object from a document
|
85
|
+
def initialize_from_document(parser, format = Tagcrumbs.options[:format])
|
86
|
+
parser = super
|
87
|
+
|
88
|
+
if self.loaded?
|
89
|
+
nodes = parser.nodes_with_name(parser.name.singularize)
|
90
|
+
return if nodes.blank?
|
91
|
+
|
92
|
+
nodes.each do |node|
|
93
|
+
node_parser = Parser.new_for_format(node, parser.format)
|
94
|
+
node_type = node_parser.get_attribute('type')
|
95
|
+
|
96
|
+
if node_type # a nested model
|
97
|
+
@array << Tagcrumbs.class_by_type(node_type).new_from_document(node, parser.format)
|
98
|
+
else # a nested node
|
99
|
+
@array << node_parser.get_value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
module Tagcrumbs
|
2
|
+
module Accessors
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend(ClassMethods)
|
6
|
+
|
7
|
+
# class variables that save the options for attributes and associations
|
8
|
+
class_inheritable_accessor :writeable_attributes_options
|
9
|
+
class_inheritable_accessor :readable_attributes_options
|
10
|
+
class_inheritable_accessor :has_one_associations_options
|
11
|
+
class_inheritable_accessor :has_many_associations_options
|
12
|
+
class_inheritable_accessor :can_be_options
|
13
|
+
|
14
|
+
self.can_be_options ||= []
|
15
|
+
self.writeable_attributes_options ||= {}
|
16
|
+
self.readable_attributes_options ||= {}
|
17
|
+
self.has_one_associations_options ||= {}
|
18
|
+
self.has_many_associations_options ||= {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# The nodes contain the values that were read from the web service
|
23
|
+
def nodes
|
24
|
+
@nodes ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# nodes_updates contain the changes that were done to nodes
|
28
|
+
def nodes_updates
|
29
|
+
@nodes_updates ||= {}
|
30
|
+
end
|
31
|
+
|
32
|
+
# has_one_associations contain the related objects
|
33
|
+
def has_one_associations
|
34
|
+
@has_one_associations ||= {}
|
35
|
+
end
|
36
|
+
|
37
|
+
# has_one_associations_updates contain updates of the related objects
|
38
|
+
def has_one_associations_updates
|
39
|
+
@has_one_associations_updates ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
# has_many_associations contains Arrays of further objects
|
43
|
+
def has_many_associations
|
44
|
+
@has_many_associations ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Restore back to the original state, discard any changes that were done after the object
|
48
|
+
# was loaded
|
49
|
+
def restore!
|
50
|
+
@nodes_updates = {}
|
51
|
+
@has_one_associations_updates = {}
|
52
|
+
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set attributes with a hash
|
57
|
+
def attributes=(args={})
|
58
|
+
args.each do |k, v|
|
59
|
+
if respond_to?("#{k}=")
|
60
|
+
self.send("#{k}=", v)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return all attributes dynamically, pass :modifiable_only => true to only return
|
66
|
+
# modifiable arguments
|
67
|
+
def attributes(args={})
|
68
|
+
hash = {}
|
69
|
+
|
70
|
+
self.class.attributes(args).each do |k|
|
71
|
+
hash[k] = send(k)
|
72
|
+
end
|
73
|
+
|
74
|
+
hash
|
75
|
+
end
|
76
|
+
|
77
|
+
# Nice output for console testing
|
78
|
+
def inspect
|
79
|
+
attributes_for_inspect = []
|
80
|
+
attributes.each{|k, v| attributes_for_inspect << "#{k}: #{attribute_for_inspect(v)}"}
|
81
|
+
"#<#{self.class} #{attributes_for_inspect.join(', ')}>"
|
82
|
+
end
|
83
|
+
|
84
|
+
def attribute_for_inspect(value)
|
85
|
+
if value.is_a?(String) && value.length > 50
|
86
|
+
"#{value[0..50]}...".inspect
|
87
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
88
|
+
%("#{value.iso8601}")
|
89
|
+
else
|
90
|
+
value.inspect
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the class name without modules
|
95
|
+
def class_name
|
96
|
+
self.class.to_s.split('::').last
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the URL to which a post request must be sent to create a new resource
|
100
|
+
def create_url
|
101
|
+
raise CreateImpossible unless can_be?(:created)
|
102
|
+
|
103
|
+
# convention over configuration, can also be overwritten in subclasses
|
104
|
+
has_many_association_name = class_name.tableize
|
105
|
+
|
106
|
+
if Tagcrumbs.current_user.respond_to?(has_many_association_name) && Tagcrumbs.current_user.send(has_many_association_name)
|
107
|
+
Tagcrumbs.current_user.send("#{has_many_association_name}_url")
|
108
|
+
else
|
109
|
+
raise CreateURLMissing
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Can this object be :created, :updated or :destroyed?
|
114
|
+
def can_be?(method)
|
115
|
+
self.class.can_be_options.include?(method.to_sym)
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
module ClassMethods
|
120
|
+
# Configure if objects of this class can be :created, :updated or :destroyed
|
121
|
+
def can_be(*args)
|
122
|
+
self.can_be_options = args
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns an array of all the attributes of a class, pass :modifiable_only => true to only get modifiable attributes
|
126
|
+
def attributes(args = {})
|
127
|
+
if args[:modifiable_only]
|
128
|
+
writeable_attributes_options.keys + modifiable_has_one_associations_options.keys
|
129
|
+
else
|
130
|
+
writeable_attributes_options.keys + readable_attributes_options.keys + modifiable_has_one_associations_options.keys
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Configure the writeable attributes of the class that will be sent to the server on a post or put
|
135
|
+
def writeable_attributes(*attributes)
|
136
|
+
attributes.each do |attribute|
|
137
|
+
define_method attribute do
|
138
|
+
if nodes_updates[attribute]
|
139
|
+
nodes_updates[attribute].value
|
140
|
+
elsif nodes[attribute]
|
141
|
+
nodes[attribute].value
|
142
|
+
else
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
define_method "#{attribute}=" do |value|
|
148
|
+
self.nodes_updates[attribute] = Node.new(attribute, value)
|
149
|
+
end
|
150
|
+
|
151
|
+
self.writeable_attributes_options[attribute] = nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Configure attributes that are only readable and that cannot be changed by the client
|
156
|
+
def readable_attributes(*attributes)
|
157
|
+
attributes.each do |attribute|
|
158
|
+
define_method attribute do
|
159
|
+
if nodes[attribute]
|
160
|
+
nodes[attribute].value
|
161
|
+
else
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
self.readable_attributes_options[attribute] = nil
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Configure a has_one relationship
|
171
|
+
# Pass :modifiable => true if this relationship can be modified.
|
172
|
+
# When the resource is sent to the webservice the resource_url of the object will be sent to identify it
|
173
|
+
def has_one(name, options={})
|
174
|
+
options[:node_name] ||= name.to_s.underscore
|
175
|
+
|
176
|
+
self.has_one_associations_options[name] = options
|
177
|
+
|
178
|
+
define_method name do # define a reader that loads the association if not done already
|
179
|
+
if has_one_associations_updates[name]
|
180
|
+
has_one_associations_updates[name]
|
181
|
+
elsif !has_one_associations.has_key?(name)
|
182
|
+
nil
|
183
|
+
else
|
184
|
+
Proxy.new(has_one_associations[name]) # use a proxy to load objects as late as possible
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
define_method "#{name}=" do |value|
|
189
|
+
self.has_one_associations_updates[name] = value
|
190
|
+
end
|
191
|
+
|
192
|
+
# define a convenience url accessor method
|
193
|
+
define_method "#{name}_url" do
|
194
|
+
if has_one_associations_updates[name]
|
195
|
+
has_one_associations_updates[name].resource_url
|
196
|
+
elsif has_one_associations[name]
|
197
|
+
has_one_associations[name].resource_url
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# define aliases for tagcrumb
|
202
|
+
if name == :tagcrumb
|
203
|
+
define_method :placemark do
|
204
|
+
self.tagcrumb
|
205
|
+
end
|
206
|
+
|
207
|
+
define_method :placemark= do |value|
|
208
|
+
self.tagcrumb = value
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Configure a has_many association
|
214
|
+
def has_many(name, options={})
|
215
|
+
options[:node_name] ||= name.to_s.underscore
|
216
|
+
|
217
|
+
self.has_many_associations_options[name] = options
|
218
|
+
|
219
|
+
define_method name do # define a reader that loads the association if not done already
|
220
|
+
if !has_many_associations.has_key?(name)
|
221
|
+
nil
|
222
|
+
else
|
223
|
+
Proxy.new(has_many_associations[name]) # use a proxy to load objects as late as possible
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# define a convenience url accessor method
|
228
|
+
define_method "#{name}_url" do
|
229
|
+
has_many_associations[name].resource_url if has_many_associations[name]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Return the names of all modifiable has one associations of the class
|
234
|
+
def modifiable_has_one_associations_options
|
235
|
+
hash = {}
|
236
|
+
|
237
|
+
self.has_one_associations_options.each do |k, v|
|
238
|
+
hash[k] = v if v[:modifiable]
|
239
|
+
end
|
240
|
+
|
241
|
+
hash
|
242
|
+
end
|
243
|
+
|
244
|
+
# Return has_one and has_many options
|
245
|
+
def association_options
|
246
|
+
has_one_associations_options.merge(has_many_associations_options)
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|