hootenanny 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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