delicious 1.0.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 (36) hide show
  1. checksums.yaml +7 -0
  2. data/delicious.gemspec +28 -0
  3. data/lib/delicious.rb +39 -0
  4. data/lib/delicious/api_model.rb +26 -0
  5. data/lib/delicious/bookmarks/api.rb +15 -0
  6. data/lib/delicious/bookmarks/methods/all.rb +112 -0
  7. data/lib/delicious/bookmarks/methods/create.rb +57 -0
  8. data/lib/delicious/bookmarks/methods/delete.rb +23 -0
  9. data/lib/delicious/bundle.rb +43 -0
  10. data/lib/delicious/bundles/api.rb +20 -0
  11. data/lib/delicious/bundles/methods/all.rb +23 -0
  12. data/lib/delicious/bundles/methods/delete.rb +23 -0
  13. data/lib/delicious/bundles/methods/find.rb +26 -0
  14. data/lib/delicious/bundles/methods/set.rb +27 -0
  15. data/lib/delicious/client.rb +57 -0
  16. data/lib/delicious/error.rb +6 -0
  17. data/lib/delicious/post.rb +42 -0
  18. data/lib/delicious/tag.rb +26 -0
  19. data/lib/delicious/tags/api.rb +15 -0
  20. data/lib/delicious/tags/methods/all.rb +24 -0
  21. data/lib/delicious/tags/methods/delete.rb +23 -0
  22. data/lib/delicious/tags/methods/rename.rb +24 -0
  23. data/lib/delicious/version.rb +5 -0
  24. data/spec/delicious/bookmarks/methods/all_spec.rb +122 -0
  25. data/spec/delicious/bookmarks/methods/create_spec.rb +120 -0
  26. data/spec/delicious/bundle_spec.rb +42 -0
  27. data/spec/delicious/bundles/methods/set_spec.rb +78 -0
  28. data/spec/delicious/client_spec.rb +189 -0
  29. data/spec/delicious/post_spec.rb +34 -0
  30. data/spec/delicious/tags/methods/all_spec.rb +70 -0
  31. data/spec/delicious/tags/methods/delete_spec.rb +47 -0
  32. data/spec/delicious/tags/methods/rename_spec.rb +47 -0
  33. data/spec/spec_helper.rb +11 -0
  34. data/spec/support/helpers/request_helper.rb +10 -0
  35. data/spec/support/shared/api_action.rb +13 -0
  36. metadata +175 -0
@@ -0,0 +1,27 @@
1
+ require 'active_support/concern'
2
+
3
+ module Delicious
4
+ module Bundles
5
+ module Methods
6
+
7
+ module Set
8
+ extend ActiveSupport::Concern
9
+
10
+ # Update bundle tags
11
+ #
12
+ # @param name [String] Bundle name
13
+ # @param tags [Array<String>] List of new tags for given bundle
14
+ # @raise [Delicious::Error] in case of error
15
+ # @return [Bundle]
16
+ def set(name, tags)
17
+ fail Delicious::Error, "Bundle name can't be blank" if name.nil?
18
+ fail Delicious::Error, 'Please specify at least 1 tag' unless (tags || []).any?
19
+ response = @client.connection.post '/v1/tags/bundles/set', bundle: name, tags: tags.join(',')
20
+ fail Delicious::Error, response.body['result'] unless 'ok' == response.body['result']
21
+ Bundle.build_persisted @client, name: name, tags: tags
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,57 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'multi_xml'
4
+ require 'delicious/version'
5
+
6
+ module Delicious
7
+ class Client
8
+ attr_accessor :access_token
9
+
10
+ # Initializes and configures Delicious client. The only requires configuration option
11
+ # for now is `access_token`. Example:
12
+ #
13
+ # ```ruby
14
+ # client = Delicious::Client.new do |client|
15
+ # client.access_token = 'my-access-token'
16
+ # end
17
+ # ```
18
+ def initialize(&block)
19
+ yield(self) if block_given?
20
+ end
21
+
22
+ # @return [Bookmarks::Api]
23
+ def bookmarks
24
+ @bookmarks ||= Bookmarks::Api.new(self)
25
+ end
26
+
27
+ # @return [Bundles::Api]
28
+ def bundles
29
+ @bundles ||= Bundles::Api.new(self)
30
+ end
31
+
32
+ # @return [Tags::Api]
33
+ def tags
34
+ @tags ||= Tags::Api.new(self)
35
+ end
36
+
37
+ # @return [Faraday::Connection]
38
+ def connection
39
+ Faraday.new(url: api_endpoint, headers: headers) do |c|
40
+ c.request :url_encoded
41
+ c.response :xml, content_type: /\bxml$/
42
+ c.adapter Faraday.default_adapter
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def api_endpoint
49
+ 'https://previous.delicious.com'.freeze
50
+ end
51
+
52
+ def headers
53
+ { authorization: "Bearer #{access_token}",
54
+ user_agent: "delicious-ruby #{Delicious.version}" }.freeze
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ module Delicious
2
+
3
+ class Error < StandardError
4
+ end
5
+
6
+ end
@@ -0,0 +1,42 @@
1
+ require 'active_model'
2
+
3
+ module Delicious
4
+ class Post
5
+ include ActiveModel::Model
6
+ include ActiveModel::Validations
7
+ include ApiModel
8
+
9
+ attr_accessor :url, :description, :extended, :tags, :dt, :shared
10
+
11
+ validates :url, presence: true
12
+ validates :description, presence: true
13
+
14
+ def tags=(t)
15
+ @tags = if t.respond_to?(:to_str)
16
+ t.split(',')
17
+ else
18
+ t || []
19
+ end
20
+ end
21
+
22
+ def shared
23
+ @shared ||= false
24
+ end
25
+
26
+ # Deletes this bookmark
27
+ #
28
+ # @raise [Delicious::Error] if bookmark was not saved yet
29
+ # @return [Boolean] `true` upon successful deletion, `false` otherwise
30
+ def delete
31
+ if persisted? && @delicious_client
32
+ @delicious_client.bookmarks.delete url: url
33
+ else
34
+ fail 'Bookmark was not saved yet'
35
+ end
36
+ end
37
+
38
+ def to_s
39
+ %Q(Delicious::Post(url: "#{url}", description: "#{description}", tags: #{tags}, extended: "#{extended}", dt: "#{dt}", shared: #{shared}))
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_model'
2
+
3
+ module Delicious
4
+ class Tag
5
+ include ActiveModel::Model
6
+ include ApiModel
7
+
8
+ attr_accessor :name, :count
9
+
10
+ # Deletes this tag
11
+ #
12
+ # @raise [Delicious::Error] if tag was not saved yet
13
+ # @return [Boolean] `true` upon successful deletion, `false` otherwise
14
+ def delete
15
+ if persisted? && @delicious_client
16
+ @delicious_client.tags.delete(name)
17
+ else
18
+ fail Delicious::Error, 'Tag was not saved yet'
19
+ end
20
+ end
21
+
22
+ def to_s
23
+ %Q(Tag(name: "#{name}", count: #{count}))
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Delicious
2
+ module Tags
3
+
4
+ class Api
5
+ include Methods::All
6
+ include Methods::Delete
7
+ include Methods::Rename
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support/concern'
2
+
3
+ module Delicious
4
+ module Tags
5
+ module Methods
6
+
7
+ module All
8
+ extend ActiveSupport::Concern
9
+
10
+ # Get all tags
11
+ #
12
+ # @return [Array<Tag>] List of tags
13
+ def all
14
+ response = @client.connection.get '/v1/tags/get'
15
+ fail Delicious::Error, 'Error getting tags' unless response.body['tags']
16
+ response.body['tags']['tag'].map do |attrs|
17
+ Tag.build_persisted @client, name: attrs['tag'], count: attrs['count'].to_i
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_support/concern'
2
+
3
+ module Delicious
4
+ module Tags
5
+ module Methods
6
+
7
+ module Delete
8
+ extend ActiveSupport::Concern
9
+
10
+ # Deletes tag
11
+ #
12
+ # @param tag [String] Tag name
13
+ # @return [Boolean] `true` on successful deletion
14
+ def delete(tag)
15
+ response = @client.connection.post '/v1/tags/delete', tag: tag
16
+ code = response.body['result']['code']
17
+ 'done' == code
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support/concern'
2
+
3
+ module Delicious
4
+ module Tags
5
+ module Methods
6
+
7
+ module Rename
8
+ extend ActiveSupport::Concern
9
+
10
+ # Renames tag
11
+ #
12
+ # @param old_name [String] Old tag name
13
+ # @param new_name [String] New tag name
14
+ # @return [Boolean] `true` on successful rename
15
+ def rename(old_name, new_name)
16
+ response = @client.connection.post '/v1/tags/rename', 'old' => old_name, 'new' => new_name
17
+ code = response.body['result']['code']
18
+ 'done' == code
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module Delicious
2
+ def self.version
3
+ '1.0.0'
4
+ end
5
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delicious::Bookmarks::Methods::All do
4
+ let(:client) do
5
+ Delicious::Client.new do |config|
6
+ config.access_token = 'my-access-token'
7
+ end
8
+ end
9
+
10
+ describe '#all' do
11
+ let(:result) { :success }
12
+ let(:method) { :get }
13
+ let(:endpoint) { %r{https:\/\/previous\.delicious\.com\/v1\/posts\/all\?(.+)} }
14
+ let(:action) { client.bookmarks.all.to_a }
15
+ let(:success_body) do
16
+ <<-EOT
17
+ <?xml version="1.0" encoding="UTF-8"?>
18
+ <posts tag="" total="748" user="slurmdrinker">
19
+ <post description="Angular Classy" extended="" hash="ee7e657f5f5998fbc136e5910080bd85" href="http://davej.github.io/angular-classy/" private="no" shared="yes" tag="angularjs,javascript,controller,angular,angular.js,library" time="2014-04-30T18:12:46Z"/>
20
+ <post description="Postgresql Array and Hstore Column Reference - Application Development - HoneyCo" extended="" hash="598b527aa58ce750807b9b02308dde07" href="http://tastehoneyco.com/blog/postgresql-array-and-hstore-column-reference/?utm_source=rubyweekly&amp;utm_medium=email/" private="no" shared="yes" tag="postgresql,rails,ruby,postgres,hstore,arrays" time="2014-04-20T18:00:21Z"/>
21
+ </posts>
22
+ EOT
23
+ end
24
+ let(:failure_body) { '<?xml version="1.0" encoding="UTF-8"?><result code="no bookmarks"/>' }
25
+
26
+ before do
27
+ body = result == :failure ? failure_body : success_body
28
+ @request = stub_request(method, endpoint)
29
+ .to_return body: body, headers: {'Content-Type' => 'text/xml; charset=UTF-8'}
30
+ end
31
+
32
+ it_behaves_like 'api action'
33
+
34
+ it 'sends /v1/posts/all request' do
35
+ action
36
+ expect(WebMock).to have_requested(:get, endpoint)
37
+ end
38
+
39
+ describe 'no bookmarks' do
40
+ let(:result) { :failure }
41
+
42
+ it 'returns empty array' do
43
+ expect(action).to eq []
44
+ end
45
+ end
46
+
47
+ describe 'limit / offset' do
48
+ it 'allows to limit count of results' do
49
+ client.bookmarks.all.limit(10).to_a
50
+ expect(WebMock).to have_requested(:get, endpoint).with(query: hash_including({ 'results' => '10' }))
51
+ end
52
+
53
+ it 'allows to start results with given offset' do
54
+ client.bookmarks.all.offset(10).to_a
55
+ expect(WebMock).to have_requested(:get, endpoint).with(query: hash_including({ 'start' => '10' }))
56
+ end
57
+
58
+ it 'allows to specify limit and offset at the same time' do
59
+ client.bookmarks.all.offset(10).limit(15).to_a
60
+ expect(WebMock).to have_requested(:get, endpoint).with(query: hash_including({ 'start' => '10', 'results' => '15' }))
61
+ end
62
+ end
63
+
64
+ describe 'filtering' do
65
+ it 'by tag' do
66
+ client.bookmarks.all.tag('angular').to_a
67
+ expect(WebMock).to have_requested(:get, endpoint).with(query: hash_including({ 'tag' => 'angular' }))
68
+ end
69
+
70
+ context 'by date' do
71
+ it 'accepts Time instances' do
72
+ from = Time.parse '2013-11-12T10:23:00Z'
73
+ to = Time.parse '2013-11-13T12:10:00Z'
74
+ client.bookmarks.all.from(from).to(to).to_a
75
+ expect(WebMock).to have_requested(:get, endpoint)
76
+ .with(query: hash_including({ 'fromdt' => '2013-11-12T10:23:00Z',
77
+ 'todt' => '2013-11-13T12:10:00Z' }))
78
+ end
79
+
80
+ it 'accepts strings' do
81
+ from = '2013/11/12 10:23:00'
82
+ to = '2013/11/13 12:10:00'
83
+ client.bookmarks.all.from(from).to(to).to_a
84
+ expect(WebMock).to have_requested(:get, endpoint)
85
+ .with(query: hash_including({ 'fromdt' => '2013-11-12T10:23:00Z',
86
+ 'todt' => '2013-11-13T12:10:00Z' }))
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'result' do
92
+ it 'has 2 items' do
93
+ expect(action.count).to eq 2
94
+ end
95
+
96
+ describe 'first' do
97
+ it 'is an instance of Delicious::Post' do
98
+ expect(action.first).to be_an_instance_of Delicious::Post
99
+ end
100
+
101
+ it 'has attributes set' do
102
+ post = action.first
103
+ expect(post.description).to eq 'Angular Classy'
104
+ expect(post.tags).to eq %w(angularjs javascript controller angular angular.js library)
105
+ expect(post.shared).to eq true
106
+ expect(post.dt).to eq '2014-04-30T18:12:46Z'
107
+ end
108
+
109
+ it 'is persisted' do
110
+ post = action.first
111
+ expect(post).to be_persisted
112
+ end
113
+
114
+ it 'can be deleted' do
115
+ post = action.first
116
+ expect(client.bookmarks).to receive(:delete).with(url: post.url)
117
+ post.delete
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delicious::Bookmarks::Methods::Create do
4
+ let(:client) do
5
+ Delicious::Client.new do |config|
6
+ config.access_token = 'my-access-token'
7
+ end
8
+ end
9
+
10
+ describe '#create' do
11
+ let(:result) { :success }
12
+
13
+ let(:success_body) { '<?xml version="1.0" encoding="UTF-8"?><result code="done"/>' }
14
+ let(:failure_body) { '<?xml version="1.0" encoding="UTF-8"?><result code="error adding link"/>' }
15
+
16
+ let(:method) { :post }
17
+ let(:endpoint) { 'https://previous.delicious.com/v1/posts/add' }
18
+
19
+ let(:tags) { 'tag1, tag2' }
20
+ let(:dt) { '2014-05-04T22:01:00Z' }
21
+ let(:attrs) do
22
+ { url: 'http://example.com/cool-blog-post',
23
+ description: 'Cool post, eh?',
24
+ extended: 'Extended info',
25
+ tags: tags,
26
+ dt: dt,
27
+ replace: 'no',
28
+ shared: 'no'
29
+ }
30
+ end
31
+ let(:action) { client.bookmarks.create attrs }
32
+
33
+ before do
34
+ body = result == :failure ? failure_body : success_body
35
+ @request = stub_request(method, endpoint)
36
+ .to_return body: body, headers: {'Content-Type' => 'text/xml; charset=UTF-8'}
37
+ end
38
+
39
+ context 'valid attributes given' do
40
+ it_behaves_like 'api action'
41
+
42
+ context 'params' do
43
+ it 'sends url=http://example.com/cool-blog-post' do
44
+ action
45
+ expect(WebMock).to have_requested(:post, endpoint).with { |r| assert_param(r, 'url', 'http://example.com/cool-blog-post') }
46
+ end
47
+
48
+ it 'sends description=Cool post, eh?' do
49
+ action
50
+ expect(WebMock).to have_requested(:post, endpoint).with { |r| assert_param(r, 'description', 'Cool post, eh?') }
51
+ end
52
+
53
+ describe 'tags' do
54
+ context 'comma-separated string' do
55
+ let(:tags) { 'tag1,tag2' }
56
+
57
+ it 'sends tags=tag1, tag2' do
58
+ action
59
+ expect(WebMock).to have_requested(:post, endpoint).with { |r| assert_param(r, 'tags', 'tag1,tag2') }
60
+ end
61
+ end
62
+
63
+ context 'array of tags' do
64
+ let(:tags) { %w(tag1 tag2) }
65
+
66
+ it 'sends tags=tag1, tag2' do
67
+ action
68
+ expect(WebMock).to have_requested(:post, endpoint).with { |r| assert_param(r, 'tags', 'tag1,tag2') }
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ describe 'result' do
75
+ subject { action }
76
+
77
+ context 'success' do
78
+ let(:result) { :success }
79
+
80
+ it { should be_an_instance_of Delicious::Post }
81
+ it 'has url' do
82
+ expect(subject.url).to eq 'http://example.com/cool-blog-post'
83
+ end
84
+ it 'returns not persisted Post object' do
85
+ expect(subject).to be_persisted
86
+ end
87
+ end
88
+
89
+ context 'failure' do
90
+ let(:result) { :failure }
91
+
92
+ it 'throws an error' do
93
+ expect { subject }.to raise_error
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'invalid attributes given' do
100
+ let(:attrs) do
101
+ { description: 'Cool site' }
102
+ end
103
+
104
+ it 'does not sends request' do
105
+ action
106
+ expect(WebMock).not_to have_requested(:post, endpoint)
107
+ end
108
+
109
+ it 'returns invalid Post object' do
110
+ p = action
111
+ expect(p).not_to be_valid
112
+ end
113
+
114
+ it 'returns not persisted Post object' do
115
+ p = action
116
+ expect(p).not_to be_persisted
117
+ end
118
+ end
119
+ end
120
+ end