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.
- checksums.yaml +7 -0
- data/delicious.gemspec +28 -0
- data/lib/delicious.rb +39 -0
- data/lib/delicious/api_model.rb +26 -0
- data/lib/delicious/bookmarks/api.rb +15 -0
- data/lib/delicious/bookmarks/methods/all.rb +112 -0
- data/lib/delicious/bookmarks/methods/create.rb +57 -0
- data/lib/delicious/bookmarks/methods/delete.rb +23 -0
- data/lib/delicious/bundle.rb +43 -0
- data/lib/delicious/bundles/api.rb +20 -0
- data/lib/delicious/bundles/methods/all.rb +23 -0
- data/lib/delicious/bundles/methods/delete.rb +23 -0
- data/lib/delicious/bundles/methods/find.rb +26 -0
- data/lib/delicious/bundles/methods/set.rb +27 -0
- data/lib/delicious/client.rb +57 -0
- data/lib/delicious/error.rb +6 -0
- data/lib/delicious/post.rb +42 -0
- data/lib/delicious/tag.rb +26 -0
- data/lib/delicious/tags/api.rb +15 -0
- data/lib/delicious/tags/methods/all.rb +24 -0
- data/lib/delicious/tags/methods/delete.rb +23 -0
- data/lib/delicious/tags/methods/rename.rb +24 -0
- data/lib/delicious/version.rb +5 -0
- data/spec/delicious/bookmarks/methods/all_spec.rb +122 -0
- data/spec/delicious/bookmarks/methods/create_spec.rb +120 -0
- data/spec/delicious/bundle_spec.rb +42 -0
- data/spec/delicious/bundles/methods/set_spec.rb +78 -0
- data/spec/delicious/client_spec.rb +189 -0
- data/spec/delicious/post_spec.rb +34 -0
- data/spec/delicious/tags/methods/all_spec.rb +70 -0
- data/spec/delicious/tags/methods/delete_spec.rb +47 -0
- data/spec/delicious/tags/methods/rename_spec.rb +47 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/helpers/request_helper.rb +10 -0
- data/spec/support/shared/api_action.rb +13 -0
- 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,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,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,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&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
|