socialcastr 0.0.1 → 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.
Files changed (40) hide show
  1. data/.gitignore +1 -0
  2. data/README.markdown +29 -6
  3. data/examples/parse_messages.rb +16 -0
  4. data/examples/post_comment.rb +11 -0
  5. data/examples/post_message.rb +15 -0
  6. data/examples/search.rb +19 -0
  7. data/lib/socialcastr/api.rb +70 -78
  8. data/lib/socialcastr/base.rb +151 -0
  9. data/lib/socialcastr/collection.rb +30 -0
  10. data/lib/socialcastr/comment.rb +2 -2
  11. data/lib/socialcastr/exceptions.rb +75 -0
  12. data/lib/socialcastr/external_resource.rb +2 -2
  13. data/lib/socialcastr/flag.rb +5 -0
  14. data/lib/socialcastr/like.rb +1 -1
  15. data/lib/socialcastr/message.rb +45 -11
  16. data/lib/socialcastr/version.rb +2 -2
  17. data/lib/socialcastr.rb +19 -11
  18. data/socialcastr.gemspec +2 -0
  19. data/spec/api_spec.rb +167 -0
  20. data/spec/base_spec.rb +235 -0
  21. data/spec/comment_spec.rb +4 -0
  22. data/spec/configuration_spec.rb +97 -0
  23. data/spec/fixtures/demo_config.yml +3 -0
  24. data/spec/fixtures/message.xml +69 -0
  25. data/spec/fixtures/messages.xml +3338 -0
  26. data/spec/message_spec.rb +173 -0
  27. data/spec/socialcastr_spec.rb +58 -0
  28. data/spec/spec_helper.rb +54 -0
  29. metadata +62 -19
  30. data/lib/socialcastr/attachment_list.rb +0 -5
  31. data/lib/socialcastr/comment_list.rb +0 -5
  32. data/lib/socialcastr/external_resource_list.rb +0 -5
  33. data/lib/socialcastr/group_list.rb +0 -5
  34. data/lib/socialcastr/group_membership_list.rb +0 -5
  35. data/lib/socialcastr/like_list.rb +0 -5
  36. data/lib/socialcastr/media_file_list.rb +0 -5
  37. data/lib/socialcastr/message_list.rb +0 -5
  38. data/lib/socialcastr/recipient_list.rb +0 -6
  39. data/lib/socialcastr/stream_list.rb +0 -5
  40. data/lib/socialcastr/tag_list.rb +0 -5
data/.gitignore CHANGED
@@ -9,3 +9,4 @@ coverage/*
9
9
  doc/*
10
10
  log/*
11
11
  pkg/*
12
+ examples/socialcast.yml
data/README.markdown CHANGED
@@ -4,31 +4,54 @@ SocialCast gem is a ruby interface to the SocialCast REST API
4
4
 
5
5
  ## INSTALLATION
6
6
 
7
- gem install socialcast4r
7
+ gem install socialcastr
8
8
 
9
9
  ## Usage
10
10
 
11
+ # configure the connection
11
12
  Socialcastr.configuration do |socialcast|
12
13
  socialcast.username = "user@example.com"
13
14
  socialcast.password = "password"
14
15
  socialcast.domain = "demo.socialcast.com"
15
16
  end
16
17
 
18
+ # obtain an instance of the API (useful to directly issue get, put, post, delete commands)
17
19
  api = Socialcastr.api
18
20
 
19
- messages = api.messages
21
+ # find all messages (currently returns just one page - 20 elements)
22
+ messages = Socialcastr::Message.find(:all)
23
+
24
+ # build a new message object
25
+ message = Socialcastr::Message.new(
26
+ :title => "hallo world!",
27
+ "body" => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
28
+ )
20
29
 
21
- message_params = { "message[title]" => "hallo world!", "message[body]" => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
22
- reply = api.add_message(message_params)
23
- message = Socialcastr::Message.parse(reply)
30
+ message.new? # => true
31
+
32
+ # persist the message to Socialcast
33
+ message.save
34
+
35
+ # comment a message
36
+ message.comment! :text => "Hallo world"
37
+
38
+ # search for messages
39
+ messages = Socialcastr::Message.search(:q => "test")
40
+
41
+
24
42
 
25
43
 
26
44
  ## Status
27
45
 
28
46
  This is just the first draft of the wrapper. It can be improved in many, many ways.
29
- The API is not completely covered either: a lot of interesing stuff, like (un)liking and attachments have been left out.
47
+ The API is not completely covered either: some of interesing stuff, like message and comments attachments have been left out.
30
48
  Feel free to help (see Contributing below)
31
49
 
50
+ ## TODO
51
+
52
+ * Base
53
+ * CRUD for nested objects (comments, likes, attachments)
54
+
32
55
  ## Contributing to the code (a.k.a. submitting a pull request)
33
56
 
34
57
  1. Fork the project.
@@ -0,0 +1,16 @@
1
+ $:.unshift("./lib")
2
+ require 'rubygems'
3
+ require 'socialcastr'
4
+
5
+ Socialcastr.configuration do |config|
6
+ config.username = "username"
7
+ config.password = "password"
8
+ config.domain = "domain"
9
+ end
10
+
11
+ xml = File.read("/Users/bru/Desktop/socialcast.xml")
12
+ start_time = Time.new
13
+ messages = Socialcastr::Message.parse(xml)
14
+ end_time = Time.new
15
+
16
+ puts "Found #{messages.count} messages in #{end_time - start_time} seconds"
@@ -0,0 +1,11 @@
1
+ $:.unshift('./lib')
2
+ require 'rubygems'
3
+ require 'socialcastr'
4
+
5
+ Socialcastr.configuration do |c|
6
+ c.config_file = File.join(File.dirname(__FILE__), "socialcast.yml")
7
+ end
8
+
9
+ message = Socialcastr::Message.last
10
+
11
+ message.comment! :text => "hallo from Socialcastr"
@@ -0,0 +1,15 @@
1
+ $:.unshift('./lib')
2
+ require 'rubygems'
3
+ require 'socialcastr'
4
+
5
+ Socialcastr.configuration do |c|
6
+ c.config_file = File.join(File.dirname(__FILE__), "socialcast.yml")
7
+ end
8
+
9
+ message = Socialcastr::Message.new( :title => "hello world", :body => "yet another message")
10
+
11
+ puts "message created: #{message.to_params.inspect}"
12
+
13
+ message.save
14
+
15
+ puts "message now is #{ message.new? ? "still new" : "persisted"} with id #{message.id}"
@@ -0,0 +1,19 @@
1
+ $:.unshift('./lib')
2
+ require 'rubygems'
3
+ require 'socialcastr'
4
+
5
+ NEEDLE="welcome"
6
+ Socialcastr.configuration do |c|
7
+ c.config_file = File.join(File.dirname(__FILE__), 'socialcast.yml')
8
+ end
9
+
10
+
11
+ puts "Searching for #{NEEDLE}..."
12
+ messages = Socialcastr::Message.search(:q => NEEDLE)
13
+ puts "found #{messages.size} results:"
14
+
15
+ messages.each do |message|
16
+ puts "#{message.user.name}:\n\t#{message.title}\n\t#{message.body}"
17
+ end
18
+
19
+
@@ -5,8 +5,6 @@ require 'uri'
5
5
  require 'digest/md5'
6
6
  require 'cgi'
7
7
 
8
-
9
-
10
8
  module Socialcastr
11
9
  class API
12
10
  attr_accessor :debug
@@ -19,102 +17,86 @@ module Socialcastr
19
17
  @endpoint = "https://#{domain}/api/"
20
18
  return self
21
19
  end
20
+
21
+ def get(path, args={})
22
+ https_request(:get, path, args)
23
+ end
22
24
 
23
- def messages(stream=nil, query={})
24
- method="messages"
25
- method.insert(0, "streams/#{stream.id}/") if stream
26
- xml = api_get(method, query)
27
- return Socialcastr::MessageList.parse(xml).messages
28
- end
29
-
30
- def search(query)
31
- method="messages/search"
32
- xml = api_get(method, query)
33
- return Socialcastr::MessageList.parse(xml).messages
34
- end
35
-
36
- def groups
37
- method = "group_memberships"
38
- xml = api_get(method)
39
- return Socialcastr::GroupMembershipList.parse(xml).group_memberships
40
- end
41
-
42
- def streams
43
- method = "streams"
44
- xml = api_get(method)
45
- return Socialcastr::StreamList.parse(xml).streams
46
- end
47
-
48
- def add_message(message)
49
- xml = api_post("messages", message)
50
- return xml
51
- end
52
-
53
- def add_comment(message_id, comment)
54
- xml = api_post("messages/#{message_id}/comments", comment)
55
- return xml
25
+ def put(path, args={})
26
+ https_request(:put, path, args)
56
27
  end
57
-
58
- def like_comment(message_id,comment_id)
59
- xml = api_post("messages/#{message_id.to_s}/comments/#{comment_id.to_s}/likes")
60
- return xml
28
+
29
+ def post(path, args={})
30
+ https_request(:post, path, args)
61
31
  end
62
32
 
63
- def unlike_comment(message_id,comment_id,like_id)
64
- xml = api_delete("messages/#{message_id}/comments/#{comment_id}/likes/#{like_id}")
65
- return xml
33
+ def delete(path, args={})
34
+ https_request(:delete, path, args)
66
35
  end
67
-
36
+
68
37
  def https_request(method, path, args)
69
38
  https = setup_https
70
- response = ""
71
-
72
- # HACK
73
- # if path == "messages/search"
74
- # path = "messages"
75
- # end
76
- # data = File.read(File.join('/tmp','fixtures','xml', "#{path}.#{@format}"))
77
- # return data
78
- # # /HACK
79
39
 
80
40
  case method
81
- when 'get'
82
- request_class = Net::HTTP::Get
83
- query=args
84
- when 'post'
85
- request_class = Net::HTTP::Post
86
- form_data = args
87
- when 'put'
88
- request_class = Net::HTTP::Put
89
- form_data = args
90
- when 'delete'
91
- request_class = Net::HTTP::Delete
41
+ when :get
42
+ request_class = Net::HTTP::Get
43
+ query=args
44
+ when :post
45
+ request_class = Net::HTTP::Post
46
+ form_data = args
47
+ when :put
48
+ request_class = Net::HTTP::Put
49
+ form_data = args
50
+ when :delete
51
+ request_class = Net::HTTP::Delete
52
+ else
53
+ raise InvalidMethod
92
54
  end
55
+ response = nil
93
56
  https.start do |session|
94
- query_string = "/api/#{path}.#{@format}"
95
- query_string += "?" + (query.collect { |k,v| "#{k}=#{CGI::escape(v.to_s)}" }.join('&')) unless query.nil?
57
+ query_string = build_query_string(path, query)
96
58
  req = request_class.new(query_string)
97
59
  req.basic_auth @username, @password
98
60
  if form_data
99
61
  req.set_form_data(args, ';')
100
62
  end
101
- response = session.request(req).body
63
+ response = session.request(req)
102
64
  end
103
65
 
104
- response
105
- end
106
-
107
- def api_get(path, args={})
108
- https_request('get', path, args)
109
- end
110
-
111
-
112
- def api_post(path, args={})
113
- https_request('post', path, args)
66
+ return handle_response(response).body
114
67
  end
115
68
 
116
- def api_delete(path, args={})
117
- https_request('delete', path, args)
69
+
70
+ # Handles response and error codes from the remote service.
71
+ def handle_response(response)
72
+ case response.code.to_i
73
+ when 301,302
74
+ raise(Redirection.new(response))
75
+ when 200...400
76
+ response
77
+ when 400
78
+ raise(BadRequest.new(response))
79
+ when 401
80
+ raise(UnauthorizedAccess.new(response))
81
+ when 403
82
+ raise(ForbiddenAccess.new(response))
83
+ when 404
84
+ raise(ResourceNotFound.new(response))
85
+ when 405
86
+ raise(MethodNotAllowed.new(response))
87
+ when 409
88
+ raise(ResourceConflict.new(response))
89
+ when 410
90
+ raise(ResourceGone.new(response))
91
+ when 422
92
+ raise(ResourceInvalid.new(response))
93
+ when 401...500
94
+ raise(ClientError.new(response))
95
+ when 500...600
96
+ raise(ServerError.new(response))
97
+ else
98
+ raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
99
+ end
118
100
  end
119
101
 
120
102
  def setup_https
@@ -124,5 +106,15 @@ module Socialcastr
124
106
  https.use_ssl = true
125
107
  return https
126
108
  end
109
+
110
+ def build_query_string(path, query=nil)
111
+ params = []
112
+ unless query.nil?
113
+ params = query.collect do |k,v|
114
+ "#{k.to_s}=#{CGI::escape(v.to_s)}"
115
+ end
116
+ end
117
+ "/api#{path.to_s =~ /\// ? path.to_s : "/" + path.to_s }.#{@format}" + (params.any? ? "?" + params.join('&') : "")
118
+ end
127
119
  end
128
120
  end
@@ -1,6 +1,157 @@
1
1
  require 'sax-machine'
2
+
2
3
  module Socialcastr
3
4
  class Base
4
5
  include SAXMachine
6
+
7
+ def initialize(arguments={})
8
+ arguments.map do |k,v|
9
+ self.send((k.to_s + "=").to_sym, v)
10
+ end
11
+ end
12
+
13
+ def id
14
+ begin
15
+ tmp_id = send self.class.id_attribute
16
+ tmp_id.to_i unless tmp_id.nil?
17
+ rescue
18
+ nil
19
+ end
20
+ end
21
+
22
+ def save
23
+ new? ? create : update
24
+ end
25
+
26
+ def create
27
+ api.post(collection_path, to_params).tap do |xml|
28
+ copy_attributes_from_object(self.class.parse(xml))
29
+ end
30
+ end
31
+
32
+ def update
33
+ api.put(element_path, to_params).tap do |xml|
34
+ copy_attributes_from_object(self.class.parse(xml))
35
+ end
36
+ end
37
+
38
+ def copy_attributes_from_object(object=nil)
39
+ object.instance_variables.each do |v|
40
+ instance_variable_set(v, object.instance_variable_get(v))
41
+ end
42
+ end
43
+
44
+ def new?
45
+ id.nil?
46
+ end
47
+
48
+ def api
49
+ self.class.api
50
+ end
51
+
52
+ def element_path
53
+ self.class.element_path(self.id)
54
+ end
55
+
56
+ def collection_path
57
+ self.class.collection_path
58
+ end
59
+
60
+ def to_params
61
+ params = {}
62
+ instance_variables.each do |variable|
63
+ params[param_name(variable)] = instance_variable_get(variable)
64
+ end
65
+ params
66
+ end
67
+
68
+ def param_name(variable_name)
69
+ "#{self.class.model_name.downcase}[#{variable_name.to_s.gsub /@/,''}]"
70
+ end
71
+
72
+ class << self
73
+ def api
74
+ @api ||= Socialcastr.api
75
+ end
76
+
77
+ def find(*arguments)
78
+ scope = arguments.slice!(0)
79
+ options = arguments.slice!(0) || {}
80
+ case scope
81
+ when :all then find_every(options)
82
+ when :first then find_every(options).first
83
+ when :last then find_every(options).to_a.last
84
+ #when :one then find_one(options)
85
+ else find_single(scope, options)
86
+ end
87
+ end
88
+
89
+ def find_single(id, options)
90
+ path = element_path(id, options)
91
+ parse(api.get(path))
92
+ end
93
+
94
+ def find_every(options)
95
+ path = collection_path(options)
96
+ parse_collection(api.get(path))
97
+ end
98
+
99
+ def all(*arguments)
100
+ find(:all, *arguments)
101
+ end
102
+
103
+ def first(*arguments)
104
+ find(:first, *arguments)
105
+ end
106
+
107
+ def last(*arguments)
108
+ find(:last, *arguments)
109
+ end
110
+
111
+ def element_path(id, prefix_options = {})
112
+ "#{prefix(prefix_options)}#{collection_name}/#{URI.escape id.to_s}"
113
+ end
114
+
115
+ def collection_path(options = {})
116
+ "#{prefix(options)}#{collection_name}"
117
+ end
118
+
119
+ def prefix(options)
120
+ options.map { |k,v| k.to_s.gsub("_id", 's') + "/" + v.to_s }.join("/") + "/"
121
+ end
122
+
123
+ def model_name
124
+ self.to_s.gsub(/^.*::/, '')
125
+ end
126
+
127
+ def collection_name
128
+ model_name.downcase + "s"
129
+ end
130
+
131
+ def parse_collection(data)
132
+ collection_class.parse(data)
133
+ end
134
+
135
+ def id_attribute
136
+ model_name.downcase + "_id"
137
+ end
138
+
139
+ def collection_class
140
+ return @collection_class if @collection_class
141
+ class_name = model_name + "List"
142
+ model_class = self
143
+ c_element = model_name.downcase.to_sym
144
+ c_name = collection_name.to_sym
145
+ klass = Object.const_set(class_name,Class.new(Socialcastr::Collection))
146
+ klass.class_eval do
147
+ collection_of c_element, :as => c_name, :class => model_class
148
+ end
149
+ return @collection_class = klass
150
+ end
151
+
152
+ def id_element(name=:id)
153
+ element name, :as => id_attribute.to_sym
154
+ end
155
+ end
5
156
  end
6
157
  end
@@ -0,0 +1,30 @@
1
+ module Socialcastr
2
+ class Collection < Base
3
+ include Enumerable
4
+
5
+ def each &block
6
+ members.each{|member| block.call(member)}
7
+ end
8
+
9
+ def size
10
+ members.size
11
+ end
12
+
13
+ def members
14
+ send self.class.members_method
15
+ end
16
+
17
+ def self.collection_of(name, options={})
18
+ if options[:as]
19
+ @members_method = options[:as]
20
+ else
21
+ @members_method = name
22
+ end
23
+ elements name, options
24
+ end
25
+
26
+ def self.members_method
27
+ @members_method
28
+ end
29
+ end
30
+ end
@@ -1,10 +1,10 @@
1
1
  module Socialcastr
2
2
  class Comment < Base
3
3
  element :editable
4
- element :attachments, :as => :attachment_list, :class => Socialcastr::AttachmentList
4
+ elements :attachment, :as => :attachments, :class => Socialcastr::Attachment
5
5
  element :likable
6
6
  element :deletable
7
- element :likes, :as => :like_list, :class => Socialcastr::LikeList
7
+ elements :like, :as => :likes, :class => Socialcastr::Like
8
8
  element :permalink_url
9
9
  element :text
10
10
  element :user, :class => Socialcastr::User
@@ -0,0 +1,75 @@
1
+ module Socialcastr
2
+ class InvalidMethod < StandardError
3
+ end
4
+ class ConnectionError < StandardError # :nodoc:
5
+ attr_reader :response
6
+
7
+ def initialize(response, message = nil)
8
+ @response = response
9
+ @message = message
10
+ end
11
+
12
+ def to_s
13
+ message = "Failed."
14
+ message << " Response code = #{response.code}." if response.respond_to?(:code)
15
+ message << " Response message = #{response.message}." if response.respond_to?(:message)
16
+ message
17
+ end
18
+ end
19
+
20
+ # Raised when a Timeout::Error occurs.
21
+ class TimeoutError < ConnectionError
22
+ def initialize(message)
23
+ @message = message
24
+ end
25
+ def to_s; @message ;end
26
+ end
27
+
28
+ # Raised when a OpenSSL::SSL::SSLError occurs.
29
+ class SSLError < ConnectionError
30
+ def initialize(message)
31
+ @message = message
32
+ end
33
+ def to_s; @message ;end
34
+ end
35
+
36
+ # 3xx Redirection
37
+ class Redirection < ConnectionError # :nodoc:
38
+ def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
39
+ end
40
+
41
+ # 4xx Client Error
42
+ class ClientError < ConnectionError; end # :nodoc:
43
+
44
+ # 400 Bad Request
45
+ class BadRequest < ClientError; end # :nodoc
46
+
47
+ # 401 Unauthorized
48
+ class UnauthorizedAccess < ClientError; end # :nodoc
49
+
50
+ # 403 Forbidden
51
+ class ForbiddenAccess < ClientError; end # :nodoc
52
+
53
+ # 404 Not Found
54
+ class ResourceNotFound < ClientError; end # :nodoc:
55
+
56
+ # 409 Conflict
57
+ class ResourceConflict < ClientError; end # :nodoc:
58
+
59
+ # 410 Gone
60
+ class ResourceGone < ClientError; end # :nodoc:
61
+
62
+ # 422 Invalid
63
+ class ResourceInvalid < ClientError; end # :nodoc:
64
+
65
+
66
+ # 5xx Server Error
67
+ class ServerError < ConnectionError; end # :nodoc:
68
+
69
+ # 405 Method Not Allowed
70
+ class MethodNotAllowed < ClientError # :nodoc:
71
+ def allowed_methods
72
+ @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
73
+ end
74
+ end
75
+ end
@@ -1,10 +1,10 @@
1
1
  module Socialcastr
2
2
  class ExternalResource < Base
3
3
  element :type
4
- element :tags, :as => :tag_list, :class => Socialcastr::TagList
4
+ elements :tag, :as => :tags, :class => Socialcastr::Tag
5
5
  element :canonical_hashtag
6
6
  element :source, :class => Socialcastr::Source
7
- element :media_files, :as => :media_file_list, :class => Socialcastr::MediaFileList
7
+ elements :media_file, :as => :media_files, :class => Socialcastr::MediaFile
8
8
  element :url
9
9
  element :title
10
10
  element :description
@@ -0,0 +1,5 @@
1
+ module Socialcastr
2
+ class Flag < Base
3
+ id_element
4
+ end
5
+ end
@@ -1,9 +1,9 @@
1
1
  module Socialcastr
2
2
  class Like < Base
3
+ id_element
3
4
  element :unlikable
4
5
  element :user, :class => Socialcastr::User
5
6
  element :created_at
6
- element :id
7
7
  def unlikable_by?(api_id)
8
8
  @unlikable && api_id == self.user_id ? true : false
9
9
  end