pius-ruby-satisfaction 0.3.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/.gemified ADDED
@@ -0,0 +1,12 @@
1
+ ---
2
+ :author: Scott Fleckenstein
3
+ :dependencies:
4
+ - rspec
5
+ - oauth
6
+ - memcache-client
7
+ - "activesupport"
8
+ :version: 0.2.0
9
+ :name: ruby-satisfaction
10
+ :summary: Helper gem for the getsatisfaction.com API
11
+ :email: nullstyle@gmail.com
12
+ :homepage: http://nullstyle.com
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ tmp/*
2
+ log/*
3
+ pkg
data/CONTRIBUTORS.txt ADDED
@@ -0,0 +1,7 @@
1
+ John Duff
2
+
3
+
4
+ Awards
5
+ ======
6
+
7
+ John Duff gets the "Getting the ball rolling" achievement award for being the first contributor besides myself.
data/License.txt ADDED
@@ -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.
data/README.txt ADDED
@@ -0,0 +1 @@
1
+ README
data/Rakefile ADDED
@@ -0,0 +1,14 @@
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/pius/ruby-satisfaction"
9
+ gemspec.authors = ["Scott Fleckenstein", "Josh Nichols", "Pius Uzamere"]
10
+ VERSION = '0.3.0'
11
+ end
12
+ rescue LoadError
13
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
14
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'satisfaction'
@@ -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,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,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.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.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,23 @@
1
+ class Sfn::Search
2
+ attr_reader :results, :root_url, :loader
3
+
4
+ def initialize(root_url, loader)
5
+ @root_url = root_url
6
+ @loader = loader
7
+ @results = {}
8
+ end
9
+
10
+ def for_likely_matches_to(name, entities = %w(companies products))
11
+ @results = entities.inject({}) {|hash, entity|
12
+ query_string = "#{@root_url}/#{entity}.json?q=#{name}"
13
+ #result = Net::HTTP.get_response(URI.parse(query_string)).body
14
+ answer = @loader.get(query_string)
15
+ if answer[0] == :ok
16
+ result = answer[1]
17
+ hash.merge({entity => JSON.parse(result)})
18
+ else
19
+ raise "Search service not available at the moment, please try again later."
20
+ end
21
+ }
22
+ end
23
+ 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,9 @@
1
+ class Satisfaction #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,162 @@
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
+ require 'satisfaction/search'
15
+
16
+
17
+ # =============
18
+ # = Resources =
19
+ # =============
20
+
21
+ require 'satisfaction/company'
22
+ require 'satisfaction/person'
23
+ require 'satisfaction/topic'
24
+ require 'satisfaction/tag'
25
+ require 'satisfaction/product'
26
+ require 'satisfaction/reply'
27
+
28
+ # =============
29
+
30
+ include Associations
31
+
32
+ attr_reader :options
33
+ attr_reader :loader
34
+ attr_reader :consumer
35
+ attr_reader :token
36
+ attr_reader :identity_map
37
+ attr_reader :search
38
+
39
+
40
+ def initialize(options={})
41
+ @options = options.reverse_merge({
42
+ :root => "http://api.getsatisfaction.com",
43
+ :autoload => false,
44
+ :request_token_url => 'http://getsatisfaction.com/api/request_token',
45
+ :access_token_url => 'http://getsatisfaction.com/api/access_token',
46
+ :authorize_url => 'http://getsatisfaction.com/api/authorize',
47
+ })
48
+ @loader = Sfn::Loader.new
49
+ @identity_map = Sfn::IdentityMap.new
50
+ @search = Sfn::Search.new(options[:root], @loader)
51
+
52
+ has_many :companies, :url => '/companies'
53
+ has_many :people, :url => '/people'
54
+ has_many :topics, :url => '/topics'
55
+ has_many :replies, :url => '/replies'
56
+ has_many :tags, :url => '/tags'
57
+ has_many :products, :url => '/products'
58
+ end
59
+
60
+ def satisfaction
61
+ self
62
+ end
63
+
64
+ def me
65
+ me = satisfaction.identity_map.get_record(Me, 'me') do
66
+ Sfn::Me.new('me', satisfaction)
67
+ end
68
+
69
+ if me.loaded?
70
+ me
71
+ else
72
+ me.load
73
+ end
74
+ end
75
+
76
+ def autoload?
77
+ options[:autoload]
78
+ end
79
+
80
+ def set_basic_auth(user, password)
81
+ identity_map.expire_record(Me, 'me')
82
+ @user = user
83
+ @password = password
84
+ end
85
+
86
+ def set_consumer(key, secret)
87
+ identity_map.expire_record(Me, 'me')
88
+ @consumer = OAuth::Consumer.new(key, secret)
89
+ end
90
+
91
+ def set_token(token, secret)
92
+ identity_map.expire_record(Me, 'me')
93
+ @token = OAuth::Token.new(token, secret)
94
+ end
95
+
96
+ def request_token
97
+ result, body = *@loader.get("#{options[:request_token_url]}", :force => true, :consumer => @consumer, :token => nil)
98
+ raise "Could not retrieve request token" unless result == :ok
99
+ response = CGI.parse(body)
100
+ OAuth::Token.new(response["oauth_token"], response["oauth_token_secret"])
101
+ end
102
+
103
+ def authorize_url(token)
104
+ "#{options[:authorize_url]}?oauth_token=#{token.token}"
105
+ end
106
+
107
+ def access_token(token)
108
+ result, body = *@loader.get("#{options[:access_token_url]}", :force => true, :consumer => @consumer, :token => token)
109
+ raise "Could not retrieve access token" unless result == :ok
110
+ response = CGI.parse(body)
111
+ OAuth::Token.new(response["oauth_token"], response["oauth_token_secret"])
112
+ end
113
+
114
+
115
+ def url(path, query_string={})
116
+ qs = query_string.map{|kv| URI.escape(kv.first.to_s) + "=" + URI.escape(kv.last.to_s)}.join("&")
117
+ URI.parse("#{@options[:root]}#{path}?#{qs}")
118
+ end
119
+
120
+ def get(path, query_string={})
121
+ url = self.url(path, query_string)
122
+
123
+ @loader.get(url, :consumer => @consumer, :token => @token, :user => @user, :password => @password)
124
+
125
+ end
126
+
127
+ def post(path, form={})
128
+ url = self.url(path)
129
+ @loader.post(url,
130
+ :consumer => @consumer,
131
+ :token => @token,
132
+ :user => @user,
133
+ :password => @password,
134
+ :form => form)
135
+ end
136
+
137
+ def delete(path)
138
+ url = self.url(path)
139
+ @loader.post(url,
140
+ :consumer => @consumer,
141
+ :token => @token,
142
+ :user => @user,
143
+ :password => @password,
144
+ :method => :delete)
145
+ end
146
+
147
+ def put(path, form={})
148
+ url = self.url(path)
149
+ @loader.post(url,
150
+ :consumer => @consumer,
151
+ :token => @token,
152
+ :user => @user,
153
+ :password => @password,
154
+ :method => :put,
155
+ :form => form)
156
+ end
157
+
158
+ private
159
+ def validate_options
160
+ raise ArgumentError, "You must specify a location for the API's service root" if options[:root].blank?
161
+ end
162
+ 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,24 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Searching" do
4
+ it "should not blow up" do
5
+ results = @satisfaction.search.for_likely_matches_to('Cyberdyne')
6
+ end
7
+
8
+ it "should be a well-formed hash with default keys" do
9
+ results = @satisfaction.search.for_likely_matches_to('Cyberdyne')
10
+ results.should_not be_empty
11
+ results.class.should == Hash
12
+ results.keys.should include('products')
13
+ results.keys.should include('companies')
14
+ #raise results.inspect
15
+ end
16
+
17
+ it "should put the results into a set of arrays keyed by category" do
18
+ results = @satisfaction.search.for_likely_matches_to('Cyberdyne')
19
+
20
+ c = results['companies']
21
+ c.class.should == Array
22
+ include('companies')
23
+ end
24
+ 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,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pius-ruby-satisfaction
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.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
+
18
+ description: Ruby interface to Get Satisfaction
19
+ email: scott@getsatisfaction.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files:
25
+ - README.txt
26
+ files:
27
+ - .gemified
28
+ - .gitignore
29
+ - CONTRIBUTORS.txt
30
+ - License.txt
31
+ - README.txt
32
+ - Rakefile
33
+ - VERSION
34
+ - init.rb
35
+ - lib/satisfaction.rb
36
+ - lib/satisfaction/associations.rb
37
+ - lib/satisfaction/cache/hash.rb
38
+ - lib/satisfaction/cache/memcache.rb
39
+ - lib/satisfaction/company.rb
40
+ - lib/satisfaction/external_dependencies.rb
41
+ - lib/satisfaction/has_satisfaction.rb
42
+ - lib/satisfaction/identity_map.rb
43
+ - lib/satisfaction/loader.rb
44
+ - lib/satisfaction/person.rb
45
+ - lib/satisfaction/product.rb
46
+ - lib/satisfaction/reply.rb
47
+ - lib/satisfaction/resource.rb
48
+ - lib/satisfaction/resource/attributes.rb
49
+ - lib/satisfaction/search.rb
50
+ - lib/satisfaction/tag.rb
51
+ - lib/satisfaction/topic.rb
52
+ - lib/satisfaction/util.rb
53
+ - lib/satisfaction/version.rb
54
+ - spec/company_spec.rb
55
+ - spec/identity_map_spec.rb
56
+ - spec/search_spec.rb
57
+ - spec/spec_helper.rb
58
+ has_rdoc: false
59
+ homepage: http://github.com/pius/ruby-satisfaction
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.2.0
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Ruby interface to Get Satisfaction
84
+ test_files:
85
+ - spec/company_spec.rb
86
+ - spec/identity_map_spec.rb
87
+ - spec/search_spec.rb
88
+ - spec/spec_helper.rb