nullstyle-ruby-satisfaction 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ tmp/*
2
+ log/*
3
+ pkg
@@ -0,0 +1,7 @@
1
+ John Duff
2
+ Pius Uzamere
3
+
4
+ Awards
5
+ ======
6
+
7
+ John Duff gets the "Getting the ball rolling" achievement award for being the first contributor besides myself.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Scott Fleckenstein
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ README
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "ruby-satisfaction"
5
+ gemspec.summary = "Ruby interface to Get Satisfaction"
6
+ gemspec.description = "Ruby interface to Get Satisfaction"
7
+ gemspec.email = "scott@getsatisfaction.com"
8
+ gemspec.homepage = "http://github.com/nullstyle/ruby-satisfaction"
9
+ gemspec.authors = ["Scott Fleckenstein", "Josh Nichols", "Pius Uzamere"]
10
+ gemspec.rubyforge_project = "satisfaction"
11
+ gemspec.add_dependency('memcache-client', '>= 1.5.0')
12
+ gemspec.add_dependency('oauth', '>= 0.3.5')
13
+ gemspec.add_dependency('activesupport', '>= 2.3.2')
14
+ end
15
+
16
+ Jeweler::RubyforgeTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 4
3
+ :patch: 0
4
+ :major: 0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'satisfaction'
@@ -0,0 +1,161 @@
1
+ require 'satisfaction/external_dependencies'
2
+ module Sfn
3
+ end
4
+ class Satisfaction
5
+ # ==================
6
+ # = Core Utilities =
7
+ # ==================
8
+ require 'satisfaction/util'
9
+ require 'satisfaction/has_satisfaction'
10
+ require 'satisfaction/associations'
11
+ require 'satisfaction/resource'
12
+ require 'satisfaction/loader'
13
+ require 'satisfaction/identity_map'
14
+
15
+
16
+ # =============
17
+ # = Resources =
18
+ # =============
19
+
20
+ require 'satisfaction/company'
21
+ require 'satisfaction/person'
22
+ require 'satisfaction/topic'
23
+ require 'satisfaction/tag'
24
+ require 'satisfaction/product'
25
+ require 'satisfaction/reply'
26
+
27
+ # =============
28
+
29
+ include Associations
30
+
31
+ attr_reader :options
32
+ attr_reader :loader
33
+ attr_reader :consumer
34
+ attr_reader :token
35
+ attr_reader :identity_map
36
+
37
+
38
+ def initialize(options={})
39
+ @options = options.reverse_merge({
40
+ :root => "http://api.getsatisfaction.com",
41
+ :autoload => false,
42
+ :request_token_url => 'http://getsatisfaction.com/api/request_token',
43
+ :access_token_url => 'http://getsatisfaction.com/api/access_token',
44
+ :authorize_url => 'http://getsatisfaction.com/api/authorize',
45
+ })
46
+ @loader = Sfn::Loader.new
47
+ @identity_map = Sfn::IdentityMap.new
48
+
49
+ has_many :companies, :url => '/companies'
50
+ has_many :people, :url => '/people'
51
+ has_many :topics, :url => '/topics'
52
+ has_many :replies, :url => '/replies'
53
+ has_many :tags, :url => '/tags'
54
+ has_many :products, :url => '/products'
55
+ end
56
+
57
+ def satisfaction
58
+ self
59
+ end
60
+
61
+ def me
62
+ me = satisfaction.identity_map.get_record(Me, 'me') do
63
+ Sfn::Me.new('me', satisfaction)
64
+ end
65
+
66
+ if me.loaded?
67
+ me
68
+ else
69
+ me.load
70
+ end
71
+ end
72
+
73
+ def autoload?
74
+ options[:autoload]
75
+ end
76
+
77
+ def set_basic_auth(user, password)
78
+ identity_map.expire_record(Me, 'me')
79
+ @user = user
80
+ @password = password
81
+ end
82
+
83
+ def set_consumer(key, secret)
84
+ identity_map.expire_record(Me, 'me')
85
+ @consumer = OAuth::Consumer.new(key, secret)
86
+ end
87
+
88
+ def set_token(token, secret)
89
+ identity_map.expire_record(Me, 'me')
90
+ @token = OAuth::Token.new(token, secret)
91
+ end
92
+
93
+ def request_token
94
+ result, body = *@loader.get("#{options[:request_token_url]}", :force => true, :consumer => @consumer, :token => nil)
95
+ raise "Could not retrieve request token" unless result == :ok
96
+ response = CGI.parse(body)
97
+ OAuth::Token.new(response["oauth_token"], response["oauth_token_secret"])
98
+ end
99
+
100
+ def authorize_url(token)
101
+ "#{options[:authorize_url]}?oauth_token=#{token.token}"
102
+ end
103
+
104
+ def access_token(token)
105
+ result, body = *@loader.get("#{options[:access_token_url]}", :force => true, :consumer => @consumer, :token => token)
106
+ raise "Could not retrieve access token" unless result == :ok
107
+ response = CGI.parse(body)
108
+ OAuth::Token.new(response["oauth_token"], response["oauth_token_secret"])
109
+ end
110
+
111
+
112
+ def url(path, query_string={})
113
+ qs = query_string.map{|kv| URI.escape(kv.first.to_s) + "=" + URI.escape(kv.last.to_s)}.join("&")
114
+ uri_string = "#{@options[:root]}#{path}"
115
+ uri_string += "?#{qs}" unless qs.blank?
116
+ URI.parse(uri_string)
117
+ end
118
+
119
+ def get(path, query_string={})
120
+ url = self.url(path, query_string)
121
+
122
+ @loader.get(url, :consumer => @consumer, :token => @token, :user => @user, :password => @password)
123
+
124
+ end
125
+
126
+ def post(path, form={})
127
+ url = self.url(path)
128
+ @loader.post(url,
129
+ :consumer => @consumer,
130
+ :token => @token,
131
+ :user => @user,
132
+ :password => @password,
133
+ :form => form)
134
+ end
135
+
136
+ def delete(path)
137
+ url = self.url(path)
138
+ @loader.post(url,
139
+ :consumer => @consumer,
140
+ :token => @token,
141
+ :user => @user,
142
+ :password => @password,
143
+ :method => :delete)
144
+ end
145
+
146
+ def put(path, form={})
147
+ url = self.url(path)
148
+ @loader.post(url,
149
+ :consumer => @consumer,
150
+ :token => @token,
151
+ :user => @user,
152
+ :password => @password,
153
+ :method => :put,
154
+ :form => form)
155
+ end
156
+
157
+ private
158
+ def validate_options
159
+ raise ArgumentError, "You must specify a location for the API's service root" if options[:root].blank?
160
+ end
161
+ end
@@ -0,0 +1,20 @@
1
+ module Associations
2
+ def has_many(resource, options={})
3
+ class_name = options[:class_name] || "Sfn::#{resource.to_s.classify}"
4
+ eval <<-EOS
5
+ def #{resource}
6
+ @#{resource} ||= Sfn::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] || "Sfn::#{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 Sfn::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] = Sfn::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 Sfn::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] = Sfn::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 Sfn::Company < Sfn::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 => 'Sfn::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,8 @@
1
+ class Sfn::HasSatisfaction
2
+ attr_reader :satisfaction
3
+
4
+ def initialize(satisfaction)
5
+ @satisfaction = satisfaction
6
+ end
7
+
8
+ end
@@ -0,0 +1,21 @@
1
+ class Sfn::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
+
18
+ def expire_record(klass, id)
19
+ @records[[klass, id]] = nil
20
+ end
21
+ end
@@ -0,0 +1,115 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+
5
+ class Sfn::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
+ reset_cache
16
+ end
17
+
18
+ def reset_cache
19
+ @cache = case @options[:cache]
20
+ when :hash then HashCache.new
21
+ when :memcache then MemcacheCache.new(@options[:memcache] || {})
22
+ else
23
+ raise ArgumentError, "Invalid cache spec: #{@options[:cache]}"
24
+ end
25
+ end
26
+
27
+ def get(url, options = {})
28
+ uri = get_uri(url)
29
+ request = Net::HTTP::Get.new(uri.request_uri)
30
+ cache_record = cache.get(uri)
31
+
32
+ if cache_record && !options[:force]
33
+ request["If-None-Match"] = cache_record.etag
34
+ end
35
+
36
+ http = Net::HTTP.new(uri.host, uri.port)
37
+ add_authentication(request, http, options)
38
+ response = execute(http, request)
39
+
40
+ case response
41
+ when Net::HTTPNotModified
42
+ return [:ok, cache_record.body]
43
+ when Net::HTTPSuccess
44
+ cache.put(uri, response)
45
+ [:ok, response.body]
46
+ when Net::HTTPMovedPermanently
47
+ limit = options[:redirect_limit] || 3
48
+ raise ArgumentError, "Too many redirects" unless limit > 0 #TODO: what is a better error here?
49
+ get(response['location'], options.merge(:redirect_limit => limit - 1))
50
+ when Net::HTTPBadRequest
51
+ [:bad_request, response.body]
52
+ when Net::HTTPForbidden
53
+ [:forbidden, response.body]
54
+ when Net::HTTPUnauthorized
55
+ [:unauthorized, response.body]
56
+ else
57
+ raise "Explode: #{response.to_yaml}"
58
+ end
59
+ end
60
+
61
+ def post(url, options)
62
+ uri = get_uri(url)
63
+ form = options[:form] || {}
64
+ method_klass = case options[:method]
65
+ when :put then Net::HTTP::Put
66
+ when :delete then Net::HTTP::Delete
67
+ else
68
+ Net::HTTP::Post
69
+ end
70
+
71
+ request = method_klass.new(uri.request_uri)
72
+
73
+ request.set_form_data(form)
74
+
75
+ http = Net::HTTP.new(uri.host, uri.port)
76
+ add_authentication(request, http, options)
77
+ response = execute(http, request)
78
+
79
+ case response
80
+ when Net::HTTPUnauthorized
81
+ [:unauthorized, response.body]
82
+ when Net::HTTPBadRequest
83
+ [:bad_request, response.body]
84
+ when Net::HTTPForbidden
85
+ [:forbidden, response.body]
86
+ when Net::HTTPSuccess
87
+ [:ok, response.body]
88
+ else
89
+ raise "Explode: #{response.to_yaml}"
90
+ end
91
+ end
92
+
93
+ private
94
+ def execute(http, request)
95
+ http.start{|http| http.request(request) }
96
+ end
97
+
98
+ def get_uri(url)
99
+ case url
100
+ when URI then url
101
+ when String then URI.parse(url)
102
+ else
103
+ raise ArgumentError, "Invalid uri, please use a String or URI object"
104
+ end
105
+ end
106
+
107
+ def add_authentication(request, http, options)
108
+ if options[:user]
109
+ request.basic_auth(options[:user], options[:password])
110
+ elsif options[:consumer]
111
+ request.oauth!(http, options[:consumer], options[:token])
112
+ end
113
+ end
114
+ end
115
+
@@ -0,0 +1,24 @@
1
+ class Sfn::Person < Sfn::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 => 'Sfn::Topic'
12
+ end
13
+ end
14
+
15
+ class Me < Sfn::Person
16
+ def path
17
+ loaded? ? super : "/me"
18
+ end
19
+
20
+ def was_loaded(result)
21
+ @id = self.attributes["id"]
22
+ setup_associations
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ class Sfn::Product < Sfn::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,13 @@
1
+ class Sfn::Reply < Sfn::Resource
2
+ attributes :content, :star_count, :topic_id
3
+ attribute :created_at, :type => Time
4
+ attribute :author, :type => Sfn::Person
5
+
6
+ def path
7
+ "/replies/#{id}"
8
+ end
9
+
10
+ def setup_associations
11
+ belongs_to :topic
12
+ end
13
+ end
@@ -0,0 +1,208 @@
1
+ require 'forwardable'
2
+
3
+ class Sfn::Resource < Sfn::HasSatisfaction
4
+ require 'satisfaction/resource/attributes'
5
+ include ::Associations
6
+ include Attributes
7
+ attr_reader :id
8
+ include Sfn::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
+
24
+ if result.first == :ok
25
+ self.attributes = JSON.parse(result.last)
26
+ was_loaded(result.last)
27
+ self
28
+ else
29
+ result
30
+ end
31
+ end
32
+
33
+ def was_loaded(result)
34
+ #override this to augment post-loading behavior
35
+ end
36
+
37
+ def delete
38
+ satisfaction.delete("#{path}.json")
39
+ end
40
+
41
+ def put(attrs)
42
+ params = requestify(attrs, self.class.name.demodulize.underscore)
43
+ result = satisfaction.put("#{path}.json", params)
44
+
45
+ if result.first == :ok
46
+ json = JSON.parse(result.last)
47
+ self.attributes = json
48
+ self
49
+ else
50
+ result
51
+ end
52
+ end
53
+
54
+ def loaded?
55
+ !@attributes.nil?
56
+ end
57
+
58
+ def inspect
59
+ if loaded?
60
+ "<#{self.class.name} #{attributes.map{|k,v| "#{k}: #{v}"}.join(' ') if !attributes.nil?}>"
61
+ else
62
+ "<#{self.class.name} #{path} UNLOADED>"
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ class Sfn::ResourceCollection < Sfn::HasSatisfaction
69
+ attr_reader :klass
70
+ attr_reader :path
71
+ include Sfn::Util
72
+
73
+ def initialize(klass, satisfaction, path)
74
+ super satisfaction
75
+ @klass = klass
76
+ @path = path
77
+ end
78
+
79
+ def page(number, options={})
80
+ Sfn::Page.new(self, number, options)
81
+ end
82
+
83
+ def get(id, options={})
84
+ #options currently ignored
85
+ satisfaction.identity_map.get_record(klass, id) do
86
+ klass.new(id, satisfaction)
87
+ end
88
+ end
89
+
90
+ def post(attrs)
91
+ params = requestify(attrs, klass.name.demodulize.underscore)
92
+ result = satisfaction.post("#{path}.json", params)
93
+
94
+ if result.first == :ok
95
+ json = JSON.parse(result.last)
96
+ id = json["id"]
97
+ obj = klass.new(id, satisfaction)
98
+ obj.attributes = json
99
+ obj
100
+ else
101
+ result
102
+ end
103
+ end
104
+
105
+ def [](*key)
106
+ options = key.extract_options!
107
+ case key.length
108
+ when 1
109
+ get(key, options)
110
+ when 2
111
+ page(key.first, options.merge(:limit => key.last))
112
+ else
113
+ raise ArgumentError, "Invalid Array arguement, only use 2-element array: :first is the page number, :last is the page size"
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ class Sfn::Page < Sfn::HasSatisfaction
120
+ attr_reader :total
121
+ attr_reader :collection
122
+
123
+ extend Forwardable
124
+ def_delegator :items, :first
125
+ def_delegator :items, :last
126
+ def_delegator :items, :each
127
+ def_delegator :items, :each_with_index
128
+ def_delegator :items, :inject
129
+ def_delegator :items, :reject
130
+ def_delegator :items, :select
131
+ def_delegator :items, :map
132
+ def_delegator :items, :[]
133
+ def_delegator :items, :length
134
+ def_delegator :items, :to_a
135
+ def_delegator :items, :empty?
136
+
137
+ def initialize(collection, page, options={})
138
+ super(collection.satisfaction)
139
+ @collection = collection
140
+ @klass = collection.klass
141
+ @page = page
142
+ @path = collection.path
143
+ @options = options
144
+ @options[:limit] ||= 10
145
+ end
146
+
147
+ # Retrieve the items for this page
148
+ # * Caches
149
+ def items
150
+ load
151
+ @data
152
+ end
153
+
154
+ def loaded?
155
+ !@data.nil?
156
+ end
157
+
158
+ def page_size
159
+ @options[:limit]
160
+ end
161
+
162
+ def next?
163
+ load #this loads the data, we shold probably make load set the ivar instead of items ;)
164
+ last_item = @page * page_size
165
+ @total > last_item
166
+ end
167
+
168
+ def next
169
+ return nil unless next?
170
+ self.class.new(@collection, @page + 1, @options)
171
+ end
172
+
173
+
174
+ def prev?
175
+ @page > 1
176
+ end
177
+
178
+ def prev
179
+ return nil unless prev?
180
+ self.class.new(@collection, @page - 1, @options)
181
+ end
182
+
183
+ def page_count
184
+ result = @total / length
185
+ result += 1 if @total % length != 0
186
+ result
187
+ end
188
+
189
+ def load(force=false)
190
+ return @data if loaded? && !force
191
+
192
+ result = satisfaction.get("#{@path}.json", @options.merge(:page => @page))
193
+
194
+ if result.first == :ok
195
+ json = JSON.parse(result.last)
196
+ @total = json["total"]
197
+
198
+ @data = json["data"].map do |result|
199
+ obj = @klass.decode_sfn(result, satisfaction)
200
+ satisfaction.identity_map.get_record(@klass, obj.id) do
201
+ obj
202
+ end
203
+ end
204
+ else
205
+ result
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,67 @@
1
+ module Sfn::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 Sfn::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 Sfn::Tag < Sfn::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 Sfn::Topic < Sfn::Resource
2
+ attributes :subject, :style, :content, :reply_count, :follower_count, :company_id, :at_sfn
3
+ attribute :last_active_at, :type => Time
4
+ attribute :created_at, :type => Time
5
+ attribute :author, :type => Sfn::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 Sfn::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
@@ -0,0 +1,75 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ruby-satisfaction}
5
+ s.version = "0.4.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Scott Fleckenstein", "Josh Nichols", "Pius Uzamere"]
9
+ s.date = %q{2009-07-10}
10
+ s.description = %q{Ruby interface to Get Satisfaction}
11
+ s.email = %q{scott@getsatisfaction.com}
12
+ s.extra_rdoc_files = [
13
+ "README.txt"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "CONTRIBUTORS.txt",
18
+ "License.txt",
19
+ "README.txt",
20
+ "Rakefile",
21
+ "VERSION.yml",
22
+ "init.rb",
23
+ "lib/satisfaction.rb",
24
+ "lib/satisfaction/associations.rb",
25
+ "lib/satisfaction/cache/hash.rb",
26
+ "lib/satisfaction/cache/memcache.rb",
27
+ "lib/satisfaction/company.rb",
28
+ "lib/satisfaction/external_dependencies.rb",
29
+ "lib/satisfaction/has_satisfaction.rb",
30
+ "lib/satisfaction/identity_map.rb",
31
+ "lib/satisfaction/loader.rb",
32
+ "lib/satisfaction/person.rb",
33
+ "lib/satisfaction/product.rb",
34
+ "lib/satisfaction/reply.rb",
35
+ "lib/satisfaction/resource.rb",
36
+ "lib/satisfaction/resource/attributes.rb",
37
+ "lib/satisfaction/tag.rb",
38
+ "lib/satisfaction/topic.rb",
39
+ "lib/satisfaction/util.rb",
40
+ "ruby-satisfaction.gemspec",
41
+ "spec/company_spec.rb",
42
+ "spec/identity_map_spec.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/nullstyle/ruby-satisfaction}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubyforge_project = %q{satisfaction}
49
+ s.rubygems_version = %q{1.3.4}
50
+ s.summary = %q{Ruby interface to Get Satisfaction}
51
+ s.test_files = [
52
+ "spec/company_spec.rb",
53
+ "spec/identity_map_spec.rb",
54
+ "spec/spec_helper.rb"
55
+ ]
56
+
57
+ if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
+ s.specification_version = 3
60
+
61
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
62
+ s.add_runtime_dependency(%q<memcache-client>, [">= 1.5.0"])
63
+ s.add_runtime_dependency(%q<oauth>, [">= 0.3.5"])
64
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.3.2"])
65
+ else
66
+ s.add_dependency(%q<memcache-client>, [">= 1.5.0"])
67
+ s.add_dependency(%q<oauth>, [">= 0.3.5"])
68
+ s.add_dependency(%q<activesupport>, [">= 2.3.2"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<memcache-client>, [">= 1.5.0"])
72
+ s.add_dependency(%q<oauth>, [">= 0.3.5"])
73
+ s.add_dependency(%q<activesupport>, [">= 2.3.2"])
74
+ end
75
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Company loader" do
4
+ it "should work" do
5
+ @satisfaction.set_consumer('lmwjv4kzwi27', 'fiei6iv61jnoukaq1aylwd8vcmnkafrs')
6
+ p @satisfaction.request_token
7
+ end
8
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Identity Map" do
4
+ it "should work single instances" do
5
+ c1 = @satisfaction.companies.get(4)
6
+ c2 = @satisfaction.companies.get(4)
7
+
8
+ c1.object_id.should == c2.object_id
9
+ end
10
+
11
+ it "should load one if the other gets loaded" do
12
+ c1 = @satisfaction.companies.get(4)
13
+ c2 = @satisfaction.companies.get(4)
14
+ c2.should_not be_loaded
15
+
16
+ c1.load
17
+
18
+ c2.should be_loaded
19
+ c2.domain.should == 'satisfaction'
20
+ end
21
+
22
+ it "should work with pages too" do
23
+ c1 = @satisfaction.companies.get(4)
24
+ c2 = @satisfaction.companies.page(1, :q => 'satisfaction').first
25
+
26
+ c1.object_id.should == c2.object_id
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
4
+
5
+ require 'satisfaction'
6
+
7
+ Spec::Runner.configure do |config|
8
+ config.prepend_before(:each){ @satisfaction = Satisfaction.new(:root => 'http://api.getsatisfaction.com') }
9
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nullstyle-ruby-satisfaction
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Scott Fleckenstein
8
+ - Josh Nichols
9
+ - Pius Uzamere
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2009-07-10 00:00:00 -07:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: memcache-client
19
+ type: :runtime
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 1.5.0
26
+ version:
27
+ - !ruby/object:Gem::Dependency
28
+ name: oauth
29
+ type: :runtime
30
+ version_requirement:
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 0.3.5
36
+ version:
37
+ - !ruby/object:Gem::Dependency
38
+ name: activesupport
39
+ type: :runtime
40
+ version_requirement:
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 2.3.2
46
+ version:
47
+ description: Ruby interface to Get Satisfaction
48
+ email: scott@getsatisfaction.com
49
+ executables: []
50
+
51
+ extensions: []
52
+
53
+ extra_rdoc_files:
54
+ - README.txt
55
+ files:
56
+ - .gitignore
57
+ - CONTRIBUTORS.txt
58
+ - License.txt
59
+ - README.txt
60
+ - Rakefile
61
+ - VERSION.yml
62
+ - init.rb
63
+ - lib/satisfaction.rb
64
+ - lib/satisfaction/associations.rb
65
+ - lib/satisfaction/cache/hash.rb
66
+ - lib/satisfaction/cache/memcache.rb
67
+ - lib/satisfaction/company.rb
68
+ - lib/satisfaction/external_dependencies.rb
69
+ - lib/satisfaction/has_satisfaction.rb
70
+ - lib/satisfaction/identity_map.rb
71
+ - lib/satisfaction/loader.rb
72
+ - lib/satisfaction/person.rb
73
+ - lib/satisfaction/product.rb
74
+ - lib/satisfaction/reply.rb
75
+ - lib/satisfaction/resource.rb
76
+ - lib/satisfaction/resource/attributes.rb
77
+ - lib/satisfaction/tag.rb
78
+ - lib/satisfaction/topic.rb
79
+ - lib/satisfaction/util.rb
80
+ - ruby-satisfaction.gemspec
81
+ - spec/company_spec.rb
82
+ - spec/identity_map_spec.rb
83
+ - spec/spec_helper.rb
84
+ has_rdoc: false
85
+ homepage: http://github.com/nullstyle/ruby-satisfaction
86
+ post_install_message:
87
+ rdoc_options:
88
+ - --charset=UTF-8
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: "0"
96
+ version:
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ version:
103
+ requirements: []
104
+
105
+ rubyforge_project: satisfaction
106
+ rubygems_version: 1.2.0
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Ruby interface to Get Satisfaction
110
+ test_files:
111
+ - spec/company_spec.rb
112
+ - spec/identity_map_spec.rb
113
+ - spec/spec_helper.rb