hootenanny 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 (112) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +5 -7
  4. data/Gemfile.lock +46 -28
  5. data/app/controllers/hootenanny/notifications_controller.rb +31 -0
  6. data/app/controllers/hootenanny/parameters.rb +16 -0
  7. data/app/controllers/hootenanny/subscriptions_controller.rb +24 -3
  8. data/app/models/hootenanny/publish_notification.rb +90 -0
  9. data/app/models/hootenanny/subscription.rb +72 -23
  10. data/config/routes.rb +2 -1
  11. data/db/migrate/20130607182642_add_started_at_and_lease_duration_to_subscriptions.rb +15 -0
  12. data/db/migrate/20130608225621_add_hmac_secret_to_subscription.rb +5 -0
  13. data/db/migrate/20130611235218_add_publish_notifications.rb +9 -0
  14. data/db/migrate/20130612153138_add_timestamps_to_publish_notification.rb +5 -0
  15. data/db/migrate/20130705200729_add_processed_flag_to_publish_notifications.rb +6 -0
  16. data/db/migrate/20130711061329_switch_publish_notification_process_state_from_boolean_to_string.rb +11 -0
  17. data/db/migrate/20130711061558_add_index_to_notifications_state.rb +5 -0
  18. data/hootenanny.gemspec +6 -2
  19. data/lib/hootenanny/configuration.rb +43 -0
  20. data/lib/hootenanny/correspondent.rb +44 -0
  21. data/lib/hootenanny/errors.rb +42 -1
  22. data/lib/hootenanny/feed.rb +75 -0
  23. data/lib/hootenanny/feed/atom_feed.rb +18 -0
  24. data/lib/hootenanny/feed/atom_feed_item.rb +8 -0
  25. data/lib/hootenanny/feed/digest_feed.rb +14 -0
  26. data/lib/hootenanny/feed/digest_feed_item.rb +11 -0
  27. data/lib/hootenanny/feed/feed_item.rb +30 -0
  28. data/lib/hootenanny/feed/file.rb +66 -0
  29. data/lib/hootenanny/feed/json_feed.rb +48 -0
  30. data/lib/hootenanny/feed/json_feed_item.rb +8 -0
  31. data/lib/hootenanny/feed/null_feed.rb +27 -0
  32. data/lib/hootenanny/feed/rss_feed.rb +52 -0
  33. data/lib/hootenanny/feed/rss_feed_item.rb +8 -0
  34. data/lib/hootenanny/feed_store.rb +30 -0
  35. data/lib/hootenanny/feed_store/file_feed_store.rb +55 -0
  36. data/lib/hootenanny/feed_store/web_feed_store.rb +42 -0
  37. data/lib/hootenanny/hub.rb +116 -22
  38. data/lib/hootenanny/publish_notification_expiration_policy.rb +17 -0
  39. data/lib/hootenanny/request.rb +40 -0
  40. data/lib/hootenanny/request/publish_notification.rb +94 -0
  41. data/lib/hootenanny/request/subscription.rb +153 -0
  42. data/lib/hootenanny/subscription_delivery.rb +47 -0
  43. data/lib/hootenanny/topic.rb +38 -0
  44. data/lib/hootenanny/topic_synchronizer.rb +71 -0
  45. data/lib/hootenanny/uri.rb +53 -0
  46. data/lib/hootenanny/verification.rb +108 -0
  47. data/lib/hootenanny/version.rb +1 -1
  48. data/spec/dummy/config/database.yml +12 -6
  49. data/spec/dummy/db/migrate/20130607183149_add_started_at_and_lease_duration_to_subscriptions.hootenanny.rb +16 -0
  50. data/spec/dummy/db/migrate/20130608231253_add_hmac_secret_to_subscription.hootenanny.rb +6 -0
  51. data/spec/dummy/db/migrate/20130611235546_add_publish_notifications.hootenanny.rb +10 -0
  52. data/spec/dummy/db/migrate/20130612153353_add_timestamps_to_publish_notification.hootenanny.rb +6 -0
  53. data/spec/dummy/db/migrate/20130705200832_add_processed_flag_to_publish_notifications.hootenanny.rb +7 -0
  54. data/spec/dummy/db/migrate/20130711061518_switch_publish_notification_process_state_from_boolean_to_string.hootenanny.rb +12 -0
  55. data/spec/dummy/db/migrate/20130711061629_add_index_to_notifications_state.hootenanny.rb +6 -0
  56. data/spec/factories/publish_notification.rb +13 -0
  57. data/spec/factories/requests/publish_notification.rb +11 -0
  58. data/spec/factories/requests/subscription.rb +46 -0
  59. data/spec/factories/subscription.rb +14 -2
  60. data/spec/factories/verification.rb +15 -0
  61. data/spec/features/publishers/can_notify_the_hub_of_content_updates_spec.rb +58 -0
  62. data/spec/features/subscribers/are_protected_from_unwarranted_subscriptions_spec.rb +59 -0
  63. data/spec/features/subscribers/can_receive_distributions_of_topic_content_spec.rb +175 -0
  64. data/spec/features/subscribers/can_subscribe_to_a_topic_spec.rb +76 -7
  65. data/spec/fixtures/feeds/atom/97d79220f68b4bf27.atom +0 -0
  66. data/spec/fixtures/feeds/atom/sample_feed.atom +51 -0
  67. data/spec/fixtures/feeds/digest/97d79220f68b4bf27.digest +1 -0
  68. data/spec/fixtures/feeds/digest/complete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +6 -0
  69. data/spec/fixtures/feeds/digest/incomplete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +5 -0
  70. data/spec/fixtures/feeds/digest/sample_feed.digest +6 -0
  71. data/spec/fixtures/feeds/json/97d79220f68b4bf27.json +1 -0
  72. data/spec/fixtures/feeds/json/feed_with_one_item.json +21 -0
  73. data/spec/fixtures/feeds/json/sample_feed.json +30 -0
  74. data/spec/fixtures/feeds/rss/5b187098da59f077f/97d79220f68b4bf27.rss +21 -0
  75. data/spec/fixtures/feeds/rss/97d79220f68b4bf27.rss +0 -0
  76. data/spec/fixtures/feeds/rss/feed_with_one_item.rss +15 -0
  77. data/spec/fixtures/feeds/rss/minimal_feed.rss +12 -0
  78. data/spec/fixtures/feeds/rss/sample_feed.rss +22 -0
  79. data/spec/fixtures/feeds/rss/sample_feed_2.rss +22 -0
  80. data/spec/lib/hootenanny/configuration_spec.rb +7 -0
  81. data/spec/lib/hootenanny/correspondent_spec.rb +94 -0
  82. data/spec/lib/hootenanny/errors_spec.rb +21 -0
  83. data/spec/lib/hootenanny/feed/atom_feed_item_spec.rb +9 -0
  84. data/spec/lib/hootenanny/feed/atom_feed_spec.rb +40 -0
  85. data/spec/lib/hootenanny/feed/digest_feed_item_spec.rb +9 -0
  86. data/spec/lib/hootenanny/feed/digest_feed_spec.rb +40 -0
  87. data/spec/lib/hootenanny/feed/feed_item_spec.rb +49 -0
  88. data/spec/lib/hootenanny/feed/file_spec.rb +66 -0
  89. data/spec/lib/hootenanny/feed/json_feed_item_spec.rb +9 -0
  90. data/spec/lib/hootenanny/feed/json_feed_spec.rb +128 -0
  91. data/spec/lib/hootenanny/feed/null_feed_spec.rb +9 -0
  92. data/spec/lib/hootenanny/feed/rss_feed_item_spec.rb +9 -0
  93. data/spec/lib/hootenanny/feed/rss_feed_spec.rb +143 -0
  94. data/spec/lib/hootenanny/feed_spec.rb +159 -0
  95. data/spec/lib/hootenanny/feed_store/file_feed_store_spec.rb +58 -0
  96. data/spec/lib/hootenanny/feed_store/web_feed_store_spec.rb +47 -0
  97. data/spec/lib/hootenanny/feed_store_spec.rb +27 -0
  98. data/spec/lib/hootenanny/hub_spec.rb +73 -0
  99. data/spec/lib/hootenanny/publish_notification_expiration_policy_spec.rb +35 -0
  100. data/spec/lib/hootenanny/request/publish_notification_spec.rb +43 -0
  101. data/spec/lib/hootenanny/request/subscription_spec.rb +89 -0
  102. data/spec/lib/hootenanny/request_spec.rb +21 -0
  103. data/spec/lib/hootenanny/subscription_delivery_spec.rb +54 -0
  104. data/spec/lib/hootenanny/topic_spec.rb +15 -0
  105. data/spec/lib/hootenanny/topic_synchronizer_spec.rb +98 -0
  106. data/spec/lib/hootenanny/uri_spec.rb +32 -0
  107. data/spec/lib/hootenanny/verification_spec.rb +92 -0
  108. data/spec/models/hootenanny/publish_notification_spec.rb +55 -0
  109. data/spec/models/hootenanny/subscription_spec.rb +58 -19
  110. data/spec/support/verification.rb +63 -0
  111. metadata +231 -14
  112. data/spec/controllers/hootenanny/hub_spec.rb +0 -15
data/.gitignore CHANGED
@@ -31,5 +31,6 @@ capybara-*
31
31
  *.scssc
32
32
  spec/dummy/tmp
33
33
  spec/dummy/db/schema.rb
34
+ pkg/
34
35
 
35
36
  *_spec.js
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ script: cd spec/dummy && rake db:create:all db:migrate db:test:prepare && cd ../.. && bundle exec rspec spec
data/Gemfile CHANGED
@@ -17,10 +17,8 @@ gem "jquery-rails"
17
17
  # gem 'debugger'
18
18
  gem 'strong_parameters'
19
19
 
20
- gem 'rspectacular', :require => false
21
- gem 'database_cleaner'
22
- gem 'sqlite3'
23
- gem 'rspec-rails'
24
- gem 'shoulda-matchers'
25
- gem 'factory_girl_rails'
26
- gem 'awesome_print'
20
+ group :development do
21
+ gem 'pry'
22
+ # gem 'pry-plus'
23
+ gem 'awesome_print'
24
+ end
@@ -1,7 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hootenanny (0.0.1)
4
+ hootenanny (0.1.0)
5
+ chronological (~> 1.0.0beta10)
6
+ faraday (~> 0.8.7)
5
7
  rails (~> 3.2)
6
8
  strong_parameters (~> 0.2.1)
7
9
 
@@ -35,10 +37,14 @@ GEM
35
37
  activesupport (3.2.13)
36
38
  i18n (= 0.6.1)
37
39
  multi_json (~> 1.0)
40
+ addressable (2.3.4)
38
41
  arel (3.0.2)
39
42
  awesome_print (1.1.0)
40
43
  builder (3.0.4)
41
- database_cleaner (1.0.1)
44
+ chronological (1.0.0beta10)
45
+ coderay (1.0.9)
46
+ crack (0.3.2)
47
+ database_cleaner (0.9.1)
42
48
  diff-lcs (1.2.4)
43
49
  erubis (2.7.0)
44
50
  factory_girl (4.2.0)
@@ -46,11 +52,13 @@ GEM
46
52
  factory_girl_rails (4.2.1)
47
53
  factory_girl (~> 4.2.0)
48
54
  railties (>= 3.0.0)
49
- fuubar (1.1.0)
55
+ faraday (0.8.7)
56
+ multipart-post (~> 1.1)
57
+ fuubar (1.1.1)
50
58
  rspec (~> 2.0)
51
59
  rspec-instafail (~> 0.2.0)
52
- ruby-progressbar (~> 1.0.0)
53
- hike (1.2.2)
60
+ ruby-progressbar (~> 1.0)
61
+ hike (1.2.3)
54
62
  i18n (0.6.1)
55
63
  journey (1.0.4)
56
64
  jquery-rails (2.2.1)
@@ -60,10 +68,16 @@ GEM
60
68
  mail (2.5.4)
61
69
  mime-types (~> 1.16)
62
70
  treetop (~> 1.4.8)
71
+ method_source (0.8.1)
63
72
  mime-types (1.23)
64
- multi_json (1.7.3)
73
+ multi_json (1.7.6)
74
+ multipart-post (1.2.0)
65
75
  pg (0.15.1)
66
76
  polyglot (0.3.3)
77
+ pry (0.9.12.2)
78
+ coderay (~> 1.0.5)
79
+ method_source (~> 0.8)
80
+ slop (~> 3.4)
67
81
  rack (1.4.5)
68
82
  rack-cache (1.2)
69
83
  rack (>= 0.4)
@@ -89,57 +103,61 @@ GEM
89
103
  rake (10.0.4)
90
104
  rdoc (3.12.2)
91
105
  json (~> 1.4)
92
- rspec (2.13.0)
93
- rspec-core (~> 2.13.0)
94
- rspec-expectations (~> 2.13.0)
95
- rspec-mocks (~> 2.13.0)
96
- rspec-core (2.13.1)
97
- rspec-expectations (2.13.0)
106
+ rspec (2.14.0.rc1)
107
+ rspec-core (= 2.14.0.rc1)
108
+ rspec-expectations (= 2.14.0.rc1)
109
+ rspec-mocks (= 2.14.0.rc1)
110
+ rspec-core (2.14.0.rc1)
111
+ rspec-expectations (2.14.0.rc1)
98
112
  diff-lcs (>= 1.1.3, < 2.0)
99
113
  rspec-instafail (0.2.4)
100
- rspec-mocks (2.13.1)
101
- rspec-rails (2.13.2)
114
+ rspec-mocks (2.14.0.rc1)
115
+ rspec-rails (2.14.0.rc1)
102
116
  actionpack (>= 3.0)
103
117
  activesupport (>= 3.0)
104
118
  railties (>= 3.0)
105
- rspec-core (~> 2.13.0)
106
- rspec-expectations (~> 2.13.0)
107
- rspec-mocks (~> 2.13.0)
119
+ rspec-core (= 2.14.0.rc1)
120
+ rspec-expectations (= 2.14.0.rc1)
121
+ rspec-mocks (= 2.14.0.rc1)
108
122
  rspectacular (0.13.0)
109
123
  fuubar (~> 1.0)
110
124
  rspec (~> 2.12)
111
- ruby-progressbar (1.0.2)
112
- shoulda-matchers (2.1.0)
113
- activesupport (>= 3.0.0)
125
+ ruby-progressbar (1.1.1)
126
+ slop (3.4.5)
114
127
  sprockets (2.2.2)
115
128
  hike (~> 1.2)
116
129
  multi_json (~> 1.0)
117
130
  rack (~> 1.0)
118
131
  tilt (~> 1.1, != 1.3.0)
119
- sqlite3 (1.3.7)
120
132
  strong_parameters (0.2.1)
121
133
  actionpack (~> 3.0)
122
134
  activemodel (~> 3.0)
123
135
  railties (~> 3.0)
124
136
  thor (0.18.1)
125
137
  tilt (1.4.1)
126
- treetop (1.4.12)
138
+ timecop (0.6.1)
139
+ treetop (1.4.14)
127
140
  polyglot
128
141
  polyglot (>= 0.3.1)
129
142
  tzinfo (0.3.37)
143
+ webmock (1.11.0)
144
+ addressable (>= 2.2.7)
145
+ crack (>= 0.3.2)
130
146
 
131
147
  PLATFORMS
132
148
  ruby
133
149
 
134
150
  DEPENDENCIES
135
151
  awesome_print
136
- database_cleaner
137
- factory_girl_rails
152
+ database_cleaner (~> 0.9.1)
153
+ factory_girl_rails (~> 4.2)
138
154
  hootenanny!
139
155
  jquery-rails
140
156
  pg
141
- rspec-rails
142
- rspectacular
143
- shoulda-matchers
144
- sqlite3
157
+ pry
158
+ rspec (~> 2.14.0.rc1)
159
+ rspec-rails (~> 2.14.0.rc1)
160
+ rspectacular (~> 0.13)
145
161
  strong_parameters
162
+ timecop (~> 0.6.1)
163
+ webmock (~> 1.11)
@@ -0,0 +1,31 @@
1
+ require 'hootenanny/hub'
2
+ require 'controllers/hootenanny/parameters'
3
+
4
+ module Hootenanny
5
+ class PublishNotificationsController < ActionController::Base
6
+ include Hootenanny::Parameters
7
+
8
+ def create
9
+ if Hootenanny::Hub.notify_of_publication(publish_notification_request)
10
+ render :nothing => true, :status => :no_content
11
+ end
12
+ rescue Hootenanny::Error => error
13
+ render json: error,
14
+ status: :bad_request
15
+ end
16
+
17
+ private
18
+
19
+ def publish_notification_request
20
+ @request ||= Hootenanny::Hub.request publish_notification_request_params
21
+ end
22
+
23
+ def publish_notification_request_params
24
+ params[:type] ||= :publish_notification
25
+
26
+ params.permit(:url)
27
+
28
+ params
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module Hootenanny
2
+ module Parameters
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
7
+ render json: {
8
+ error: {
9
+ message: "Required parameter missing: #{parameter_missing_exception.param}"
10
+ }
11
+ },
12
+ status: :bad_request
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,14 +1,35 @@
1
1
  require 'hootenanny/hub'
2
+ require 'controllers/hootenanny/parameters'
2
3
 
3
4
  module Hootenanny
4
5
  class SubscriptionsController < ActionController::Base
6
+ include Hootenanny::Parameters
7
+
5
8
  def create
6
- if Hootenanny::Hub.subscribe(params[:callback], to: params[:topic])
9
+ if Hootenanny::Hub.subscribe(subscription_request)
7
10
  render :nothing => true, :status => :no_content
8
11
  end
9
- rescue Hootenanny::SubscriptionAssignmentError => e
10
- render json: { error: e.to_h },
12
+ rescue Hootenanny::Error => error
13
+ render json: error,
11
14
  status: :bad_request
12
15
  end
16
+
17
+ private
18
+
19
+ def subscription_request
20
+ @request ||= Hootenanny::Hub.request subscription_request_params
21
+ end
22
+
23
+ def subscription_request_params
24
+ params[:type] ||= :subscription
25
+
26
+ params.require(:callback)
27
+ params.require(:topic)
28
+ params.permit(:lease_seconds,
29
+ :verify_token,
30
+ :secret)
31
+
32
+ params
33
+ end
13
34
  end
14
35
  end
@@ -0,0 +1,90 @@
1
+ require 'hootenanny/uri'
2
+
3
+ module Hootenanny
4
+ class PublishNotification < ActiveRecord::Base
5
+ self.table_name = 'hootenanny_publish_notifications'
6
+
7
+ ###
8
+ # Private: Retrieves a set of notifications for a given set of topics. If
9
+ # given one topic, only one notification should be returned since there can
10
+ # only be one notification for a given topic in the queue at any time.
11
+ #
12
+ # topic - A String or an Array representing the Topic to look for.
13
+ #
14
+ # Examples:
15
+ #
16
+ # Hootenanny::PublishNotification.for('http://mytopic')
17
+ # #=> <ActiveRecord::Relation>
18
+ #
19
+ # Hootenanny::PublishNotification.for %w{http://mytopic
20
+ # http://anothertopic}
21
+ # #=> <ActiveRecord::Relation>
22
+ #
23
+ # Returns an ActiveRecord::Relation
24
+ #
25
+ def self.for(topic)
26
+ where(:topic => topic)
27
+ end
28
+
29
+ def self.unprocessed
30
+ where(:state => 'unprocessed')
31
+ end
32
+
33
+ def self.processed
34
+ where(:state => 'processed')
35
+ end
36
+
37
+ def self.expired
38
+ where(:state => 'expired')
39
+ end
40
+
41
+ ###
42
+ # Private: Creates a notification for a given topic. If the notification for
43
+ # the topic exists, it touches it to reflect the new update time and returns
44
+ # it. It does _not_ create a new notification.
45
+ #
46
+ # topic - A String representing a URI of a topic which has been proposed to
47
+ # have been updated.
48
+ #
49
+ # Examples:
50
+ #
51
+ # Hootenanny::PublishNotification.notify('http://example.com')
52
+ # # => <Hootenanny::PublishNotification topic: 'http://example.com'>
53
+ #
54
+ # Returns a Hootenanny::PublishNotification
55
+ # Raises a Hootenanny::URI::InvalidSchemeError if the topic URI are using
56
+ # something other than HTTP or HTTPS
57
+ # Raises a Hootenanny::URI::InvalidError if the URI can't be parsed properly
58
+ #
59
+ def self.notify(topic)
60
+ topic = Hootenanny::URI.parse(topic).to_s
61
+
62
+ find_or_initialize(topic).touch
63
+ end
64
+
65
+ def self.each_unprocessed
66
+ unprocessed.find_each do |notification|
67
+ yield notification
68
+ end
69
+ end
70
+
71
+ def process
72
+ self.update_attributes(:state => 'processed')
73
+ end
74
+
75
+ def expire
76
+ self.update_attributes(:state => 'expired')
77
+ end
78
+
79
+ def processed?
80
+ self.state == 'processed'
81
+ end
82
+
83
+ private
84
+
85
+ def self.find_or_initialize(topic)
86
+ where(topic: topic).first ||
87
+ PublishNotification.create!(topic: topic)
88
+ end
89
+ end
90
+ end
@@ -1,13 +1,26 @@
1
- require 'uri'
2
- require 'hootenanny/errors'
1
+ require 'active_record'
2
+ require 'chronological'
3
+ require 'hootenanny/uri'
3
4
 
4
5
  module Hootenanny
5
6
  class Subscription < ActiveRecord::Base
7
+ extend Chronological
8
+
6
9
  self.table_name = 'hootenanny_subscriptions'
7
10
 
11
+ ###
12
+ # Chronological generates time range logic based on :started_at and
13
+ # :duration_in_seconds
14
+ #
15
+ timeframe type: :duration_from_start,
16
+ duration: :lease_duration
17
+
8
18
  ###
9
19
  # Private: Finds all subscriptions which are subscribed to a given topic
10
20
  #
21
+ # It takes into account only _active_ subscriptions. If the subscription has
22
+ # expired, it will not be returned.
23
+ #
11
24
  # topic - A String represeenting a topic which has been subscribed to
12
25
  #
13
26
  # Example:
@@ -19,28 +32,39 @@ class Subscription < ActiveRecord::Base
19
32
  # subscriptions which are subscribed to the topic.
20
33
  #
21
34
  def self.to(topic)
22
- where(:topic => topic)
35
+ active.where(:topic => topic.to_s)
23
36
  end
24
37
 
25
38
  ###
26
- # Private: Assigns a given subscriber to a given topic URL by creating
39
+ # Private: Subscribes a given subscriber to a given topic URL by creating
27
40
  # a Subscription.
28
41
  #
42
+ # This operation is idempotent. It will not modify a subscription if one
43
+ # already exists for the subscriber/topic passed in.
44
+ #
29
45
  # options - A Hash of options
30
46
  #
31
- # :subscriber - A String representing the URI which should be
32
- # notified when changes occur on the topic
33
- # :to - A String representing the topic URI which is
34
- # publishing the items that the subscriber is
35
- # interested in.
47
+ # :subscriber - A String representing the URI which should be
48
+ # notified when changes occur on the topic
49
+ # :to - A String representing the topic URI which is
50
+ # publishing the items that the subscriber is
51
+ # interested in.
52
+ # :digest_secret - A String token of no more than 200 bytes which
53
+ # will be used during content distribution so that
54
+ # the subscriber can verify that the request is
55
+ # from the hub and not a undesired 3rd party
56
+ # (optional).
57
+ # :lease_duration - A Number representing the length of time (in
58
+ # seconds) after the subscription is created until
59
+ # it should be considered invalid
36
60
  #
37
61
  # Example:
38
62
  #
39
63
  # # If a subscription does not yet exist for the subscriber/topic, it
40
64
  # # creates # a new Subscription
41
65
  #
42
- # Subscription.assign(subscriber: 'http://example.com/my_callback',
43
- # to: 'http://example.org/my_topic')
66
+ # Subscription.subscribe(subscriber: 'http://example.com/my_callback',
67
+ # to: 'http://example.org/my_topic')
44
68
  #
45
69
  # # => <Subscription subscriber: 'http://example.com/my_callback',
46
70
  # topic: 'http://example.org/my_topic'>
@@ -48,24 +72,49 @@ class Subscription < ActiveRecord::Base
48
72
  #
49
73
  #
50
74
  # # If a subscription already exists for the subscriber/topic, it retrieves
51
- # # and returns the current Subscription
75
+ # # and returns the current Subscription with all other attributes updated
76
+ # # as if the subscription were new.
52
77
  #
53
- # Subscription.assign(subscriber: 'http://example.com/my_callback',
54
- # to: 'http://example.org/my_topic')
78
+ # Subscription.subscribe(subscriber: 'http://example.com/my_callback',
79
+ # to: 'http://example.org/my_topic',
80
+ # digest_secret: 'new_secret')
55
81
  #
56
- # # => <Subscription subscriber: 'http://example.com/my_callback',
57
- # topic: 'http://example.org/my_topic'>
82
+ # # => <Subscription subscriber: 'http://example.com/my_callback',
83
+ # topic: 'http://example.org/my_topic',
84
+ # digest_secret: 'new_secret'>
58
85
  #
59
86
  # Returns a Subscription
87
+ # Raises a Hootenanny::URI::InvalidSchemeError if the URIs are using something
88
+ # other than HTTP or HTTPS
89
+ # Raises a Hootenanny::URI::InvalidError if the URI can't be parsed properly
60
90
  #
61
- def self.assign(options = {})
62
- subscriber = URI.parse(options.fetch(:subscriber)).to_s
63
- topic = URI.parse(options.fetch(:to)).to_s
91
+ def self.subscribe(options = {})
92
+ subscriber = Hootenanny::URI.parse(options.fetch(:subscriber)).to_s
93
+ topic = Hootenanny::URI.parse(options.fetch(:to)).to_s
94
+
95
+ attrs = {}
96
+ attrs[:started_at] = Time.now.utc
97
+ attrs[:digest_secret] = options.fetch(:digest_secret, nil)
98
+ attrs[:lease_duration] = options.fetch(:lease_duration)
99
+
100
+ subscription = find_or_initialize(subscriber, topic)
101
+
102
+ subscription.update_attributes(attrs)
103
+
104
+ subscription
105
+ end
106
+
107
+ def subscriber
108
+ Hootenanny::URI.parse(read_attribute(:subscriber))
109
+ end
110
+
111
+ private
64
112
 
65
- where( subscriber: subscriber, topic: topic).first ||
66
- create(subscriber: subscriber, topic: topic)
67
- rescue URI::InvalidURIError
68
- raise Hootenanny::SubscriptionAssignmentError.new('All options passed need to be valid URIs')
113
+ def self.find_or_initialize(subscriber, topic)
114
+ where(subscriber: subscriber,
115
+ topic: topic).first ||
116
+ Subscription.new(subscriber: subscriber,
117
+ topic: topic)
69
118
  end
70
119
  end
71
120
  end