ruby-satisfaction 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/lib/satisfaction.rb +145 -0
- data/lib/satisfaction/associations.rb +20 -0
- data/lib/satisfaction/cache/hash.rb +14 -0
- data/lib/satisfaction/cache/memcache.rb +17 -0
- data/lib/satisfaction/company.rb +18 -0
- data/lib/satisfaction/external_dependencies.rb +11 -0
- data/lib/satisfaction/has_satisfaction.rb +8 -0
- data/lib/satisfaction/identity_map.rb +17 -0
- data/lib/satisfaction/loader.rb +105 -0
- data/lib/satisfaction/person.rb +25 -0
- data/lib/satisfaction/product.rb +17 -0
- data/lib/satisfaction/reply.rb +13 -0
- data/lib/satisfaction/resource.rb +190 -0
- data/lib/satisfaction/resource/attributes.rb +67 -0
- data/lib/satisfaction/tag.rb +14 -0
- data/lib/satisfaction/topic.rb +20 -0
- data/lib/satisfaction/util.rb +13 -0
- data/lib/satisfaction/version.rb +9 -0
- metadata +108 -0
data/lib/satisfaction.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'satisfaction/external_dependencies'
|
2
|
+
|
3
|
+
class Satisfaction
|
4
|
+
# ==================
|
5
|
+
# = Core Utilities =
|
6
|
+
# ==================
|
7
|
+
require 'satisfaction/util'
|
8
|
+
require 'satisfaction/has_satisfaction'
|
9
|
+
require 'satisfaction/associations'
|
10
|
+
require 'satisfaction/resource'
|
11
|
+
require 'satisfaction/loader'
|
12
|
+
require 'satisfaction/identity_map'
|
13
|
+
|
14
|
+
|
15
|
+
# =============
|
16
|
+
# = Resources =
|
17
|
+
# =============
|
18
|
+
|
19
|
+
require 'satisfaction/company'
|
20
|
+
require 'satisfaction/person'
|
21
|
+
require 'satisfaction/topic'
|
22
|
+
require 'satisfaction/tag'
|
23
|
+
require 'satisfaction/product'
|
24
|
+
require 'satisfaction/reply'
|
25
|
+
|
26
|
+
# =============
|
27
|
+
|
28
|
+
include Associations
|
29
|
+
|
30
|
+
attr_reader :options
|
31
|
+
attr_reader :loader
|
32
|
+
attr_reader :consumer
|
33
|
+
attr_reader :token
|
34
|
+
attr_reader :identity_map
|
35
|
+
|
36
|
+
|
37
|
+
def initialize(options={})
|
38
|
+
@options = options.reverse_merge({
|
39
|
+
:root => "http://api.getsatisfaction.com",
|
40
|
+
:autoload => false,
|
41
|
+
:request_token_url => 'http://getsatisfaction.com/api/request_token',
|
42
|
+
:access_token_url => 'http://getsatisfaction.com/api/access_token',
|
43
|
+
:authorize_url => 'http://getsatisfaction.com/api/authorize',
|
44
|
+
})
|
45
|
+
@loader = Satisfaction::Loader.new
|
46
|
+
@identity_map = Satisfaction::IdentityMap.new
|
47
|
+
|
48
|
+
has_many :companies, :url => '/companies'
|
49
|
+
has_many :people, :url => '/people'
|
50
|
+
has_many :topics, :url => '/topics'
|
51
|
+
has_many :replies, :url => '/replies'
|
52
|
+
has_many :tags, :url => '/tags'
|
53
|
+
has_many :products, :url => '/products'
|
54
|
+
end
|
55
|
+
|
56
|
+
def satisfaction
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def me
|
61
|
+
@me ||= Me.new('me', self)
|
62
|
+
@me.load
|
63
|
+
@me
|
64
|
+
end
|
65
|
+
|
66
|
+
def autoload?
|
67
|
+
options[:autoload]
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_basic_auth(user, password)
|
71
|
+
@user = user
|
72
|
+
@password = password
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_consumer(key, secret)
|
76
|
+
@consumer = OAuth::Consumer.new(key, secret)
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_token(token, secret)
|
80
|
+
@token = OAuth::Token.new(token, secret)
|
81
|
+
end
|
82
|
+
|
83
|
+
def request_token
|
84
|
+
response = CGI.parse(@loader.get("#{options[:request_token_url]}", :force => true, :consumer => @consumer, :token => nil))
|
85
|
+
OAuth::Token.new(response["oauth_token"], response["oauth_token_secret"])
|
86
|
+
end
|
87
|
+
|
88
|
+
def authorize_url(token)
|
89
|
+
"#{options[:authorize_url]}?oauth_token=#{token.token}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def access_token(token)
|
93
|
+
response = CGI.parse(@loader.get("#{options[:access_token_url]}", :force => true, :consumer => @consumer, :token => token))
|
94
|
+
OAuth::Token.new(response["oauth_token"], response["oauth_token_secret"])
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def url(path, query_string={})
|
99
|
+
qs = query_string.map{|kv| URI.escape(kv.first.to_s) + "=" + URI.escape(kv.last.to_s)}.join("&")
|
100
|
+
URI.parse("#{@options[:root]}#{path}?#{qs}")
|
101
|
+
end
|
102
|
+
|
103
|
+
def get(path, query_string={})
|
104
|
+
url = self.url(path, query_string)
|
105
|
+
|
106
|
+
@loader.get(url, :consumer => @consumer, :token => @token, :user => @user, :password => @password)
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
def post(path, form={})
|
111
|
+
url = self.url(path)
|
112
|
+
@loader.post(url,
|
113
|
+
:consumer => @consumer,
|
114
|
+
:token => @token,
|
115
|
+
:user => @user,
|
116
|
+
:password => @password,
|
117
|
+
:form => form)
|
118
|
+
end
|
119
|
+
|
120
|
+
def delete(path)
|
121
|
+
url = self.url(path)
|
122
|
+
@loader.post(url,
|
123
|
+
:consumer => @consumer,
|
124
|
+
:token => @token,
|
125
|
+
:user => @user,
|
126
|
+
:password => @password,
|
127
|
+
:method => :delete)
|
128
|
+
end
|
129
|
+
|
130
|
+
def put(path, form={})
|
131
|
+
url = self.url(path)
|
132
|
+
@loader.post(url,
|
133
|
+
:consumer => @consumer,
|
134
|
+
:token => @token,
|
135
|
+
:user => @user,
|
136
|
+
:password => @password,
|
137
|
+
:method => :put,
|
138
|
+
:form => form)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def validate_options
|
143
|
+
raise ArgumentError, "You must specify a location for the API's service root" if options[:root].blank?
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Associations
|
2
|
+
def has_many(resource, options={})
|
3
|
+
class_name = options[:class_name] || resource.to_s.classify
|
4
|
+
eval <<-EOS
|
5
|
+
def #{resource}
|
6
|
+
@#{resource} ||= ResourceCollection.new(#{class_name}, self.satisfaction, '#{options[:url]}')
|
7
|
+
end
|
8
|
+
EOS
|
9
|
+
end
|
10
|
+
|
11
|
+
def belongs_to(resource, options={})
|
12
|
+
class_name = options[:class_name] || resource.to_s.classify
|
13
|
+
parent_id = options[:parent_attribute] || "#{resource}_id"
|
14
|
+
eval <<-EOS
|
15
|
+
def #{resource}
|
16
|
+
@#{resource} ||= #{class_name}.new(#{parent_id}, self.satisfaction)
|
17
|
+
end
|
18
|
+
EOS
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Satisfaction::Loader::HashCache
|
2
|
+
def initialize
|
3
|
+
@cached_responses = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def put(url, response)
|
7
|
+
return nil if response["ETag"].blank?
|
8
|
+
@cached_responses[url.to_s] = Satisfaction::Loader::CacheRecord.new(url, response["ETag"], response.body)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(url)
|
12
|
+
@cached_responses[url.to_s]
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
class Satisfaction::Loader::MemcacheCache
|
3
|
+
def initialize(options = {})
|
4
|
+
options = options.reverse_merge({:servers => ['127.0.0.1:11211'], :namespace => 'satisfaction', })
|
5
|
+
@m = MemCache.new(options.delete(:servers), options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def put(url, response)
|
9
|
+
return nil if response["ETag"].blank?
|
10
|
+
|
11
|
+
@m[url.to_s] = Loader::CacheRecord.new(url, response["ETag"], response.body)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(url)
|
15
|
+
@m[url.to_s]
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Company < Resource
|
2
|
+
|
3
|
+
attributes :domain, :name, :logo, :description
|
4
|
+
|
5
|
+
def path
|
6
|
+
"/companies/#{@id}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def setup_associations
|
10
|
+
has_many :people, :url => "#{path}/people"
|
11
|
+
has_many :topics, :url => "#{path}/topics"
|
12
|
+
has_many :products, :url => "#{path}/products"
|
13
|
+
has_many :employees, :url => "#{path}/employees", :class_name => 'Person'
|
14
|
+
has_many :tags, :url => "#{path}/tags"
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'hpricot'
|
4
|
+
require 'json'
|
5
|
+
require 'json/add/rails' #make json play nice with the json rails outputs
|
6
|
+
gem('memcache-client')
|
7
|
+
require 'memcache'
|
8
|
+
|
9
|
+
require 'oauth'
|
10
|
+
require 'oauth/signature/hmac/sha1'
|
11
|
+
require 'oauth/client/net_http'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Satisfaction::IdentityMap
|
2
|
+
attr_reader :records, :pages
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@records = {}
|
6
|
+
@pages = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_record(klass, id, &block)
|
10
|
+
result = @records[[klass, id]]
|
11
|
+
result ||= begin
|
12
|
+
obj = yield(klass, id)
|
13
|
+
@records[[klass, id]] = obj
|
14
|
+
end
|
15
|
+
result
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
|
5
|
+
class Satisfaction::Loader
|
6
|
+
require 'satisfaction/cache/hash'
|
7
|
+
require 'satisfaction/cache/memcache'
|
8
|
+
|
9
|
+
CacheRecord = Struct.new(:url, :etag, :body)
|
10
|
+
attr_reader :cache
|
11
|
+
attr_reader :options
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
@options = options.reverse_merge({:cache => :hash})
|
15
|
+
@cache = case @options[:cache]
|
16
|
+
when :hash then HashCache.new
|
17
|
+
when :memcache then MemcacheCache.new(@options[:memcache] || {})
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Invalid cache spec: #{@options[:cache]}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(url, options = {})
|
24
|
+
uri = get_uri(url)
|
25
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
26
|
+
cache_record = cache.get(uri)
|
27
|
+
|
28
|
+
if cache_record && !options[:force]
|
29
|
+
request["If-None-Match"] = cache_record.etag
|
30
|
+
end
|
31
|
+
|
32
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
33
|
+
add_authentication(request, options)
|
34
|
+
response = execute(http, request)
|
35
|
+
|
36
|
+
case response
|
37
|
+
when Net::HTTPNotModified
|
38
|
+
return cache_record.body
|
39
|
+
when Net::HTTPSuccess
|
40
|
+
cache.put(uri, response)
|
41
|
+
response.body
|
42
|
+
when Net::HTTPMovedPermanently
|
43
|
+
limit = options[:redirect_limit] || 3
|
44
|
+
raise ArgumentError, "Too many redirects" unless limit > 0 #TODO: what is a better error here?
|
45
|
+
get(response['location'], options.merge(:redirect_limit => limit - 1))
|
46
|
+
else
|
47
|
+
raise "Explode: #{response.to_yaml}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def post(url, options)
|
52
|
+
uri = get_uri(url)
|
53
|
+
form = options[:form] || {}
|
54
|
+
method_klass = case options[:method]
|
55
|
+
when :put then Net::HTTP::Put
|
56
|
+
when :delete then Net::HTTP::Delete
|
57
|
+
else
|
58
|
+
Net::HTTP::Post
|
59
|
+
end
|
60
|
+
|
61
|
+
request = method_klass.new(uri.request_uri)
|
62
|
+
|
63
|
+
request.set_form_data(form)
|
64
|
+
|
65
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
66
|
+
add_authentication(request, options)
|
67
|
+
response = execute(http, request)
|
68
|
+
|
69
|
+
case response
|
70
|
+
when Net::HTTPUnauthorized
|
71
|
+
[:unauthorized, response.body]
|
72
|
+
when Net::HTTPBadRequest
|
73
|
+
[:bad_request, response.body]
|
74
|
+
when Net::HTTPForbidden
|
75
|
+
[:forbidden, response.body]
|
76
|
+
when Net::HTTPSuccess
|
77
|
+
[:ok, response.body]
|
78
|
+
else
|
79
|
+
raise "Explode: #{response.to_yaml}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def execute(http, request)
|
85
|
+
http.start{|http| http.request(request) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_uri(url)
|
89
|
+
case url
|
90
|
+
when URI then url
|
91
|
+
when String then URI.parse(url)
|
92
|
+
else
|
93
|
+
raise ArgumentError, "Invalid uri, please use a String or URI object"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_authentication(request, options)
|
98
|
+
if options[:user]
|
99
|
+
request.basic_auth(options[:user], options[:password])
|
100
|
+
elsif options[:consumer]
|
101
|
+
request.oauth!(http, options[:consumer], options[:token])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Person < Resource
|
2
|
+
attributes :name, :id, :photo, :tagline
|
3
|
+
|
4
|
+
def path
|
5
|
+
"/people/#{@id}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def setup_associations
|
9
|
+
has_many :replies, :url => "#{path}/replies"
|
10
|
+
has_many :topics, :url => "#{path}/topics"
|
11
|
+
has_many :followed_topics, :url => "#{path}/followed/topics", :class_name => 'Topic'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Me < Person
|
16
|
+
def path
|
17
|
+
"/me"
|
18
|
+
end
|
19
|
+
|
20
|
+
def load
|
21
|
+
result = satisfaction.get("#{path}.json")
|
22
|
+
self.attributes = JSON.parse(result)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Product < Resource
|
2
|
+
attributes :name, :url, :image, :description
|
3
|
+
attribute :last_active_at, :type => Time
|
4
|
+
attribute :created_at, :type => Time
|
5
|
+
|
6
|
+
def path
|
7
|
+
"/products/#{@id}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup_associations
|
11
|
+
has_many :topics, :url => "#{path}/topics"
|
12
|
+
has_many :people, :url => "#{path}/people"
|
13
|
+
has_many :companies, :url => "#{path}/companies"
|
14
|
+
has_many :tags, :url => "#{path}/tags"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
class Resource < Satisfaction::HasSatisfaction
|
4
|
+
require 'satisfaction/resource/attributes'
|
5
|
+
include ::Associations
|
6
|
+
include Attributes
|
7
|
+
attr_reader :id
|
8
|
+
include Satisfaction::Util
|
9
|
+
|
10
|
+
|
11
|
+
def initialize(id, satisfaction)
|
12
|
+
super satisfaction
|
13
|
+
@id = id
|
14
|
+
setup_associations if respond_to?(:setup_associations)
|
15
|
+
end
|
16
|
+
|
17
|
+
def path
|
18
|
+
raise "path not implemented in Resource base class"
|
19
|
+
end
|
20
|
+
|
21
|
+
def load
|
22
|
+
result = satisfaction.get("#{path}.json")
|
23
|
+
self.attributes = JSON.parse(result)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete
|
28
|
+
satisfaction.delete("#{path}.json")
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def put(attrs)
|
33
|
+
params = requestify(attrs, self.class.name.underscore)
|
34
|
+
result = satisfaction.put("#{path}.json", params)
|
35
|
+
|
36
|
+
if result.first == :ok
|
37
|
+
json = JSON.parse(result.last)
|
38
|
+
self.attributes = json
|
39
|
+
self
|
40
|
+
else
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def loaded?
|
46
|
+
!@attributes.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
"<#{self.class.name} #{attributes.map{|k,v| "#{k}: #{v}"}.join(' ') if !attributes.nil?}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class ResourceCollection < Satisfaction::HasSatisfaction
|
56
|
+
attr_reader :klass
|
57
|
+
attr_reader :path
|
58
|
+
include Satisfaction::Util
|
59
|
+
|
60
|
+
def initialize(klass, satisfaction, path)
|
61
|
+
super satisfaction
|
62
|
+
@klass = klass
|
63
|
+
@path = path
|
64
|
+
end
|
65
|
+
|
66
|
+
def page(number, options={})
|
67
|
+
Page.new(self, number, options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def get(id, options={})
|
71
|
+
#options currently ignored
|
72
|
+
satisfaction.identity_map.get_record(klass, id) do
|
73
|
+
klass.new(id, satisfaction)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def post(attrs)
|
78
|
+
params = requestify(attrs, klass.name.underscore)
|
79
|
+
result = satisfaction.post("#{path}.json", params)
|
80
|
+
|
81
|
+
if result.first == :ok
|
82
|
+
json = JSON.parse(result.last)
|
83
|
+
id = json["id"]
|
84
|
+
obj = klass.new(id, satisfaction)
|
85
|
+
obj.attributes = json
|
86
|
+
obj
|
87
|
+
else
|
88
|
+
result
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def [](*key)
|
93
|
+
options = key.extract_options!
|
94
|
+
case key.length
|
95
|
+
when 1
|
96
|
+
get(key, options)
|
97
|
+
when 2
|
98
|
+
page(key.first, options.merge(:limit => key.last))
|
99
|
+
else
|
100
|
+
raise ArgumentError, "Invalid Array arguement, only use 2-element array: :first is the page number, :last is the page size"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
class Page < Satisfaction::HasSatisfaction
|
107
|
+
attr_reader :total
|
108
|
+
attr_reader :collection
|
109
|
+
|
110
|
+
extend Forwardable
|
111
|
+
def_delegator :items, :first
|
112
|
+
def_delegator :items, :last
|
113
|
+
def_delegator :items, :each
|
114
|
+
def_delegator :items, :each_with_index
|
115
|
+
def_delegator :items, :inject
|
116
|
+
def_delegator :items, :reject
|
117
|
+
def_delegator :items, :select
|
118
|
+
def_delegator :items, :map
|
119
|
+
def_delegator :items, :[]
|
120
|
+
def_delegator :items, :length
|
121
|
+
def_delegator :items, :to_a
|
122
|
+
def_delegator :items, :empty?
|
123
|
+
|
124
|
+
def initialize(collection, page, options={})
|
125
|
+
super(collection.satisfaction)
|
126
|
+
@collection = collection
|
127
|
+
@klass = collection.klass
|
128
|
+
@page = page
|
129
|
+
@path = collection.path
|
130
|
+
@options = options
|
131
|
+
@options[:limit] ||= 10
|
132
|
+
end
|
133
|
+
|
134
|
+
# Retrieve the items for this page
|
135
|
+
# * Caches
|
136
|
+
def items
|
137
|
+
load
|
138
|
+
@data
|
139
|
+
end
|
140
|
+
|
141
|
+
def loaded?
|
142
|
+
!@data.nil?
|
143
|
+
end
|
144
|
+
|
145
|
+
def page_size
|
146
|
+
@options[:limit]
|
147
|
+
end
|
148
|
+
|
149
|
+
def next?
|
150
|
+
load #this loads the data, we shold probably make load set the ivar instead of items ;)
|
151
|
+
last_item = @page * page_size
|
152
|
+
@total > last_item
|
153
|
+
end
|
154
|
+
|
155
|
+
def next
|
156
|
+
return nil unless next?
|
157
|
+
self.class.new(@collection, @page + 1, @options)
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def prev?
|
162
|
+
@page > 1
|
163
|
+
end
|
164
|
+
|
165
|
+
def prev
|
166
|
+
return nil unless prev?
|
167
|
+
self.class.new(@collection, @page - 1, @options)
|
168
|
+
end
|
169
|
+
|
170
|
+
def page_count
|
171
|
+
result = @total / length
|
172
|
+
result += 1 if @total % length != 0
|
173
|
+
result
|
174
|
+
end
|
175
|
+
|
176
|
+
def load(force=false)
|
177
|
+
return @data if loaded? && !force
|
178
|
+
|
179
|
+
results = satisfaction.get("#{@path}.json", @options.merge(:page => @page))
|
180
|
+
json = JSON.parse(results)
|
181
|
+
@total = json["total"]
|
182
|
+
|
183
|
+
@data = json["data"].map do |result|
|
184
|
+
obj = @klass.decode_sfn(result, satisfaction)
|
185
|
+
satisfaction.identity_map.get_record(@klass, obj.id) do
|
186
|
+
obj
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Resource::Attributes
|
2
|
+
def self.included(base)
|
3
|
+
base.class_eval do
|
4
|
+
extend ClassMethods
|
5
|
+
include InstanceMethods
|
6
|
+
attr_reader :attributes
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def attributes(*names)
|
13
|
+
options = names.extract_options!
|
14
|
+
|
15
|
+
names.each do |name|
|
16
|
+
attribute name, options unless name.blank?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def attribute(name, options)
|
21
|
+
options.reverse_merge!(:type => 'nil')
|
22
|
+
raise "Name can't be empty" if name.blank?
|
23
|
+
|
24
|
+
class_eval <<-EOS
|
25
|
+
def #{name}
|
26
|
+
self.load unless self.loaded?
|
27
|
+
@#{name} ||= decode_raw_attribute(@attributes['#{name}'], #{options[:type]}) if @attributes
|
28
|
+
end
|
29
|
+
EOS
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
module InstanceMethods
|
35
|
+
def attributes=(value)
|
36
|
+
@attributes = value.with_indifferent_access
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def decode_raw_attribute(value, type)
|
41
|
+
if type.respond_to?(:decode_sfn)
|
42
|
+
type.decode_sfn(value, self.satisfaction)
|
43
|
+
else
|
44
|
+
value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class Time
|
52
|
+
def self.decode_sfn(value, satisfaction)
|
53
|
+
parse(value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Resource
|
58
|
+
def self.decode_sfn(value, satisfaction)
|
59
|
+
case value
|
60
|
+
when Hash
|
61
|
+
id = value['id']
|
62
|
+
returning(new(id, satisfaction)){|r| r.attributes = value}
|
63
|
+
else
|
64
|
+
new(value, satisfaction)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Tag < Resource
|
2
|
+
attributes :name
|
3
|
+
|
4
|
+
def path
|
5
|
+
"/tags/#{@id}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def setup_associations
|
9
|
+
has_many :topics, :url => "#{path}/topics"
|
10
|
+
has_many :companies, :url => "#{path}/companies"
|
11
|
+
has_many :products, :url => "#{path}/products"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Topic < Resource
|
2
|
+
attributes :subject, :style, :content, :reply_count, :follower_count, :company_id
|
3
|
+
attribute :last_active_at, :type => Time
|
4
|
+
attribute :created_at, :type => Time
|
5
|
+
attribute :author, :type => Person
|
6
|
+
|
7
|
+
def path
|
8
|
+
"/topics/#{@id}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup_associations
|
12
|
+
has_many :replies, :url => "#{path}/replies"
|
13
|
+
has_many :people, :url => "#{path}/people"
|
14
|
+
has_many :products, :url => "#{path}/products"
|
15
|
+
has_many :tags, :url => "#{path}/tags"
|
16
|
+
|
17
|
+
belongs_to :company
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Satisfaction::Util
|
2
|
+
def requestify(parameters, prefix=nil)
|
3
|
+
parameters.inject({}) do |results, kv|
|
4
|
+
if Hash === kv.last
|
5
|
+
results = results.merge(requestify(kv.last, "#{prefix}[#{kv.first}]"))
|
6
|
+
else
|
7
|
+
results["#{prefix}[#{kv.first}]"] = kv.last
|
8
|
+
end
|
9
|
+
|
10
|
+
results
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-satisfaction
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Fleckenstein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-04-09 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: oauth
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0"
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: memcache-client
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "0"
|
41
|
+
version:
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: activesupport
|
44
|
+
version_requirement:
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
description:
|
52
|
+
email: nullstyle@gmail.com
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- lib/satisfaction
|
61
|
+
- lib/satisfaction/associations.rb
|
62
|
+
- lib/satisfaction/cache
|
63
|
+
- lib/satisfaction/cache/hash.rb
|
64
|
+
- lib/satisfaction/cache/memcache.rb
|
65
|
+
- lib/satisfaction/company.rb
|
66
|
+
- lib/satisfaction/external_dependencies.rb
|
67
|
+
- lib/satisfaction/has_satisfaction.rb
|
68
|
+
- lib/satisfaction/identity_map.rb
|
69
|
+
- lib/satisfaction/loader.rb
|
70
|
+
- lib/satisfaction/person.rb
|
71
|
+
- lib/satisfaction/product.rb
|
72
|
+
- lib/satisfaction/reply.rb
|
73
|
+
- lib/satisfaction/resource
|
74
|
+
- lib/satisfaction/resource/attributes.rb
|
75
|
+
- lib/satisfaction/resource.rb
|
76
|
+
- lib/satisfaction/tag.rb
|
77
|
+
- lib/satisfaction/topic.rb
|
78
|
+
- lib/satisfaction/util.rb
|
79
|
+
- lib/satisfaction/version.rb
|
80
|
+
- lib/satisfaction.rb
|
81
|
+
has_rdoc: false
|
82
|
+
homepage: http://nullstyle.com
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
version:
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.0.1
|
104
|
+
signing_key:
|
105
|
+
specification_version: 2
|
106
|
+
summary: Helper gem for the getsatisfaction.com API
|
107
|
+
test_files: []
|
108
|
+
|