Merge pull request #116 from cantino/do_not_symbolize

Do not symbolize

Andrew Cantino 11 years ago
parent
commit
3d8393357e
60 changed files with 986 additions and 824 deletions
  1. 2 1
      CHANGES.md
  2. 4 2
      README.md
  3. 1 1
      VERSION
  4. 1 0
      app/assets/javascripts/application.js.coffee.erb
  5. 4 0
      app/assets/stylesheets/application.css.scss.erb
  6. 3 3
      app/concerns/email_concern.rb
  7. 8 8
      app/concerns/twitter_concern.rb
  8. 6 6
      app/concerns/weibo_concern.rb
  9. 1 1
      app/controllers/events_controller.rb
  10. 1 1
      app/controllers/logs_controller.rb
  11. 17 12
      app/models/agent.rb
  12. 3 0
      app/models/agent_log.rb
  13. 13 13
      app/models/agents/adioso_agent.rb
  14. 14 14
      app/models/agents/digest_email_agent.rb
  15. 4 4
      app/models/agents/email_agent.rb
  16. 22 22
      app/models/agents/event_formatting_agent.rb
  17. 120 121
      app/models/agents/human_task_agent.rb
  18. 2 2
      app/models/agents/manual_event_agent.rb
  19. 27 29
      app/models/agents/peak_detector_agent.rb
  20. 4 4
      app/models/agents/post_agent.rb
  21. 10 10
      app/models/agents/sentiment_agent.rb
  22. 15 15
      app/models/agents/translation_agent.rb
  23. 24 24
      app/models/agents/trigger_agent.rb
  24. 25 25
      app/models/agents/twilio_agent.rb
  25. 20 20
      app/models/agents/twitter_publish_agent.rb
  26. 23 23
      app/models/agents/twitter_stream_agent.rb
  27. 12 12
      app/models/agents/twitter_user_agent.rb
  28. 3 3
      app/models/agents/user_location_agent.rb
  29. 9 9
      app/models/agents/weather_agent.rb
  30. 8 8
      app/models/agents/webhook_agent.rb
  31. 39 39
      app/models/agents/website_agent.rb
  32. 19 19
      app/models/agents/weibo_publish_agent.rb
  33. 11 11
      app/models/agents/weibo_user_agent.rb
  34. 6 8
      app/models/event.rb
  35. 1 1
      app/views/agents/show.html.erb
  36. 1 1
      app/views/layouts/application.html.erb
  37. 0 7
      config/initializers/recursively_symbolize_keys.rb
  38. 67 0
      db/migrate/20131223032112_switch_to_json_serialization.rb
  39. 14 0
      db/migrate/20131227000021_add_cached_dates_to_agent.rb
  40. 42 0
      lib/json_serialized_field.rb
  41. 9 0
      lib/json_with_indifferent_access.rb
  42. 0 43
      lib/serialize_and_symbolize.rb
  43. 0 11
      lib/utils.rb
  44. 2 2
      spec/controllers/agents_controller_spec.rb
  45. 3 1
      spec/controllers/logs_controller_spec.rb
  46. 3 3
      spec/controllers/user_location_updates_controller_spec.rb
  47. 2 2
      spec/controllers/webhooks_controller_spec.rb
  48. 7 7
      spec/fixtures/agents.yml
  49. 2 2
      spec/fixtures/events.yml
  50. 8 0
      spec/models/agent_log_spec.rb
  51. 70 2
      spec/models/agent_spec.rb
  52. 5 5
      spec/models/agents/digest_email_agent_spec.rb
  53. 4 4
      spec/models/agents/email_agent_spec.rb
  54. 162 162
      spec/models/agents/human_task_agent_spec.rb
  55. 29 29
      spec/models/agents/peak_detector_agent_spec.rb
  56. 1 1
      spec/models/agents/sentiment_agent_spec.rb
  57. 53 53
      spec/models/agents/trigger_agent_spec.rb
  58. 7 7
      spec/models/agents/twitter_stream_agent_spec.rb
  59. 4 6
      spec/models/agents/webhook_agent_spec.rb
  60. 9 5
      spec/models/agents/website_agent_spec.rb

+ 2 - 1
CHANGES.md

@@ -1,8 +1,9 @@
1 1
 # Changes
2 2
 
3
+* 0.3 (Jan 1, 2014)    - Remove symbolization of memory, options, and payloads; convert memory, options, and payloads to JSON from YAML.  Migration will perform conversion and adjust tables to be UTF-8.  Recommend making a DB backup before migrating.
3 4
 * 0.2 (Nov 6, 2013)    - PeakDetectorAgent now uses `window_duration_in_days` and `min_peak_spacing_in_days`.  Additionally, peaks trigger when the time series rises over the standard deviation multiple, not after it starts to fall.
4 5
 * June 29, 2013        - Removed rails\_admin because it was causing deployment issues. Better to have people install their favorite admin tool if they want one.
5 6
 * June, 2013           - A number of new agents have been contributed, including interfaces to Weibo, Twitter, and Twilio, as well as Agents for translation, sentiment analysis, and for posting and receiving webhooks.
6
-* March 24, 2013 (0.1) - Refactored loading of Agents for `check` and `receive` to use ids instead of full objects.  This should fix the too-large delayed_job issues.  Added `system_timer` and `fastercsv` to the Gemfile for the Ruby 1.8 platform.
7
+* March 24, 2013 (0.1) - Refactored loading of Agents for `check` and `receive` to use ids instead of full objects.  This should fix the too-large delayed\_job issues.  Added `system_timer` and `fastercsv` to the Gemfile for the Ruby 1.8 platform.
7 8
 * March 18, 2013       - Added Wiki page about the [Agent API](https://github.com/cantino/huginn/wiki/Creating-a-new-agent).
8 9
 * March 17, 2013       - Switched to JSONPath for defining paths through JSON structures.  The WebsiteAgent can now scrape and parse JSON.

+ 4 - 2
README.md

@@ -13,7 +13,10 @@ Huginn is a system for building agents that perform automated tasks for you onli
13 13
 * Track the weather and get an email when it's going to rain (or snow) tomorrow
14 14
 * Follow your project names on Twitter and get updates when people mention them
15 15
 * Scrape websites and receive emails when they change
16
+* Compose digest emails about things you care about to be sent at specific times of the day
17
+* Track counts of high frequency events and SMS on changes, such as the term "san francisco emergency"
16 18
 * Track your location over time
19
+* Create Amazon Mechanical Turk tasks as the input, or output, of events
17 20
 
18 21
 Follow [@tectonic](https://twitter.com/tectonic) for updates as Huginn evolves, and join us in \#huginn on Freenode IRC to discuss the project.
19 22
 
@@ -84,7 +87,7 @@ In order to use the WeatherAgent you need an [API key with Wunderground](http://
84 87
 
85 88
 You can use [Post Location](https://github.com/cantino/post_location) on your iPhone to post your location to an instance of the UserLocationAgent.  Make a new one to see instructions.
86 89
 
87
-#### Enable DelayedJobWeb for handy delayed_job monitoring and control
90
+#### Enable DelayedJobWeb for handy delayed\_job monitoring and control
88 91
 
89 92
 * Edit `config.ru`, uncomment the DelayedJobWeb section, and change the DelayedJobWeb username and password.
90 93
 * Uncomment `match "/delayed_job" => DelayedJobWeb, :anchor => false` in `config/routes.rb`.
@@ -110,6 +113,5 @@ Please fork, add specs, and send pull requests!
110 113
 
111 114
 [![Build Status](https://travis-ci.org/cantino/huginn.png)](https://travis-ci.org/cantino/huginn) [![Code Climate](https://codeclimate.com/github/cantino/huginn.png)](https://codeclimate.com/github/cantino/huginn)
112 115
 
113
-
114 116
 [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/cantino/huginn/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
115 117
 

+ 1 - 1
VERSION

@@ -1 +1 @@
1
-0.2
1
+0.3

+ 1 - 0
app/assets/javascripts/application.js.coffee.erb

@@ -102,6 +102,7 @@ $(document).ready ->
102 102
       $("#logs .refresh, #logs .clear").hide()
103 103
       $.post "/agents/#{agentId}/logs/clear", { "_method": "DELETE" }, (html) =>
104 104
         $("#logs .logs").html html
105
+        $("#show-tabs li a.recent-errors").removeClass 'recent-errors'
105 106
         $("#logs .spinner").stop(true, true).fadeOut ->
106 107
           $("#logs .refresh, #logs .clear").show()
107 108
 

+ 4 - 0
app/assets/stylesheets/application.css.scss.erb

@@ -122,3 +122,7 @@ span.not-applicable:after {
122 122
     margin: 0 10px;
123 123
   }
124 124
 }
125
+
126
+#show-tabs li a.recent-errors {
127
+  font-weight: bold;
128
+}

+ 3 - 3
app/concerns/email_concern.rb

@@ -1,18 +1,18 @@
1 1
 module EmailConcern
2 2
   extend ActiveSupport::Concern
3 3
 
4
-  MAIN_KEYS = %w[title message text main value].map(&:to_sym)
4
+  MAIN_KEYS = %w[title message text main value]
5 5
 
6 6
   included do
7 7
     self.validate :validate_email_options
8 8
   end
9 9
 
10 10
   def validate_email_options
11
-    errors.add(:base, "subject and expected_receive_period_in_days are required") unless options[:subject].present? && options[:expected_receive_period_in_days].present?
11
+    errors.add(:base, "subject and expected_receive_period_in_days are required") unless options['subject'].present? && options['expected_receive_period_in_days'].present?
12 12
   end
13 13
 
14 14
   def working?
15
-    last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
15
+    last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
16 16
   end
17 17
 
18 18
   def present(payload)

+ 8 - 8
app/concerns/twitter_concern.rb

@@ -7,20 +7,20 @@ module TwitterConcern
7 7
   end
8 8
 
9 9
   def validate_twitter_options
10
-    unless options[:consumer_key].present? &&
11
-      options[:consumer_secret].present? &&
12
-      options[:oauth_token].present? &&
13
-      options[:oauth_token_secret].present?
10
+    unless options['consumer_key'].present? &&
11
+      options['consumer_secret'].present? &&
12
+      options['oauth_token'].present? &&
13
+      options['oauth_token_secret'].present?
14 14
       errors.add(:base, "consumer_key, consumer_secret, oauth_token and oauth_token_secret are required to authenticate with the Twitter API")
15 15
     end
16 16
   end
17 17
 
18 18
   def configure_twitter
19 19
     Twitter.configure do |config|
20
-      config.consumer_key = options[:consumer_key]
21
-      config.consumer_secret = options[:consumer_secret]
22
-      config.oauth_token = options[:oauth_token] || options[:access_key]
23
-      config.oauth_token_secret = options[:oauth_token_secret] || options[:access_secret]
20
+      config.consumer_key = options['consumer_key']
21
+      config.consumer_secret = options['consumer_secret']
22
+      config.oauth_token = options['oauth_token'] || options['access_key']
23
+      config.oauth_token_secret = options['oauth_token_secret'] || options['access_secret']
24 24
     end
25 25
   end
26 26
 

+ 6 - 6
app/concerns/weibo_concern.rb

@@ -6,19 +6,19 @@ module WeiboConcern
6 6
   end
7 7
 
8 8
   def validate_weibo_options
9
-    unless options[:app_key].present? &&
10
-        options[:app_secret].present? &&
11
-        options[:access_token].present?
9
+    unless options['app_key'].present? &&
10
+        options['app_secret'].present? &&
11
+        options['access_token'].present?
12 12
         errors.add(:base, "app_key, app_secret and access_token are required")
13 13
     end
14 14
   end
15 15
 
16 16
   def weibo_client
17 17
     unless @weibo_client
18
-      WeiboOAuth2::Config.api_key = options[:app_key] # WEIBO_APP_KEY
19
-      WeiboOAuth2::Config.api_secret = options[:app_secret] # WEIBO_APP_SECRET
18
+      WeiboOAuth2::Config.api_key = options['app_key'] # WEIBO_APP_KEY
19
+      WeiboOAuth2::Config.api_secret = options['app_secret'] # WEIBO_APP_SECRET
20 20
       @weibo_client = WeiboOAuth2::Client.new
21
-      @weibo_client.get_token_from_hash :access_token => options[:access_token]
21
+      @weibo_client.get_token_from_hash :access_token => options['access_token']
22 22
     end
23 23
     @weibo_client
24 24
   end

+ 1 - 1
app/controllers/events_controller.rb

@@ -6,7 +6,7 @@ class EventsController < ApplicationController
6 6
       @agent = current_user.agents.find(params[:agent])
7 7
       @events = @agent.events.page(params[:page])
8 8
     else
9
-      @events = current_user.events.page(params[:page])
9
+      @events = current_user.events.preload(:agent).page(params[:page])
10 10
     end
11 11
 
12 12
     respond_to do |format|

+ 1 - 1
app/controllers/logs_controller.rb

@@ -7,7 +7,7 @@ class LogsController < ApplicationController
7 7
   end
8 8
 
9 9
   def clear
10
-    @agent.logs.delete_all
10
+    @agent.delete_logs!
11 11
     index
12 12
   end
13 13
 

+ 17 - 12
app/models/agent.rb

@@ -1,14 +1,13 @@
1
-require 'serialize_and_symbolize'
1
+require 'json_serialized_field'
2 2
 require 'assignable_types'
3 3
 require 'markdown_class_attributes'
4 4
 require 'utils'
5 5
 
6 6
 class Agent < ActiveRecord::Base
7
-  include SerializeAndSymbolize
8 7
   include AssignableTypes
9 8
   include MarkdownClassAttributes
9
+  include JSONSerializedField
10 10
 
11
-  serialize_and_symbolize :options, :memory
12 11
   markdown_class_attributes :description, :event_description
13 12
 
14 13
   load_types_in "Agents"
@@ -18,9 +17,12 @@ class Agent < ActiveRecord::Base
18 17
 
19 18
   attr_accessible :options, :memory, :name, :type, :schedule, :source_ids
20 19
 
20
+  json_serialize :options, :memory
21
+
21 22
   validates_presence_of :name, :user
22 23
   validate :sources_are_owned
23 24
   validate :validate_schedule
25
+  validate :validate_options
24 26
 
25 27
   after_initialize :set_default_schedule
26 28
   before_validation :set_default_schedule
@@ -32,7 +34,6 @@ class Agent < ActiveRecord::Base
32 34
   has_many :events, :dependent => :delete_all, :inverse_of => :agent, :order => "events.id desc"
33 35
   has_one  :most_recent_event, :inverse_of => :agent, :class_name => "Event", :order => "events.id desc"
34 36
   has_many :logs, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog", :order => "agent_logs.id desc"
35
-  has_one  :most_recent_log, :inverse_of => :agent, :class_name => "AgentLog", :order => "agent_logs.id desc"
36 37
   has_many :received_events, :through => :sources, :class_name => "Event", :source => :events, :order => "events.id desc"
37 38
   has_many :links_as_source, :dependent => :delete_all, :foreign_key => "source_id", :class_name => "Link", :inverse_of => :source
38 39
   has_many :links_as_receiver, :dependent => :delete_all, :foreign_key => "receiver_id", :class_name => "Link", :inverse_of => :receiver
@@ -74,13 +75,16 @@ class Agent < ActiveRecord::Base
74 75
     raise "Implement me in your subclass"
75 76
   end
76 77
 
77
-  def event_created_within(days)
78
-    event = most_recent_event
79
-    event && event.created_at > days.to_i.days.ago && event.payload.present? && event
78
+  def validate_options
79
+    # Implement me in your subclass to test for valid options.
80
+  end
81
+
82
+  def event_created_within?(days)
83
+    last_event_at && last_event_at > days.to_i.days.ago
80 84
   end
81 85
 
82 86
   def recent_error_logs?
83
-    most_recent_log.try(:level) == 4
87
+    last_event_at && last_error_log_at && last_error_log_at > (last_event_at - 2.minutes)
84 88
   end
85 89
 
86 90
   def sources_are_owned
@@ -120,10 +124,6 @@ class Agent < ActiveRecord::Base
120 124
     self.schedule = nil if cannot_be_scheduled?
121 125
   end
122 126
 
123
-  def last_event_at
124
-    @memoized_last_event_at ||= most_recent_event.try(:created_at)
125
-  end
126
-
127 127
   def default_schedule
128 128
     self.class.default_schedule
129 129
   end
@@ -158,6 +158,11 @@ class Agent < ActiveRecord::Base
158 158
     end
159 159
   end
160 160
 
161
+  def delete_logs!
162
+    logs.delete_all
163
+    update_column :last_error_log_at, nil
164
+  end
165
+
161 166
   def log(message, options = {})
162 167
     puts "Agent##{id}: #{message}" unless Rails.env.test?
163 168
     AgentLog.log_for_agent(self, message, options)

+ 3 - 0
app/models/agent_log.rb

@@ -14,6 +14,9 @@ class AgentLog < ActiveRecord::Base
14 14
       oldest_id_to_keep = agent.logs.limit(1).offset(log_length - 1).pluck("agent_logs.id")
15 15
       agent.logs.where("agent_logs.id < ?", oldest_id_to_keep).delete_all
16 16
     end
17
+
18
+    agent.update_column :last_error_log_at, Time.now if log.level >= 4
19
+
17 20
     log
18 21
   end
19 22
 

+ 13 - 13
app/models/agents/adioso_agent.rb

@@ -29,22 +29,22 @@ module Agents
29 29
 
30 30
     def default_options
31 31
       {
32
-        :start_date => Date.today.httpdate[0..15],
33
-        :end_date   => Date.today.plus_with_duration(100).httpdate[0..15],
34
-        :from       => "New York",
35
-        :to         => "Chicago",
36
-        :username   => "xx",
37
-        :password   => "xx",
38
-				:expected_update_period_in_days => "1"
32
+        'start_date' => Date.today.httpdate[0..15],
33
+        'end_date'   => Date.today.plus_with_duration(100).httpdate[0..15],
34
+        'from'       => "New York",
35
+        'to'         => "Chicago",
36
+        'username'   => "xx",
37
+        'password'   => "xx",
38
+				'expected_update_period_in_days' => "1"
39 39
       }
40 40
     end
41 41
 
42 42
     def working?
43
-      event_created_within(options[:expected_update_period_in_days]) && !recent_error_logs?
43
+      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
44 44
     end
45 45
 
46 46
     def validate_options
47
-			unless %w[start_date end_date from to username password expected_update_period_in_days].all? { |field| options[field.to_sym].present? }
47
+			unless %w[start_date end_date from to username password expected_update_period_in_days].all? { |field| options[field].present? }
48 48
 				errors.add(:base, "All fields are required")
49 49
 			end
50 50
 		end
@@ -54,9 +54,9 @@ module Agents
54 54
     end
55 55
 
56 56
     def check
57
-      auth_options = {:basic_auth => {:username =>options[:username], :password=>options[:password]}}
58
-      parse_response = HTTParty.get "http://api.adioso.com/v2/search/parse?q=#{URI.encode(options[:from])}+to+#{URI.encode(options[:to])}", auth_options
59
-      fare_request = parse_response["search_url"].gsub /(end=)(\d*)([^\d]*)(\d*)/, "\\1#{date_to_unix_epoch(options[:end_date])}\\3#{date_to_unix_epoch(options[:start_date])}"
57
+      auth_options = {:basic_auth => {:username =>options[:username], :password=>options['password']}}
58
+      parse_response = HTTParty.get "http://api.adioso.com/v2/search/parse?q=#{URI.encode(options['from'])}+to+#{URI.encode(options['to'])}", auth_options
59
+      fare_request = parse_response["search_url"].gsub /(end=)(\d*)([^\d]*)(\d*)/, "\\1#{date_to_unix_epoch(options['end_date'])}\\3#{date_to_unix_epoch(options['start_date'])}"
60 60
       fare = HTTParty.get fare_request, auth_options
61 61
 
62 62
 			if fare["warnings"]
@@ -64,7 +64,7 @@ module Agents
64 64
 			else
65 65
 				event = fare["results"].min {|a,b| a["cost"] <=> b["cost"]}
66 66
 				event["date"]  = Time.at(event["date"]).to_date.httpdate[0..15]
67
-				event["route"] = "#{options[:from]} to #{options[:to]}" 
67
+				event["route"] = "#{options['from']} to #{options['to']}"
68 68
 				create_event :payload => event
69 69
 			end
70 70
     end

+ 14 - 14
app/models/agents/digest_email_agent.rb

@@ -9,7 +9,7 @@ module Agents
9 9
     description <<-MD
10 10
       The DigestEmailAgent collects any Events sent to it and sends them all via email when run.
11 11
       The email will be sent to your account's address and will have a `subject` and an optional `headline` before
12
-      listing the Events.  If the Events' payloads contain a `:message`, that will be highlighted, otherwise everything in
12
+      listing the Events.  If the Events' payloads contain a `message`, that will be highlighted, otherwise everything in
13 13
       their payloads will be shown.
14 14
 
15 15
       Set `expected_receive_period_in_days` to the maximum amount of time that you'd expect to pass between Events being received by this Agent.
@@ -17,29 +17,29 @@ module Agents
17 17
 
18 18
     def default_options
19 19
       {
20
-          :subject => "You have some notifications!",
21
-          :headline => "Your notifications:",
22
-          :expected_receive_period_in_days => "2"
20
+          'subject' => "You have some notifications!",
21
+          'headline' => "Your notifications:",
22
+          'expected_receive_period_in_days' => "2"
23 23
       }
24 24
     end
25 25
 
26 26
     def receive(incoming_events)
27 27
       incoming_events.each do |event|
28
-        self.memory[:queue] ||= []
29
-        self.memory[:queue] << event.payload
30
-        self.memory[:events] ||= []
31
-        self.memory[:events] << event.id
28
+        self.memory['queue'] ||= []
29
+        self.memory['queue'] << event.payload
30
+        self.memory['events'] ||= []
31
+        self.memory['events'] << event.id
32 32
       end
33 33
     end
34 34
 
35 35
     def check
36
-      if self.memory[:queue] && self.memory[:queue].length > 0
37
-        ids = self.memory[:events].join(",")
38
-        groups = self.memory[:queue].map { |payload| present(payload) }
36
+      if self.memory['queue'] && self.memory['queue'].length > 0
37
+        ids = self.memory['events'].join(",")
38
+        groups = self.memory['queue'].map { |payload| present(payload) }
39 39
         log "Sending digest mail to #{user.email} with events [#{ids}]"
40
-        SystemMailer.delay.send_message(:to => user.email, :subject => options[:subject], :headline => options[:headline], :groups => groups)
41
-        self.memory[:queue] = []
42
-        self.memory[:events] = []
40
+        SystemMailer.delay.send_message(:to => user.email, :subject => options['subject'], :headline => options['headline'], :groups => groups)
41
+        self.memory['queue'] = []
42
+        self.memory['events'] = []
43 43
       end
44 44
     end
45 45
   end

+ 4 - 4
app/models/agents/email_agent.rb

@@ -16,16 +16,16 @@ module Agents
16 16
 
17 17
     def default_options
18 18
       {
19
-          :subject => "You have a notification!",
20
-          :headline => "Your notification:",
21
-          :expected_receive_period_in_days => "2"
19
+          'subject' => "You have a notification!",
20
+          'headline' => "Your notification:",
21
+          'expected_receive_period_in_days' => "2"
22 22
       }
23 23
     end
24 24
 
25 25
     def receive(incoming_events)
26 26
       incoming_events.each do |event|
27 27
         log "Sending digest mail to #{user.email} with event #{event.id}"
28
-        SystemMailer.delay.send_message(:to => user.email, :subject => options[:subject], :headline => options[:headline], :groups => [present(event.payload)])
28
+        SystemMailer.delay.send_message(:to => user.email, :subject => options['subject'], :headline => options['headline'], :groups => [present(event.payload)])
29 29
       end
30 30
     end
31 31
   end

+ 22 - 22
app/models/agents/event_formatting_agent.rb

@@ -8,20 +8,20 @@ module Agents
8 8
       For example, here is a possible Event:
9 9
 
10 10
           {
11
-            :high => {
12
-              :celsius => "18",
13
-              :fahreinheit => "64"
11
+            "high": {
12
+              "celsius": "18",
13
+              "fahreinheit": "64"
14 14
             },
15
-            :conditions => "Rain showers",
16
-            :data   => "This is some data"
15
+            "conditions": "Rain showers",
16
+            "data": "This is some data"
17 17
           }
18 18
 
19 19
       You may want to send this event to another Agent, for example a Twilio Agent, which expects a `message` key.
20 20
       You can use an Event Formatting Agent's `instructions` setting to do this in the following way:
21 21
 
22
-          instructions: {
23
-            message: "Today's conditions look like <$.conditions> with a high temperature of <$.high.celsius> degrees Celsius.",
24
-            subject: "$.data"
22
+          "instructions": {
23
+            "message": "Today's conditions look like <$.conditions> with a high temperature of <$.high.celsius> degrees Celsius.",
24
+            "subject": "$.data"
25 25
           }
26 26
 
27 27
       JSONPaths must be between < and > . Make sure that you don't use these symbols anywhere else.
@@ -29,8 +29,8 @@ module Agents
29 29
       Events generated by this possible Event Formatting Agent will look like:
30 30
 
31 31
           {
32
-            :message => "Today's conditions look like Rain showers with a high temperature of 18 degrees Celsius.",
33
-            :subject => "This is some data"
32
+            "message": "Today's conditions look like Rain showers with a high temperature of 18 degrees Celsius.",
33
+            "subject": "This is some data"
34 34
           }
35 35
 
36 36
       If you want to retain original contents of events and only add new keys, then set `mode` to `merge`, otherwise set it to `clean`.
@@ -40,25 +40,25 @@ module Agents
40 40
       To CGI escape output (for example when creating a link), prefix with `escape`, like so:
41 41
 
42 42
           {
43
-            :message => "A peak was on Twitter in <$.group_by>.  Search: https://twitter.com/search?q=<escape $.group_by>"
43
+            "message": "A peak was on Twitter in <$.group_by>.  Search: https://twitter.com/search?q=<escape $.group_by>"
44 44
           }
45 45
     MD
46 46
 
47 47
     event_description "User defined"
48 48
 
49 49
     def validate_options
50
-      errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options[:instructions].present? and options[:mode].present? and options[:skip_agent].present? and options[:skip_created_at].present?
50
+      errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options['instructions'].present? and options['mode'].present? and options['skip_agent'].present? and options['skip_created_at'].present?
51 51
     end
52 52
 
53 53
     def default_options
54 54
       {
55
-        :instructions => {
56
-          :message =>  "You received a text <$.text> from <$.fields.from>",
57
-          :some_other_field => "Looks like the weather is going to be <$.fields.weather>"
55
+        'instructions' => {
56
+          'message' =>  "You received a text <$.text> from <$.fields.from>",
57
+          'some_other_field' => "Looks like the weather is going to be <$.fields.weather>"
58 58
         },
59
-        :mode => "clean",
60
-        :skip_agent => "false",
61
-        :skip_created_at => "false"
59
+        'mode' => "clean",
60
+        'skip_agent' => "false",
61
+        'skip_created_at' => "false"
62 62
       }
63 63
     end
64 64
 
@@ -68,10 +68,10 @@ module Agents
68 68
 
69 69
     def receive(incoming_events)
70 70
       incoming_events.each do |event|
71
-        formatted_event = options[:mode].to_s == "merge" ? event.payload : {}
72
-        options[:instructions].each_pair {|key, value| formatted_event[key] = Utils.interpolate_jsonpaths(value, event.payload) }
73
-        formatted_event[:agent] = Agent.find(event.agent_id).type.slice!(8..-1) unless options[:skip_agent].to_s == "true"
74
-        formatted_event[:created_at] = event.created_at unless options[:skip_created_at].to_s == "true"
71
+        formatted_event = options['mode'].to_s == "merge" ? event.payload : {}
72
+        options['instructions'].each_pair {|key, value| formatted_event[key] = Utils.interpolate_jsonpaths(value, event.payload) }
73
+        formatted_event['agent'] = Agent.find(event.agent_id).type.slice!(8..-1) unless options['skip_agent'].to_s == "true"
74
+        formatted_event['created_at'] = event.created_at unless options['skip_created_at'].to_s == "true"
75 75
         create_event :payload => formatted_event
76 76
       end
77 77
     end

+ 120 - 121
app/models/agents/human_task_agent.rb

@@ -128,73 +128,73 @@ module Agents
128 128
     MD
129 129
 
130 130
     def validate_options
131
-      options[:hit] ||= {}
132
-      options[:hit][:questions] ||= []
133
-
134
-      errors.add(:base, "'trigger_on' must be one of 'schedule' or 'event'") unless %w[schedule event].include?(options[:trigger_on])
135
-      errors.add(:base, "'hit.assignments' should specify the number of HIT assignments to create") unless options[:hit][:assignments].present? && options[:hit][:assignments].to_i > 0
136
-      errors.add(:base, "'hit.title' must be provided") unless options[:hit][:title].present?
137
-      errors.add(:base, "'hit.description' must be provided") unless options[:hit][:description].present?
138
-      errors.add(:base, "'hit.questions' must be provided") unless options[:hit][:questions].present? && options[:hit][:questions].length > 0
139
-
140
-      if options[:trigger_on] == "event"
141
-        errors.add(:base, "'expected_receive_period_in_days' is required when 'trigger_on' is set to 'event'") unless options[:expected_receive_period_in_days].present?
142
-      elsif options[:trigger_on] == "schedule"
143
-        errors.add(:base, "'submission_period' must be set to a positive number of hours when 'trigger_on' is set to 'schedule'") unless options[:submission_period].present? && options[:submission_period].to_i > 0
131
+      options['hit'] ||= {}
132
+      options['hit']['questions'] ||= []
133
+
134
+      errors.add(:base, "'trigger_on' must be one of 'schedule' or 'event'") unless %w[schedule event].include?(options['trigger_on'])
135
+      errors.add(:base, "'hit.assignments' should specify the number of HIT assignments to create") unless options['hit']['assignments'].present? && options['hit']['assignments'].to_i > 0
136
+      errors.add(:base, "'hit.title' must be provided") unless options['hit']['title'].present?
137
+      errors.add(:base, "'hit.description' must be provided") unless options['hit']['description'].present?
138
+      errors.add(:base, "'hit.questions' must be provided") unless options['hit']['questions'].present? && options['hit']['questions'].length > 0
139
+
140
+      if options['trigger_on'] == "event"
141
+        errors.add(:base, "'expected_receive_period_in_days' is required when 'trigger_on' is set to 'event'") unless options['expected_receive_period_in_days'].present?
142
+      elsif options['trigger_on'] == "schedule"
143
+        errors.add(:base, "'submission_period' must be set to a positive number of hours when 'trigger_on' is set to 'schedule'") unless options['submission_period'].present? && options['submission_period'].to_i > 0
144 144
       end
145 145
 
146
-      if options[:hit][:questions].any? { |question| [:key, :name, :required, :type, :question].any? {|k| !question[k].present? } }
146
+      if options['hit']['questions'].any? { |question| %w[key name required type question].any? {|k| !question[k].present? } }
147 147
         errors.add(:base, "all questions must set 'key', 'name', 'required', 'type', and 'question'")
148 148
       end
149 149
 
150
-      if options[:hit][:questions].any? { |question| question[:type] == "selection" && (!question[:selections].present? || question[:selections].length == 0 || !question[:selections].all? {|s| s[:key].present? } || !question[:selections].all? { |s| s[:text].present? })}
150
+      if options['hit']['questions'].any? { |question| question['type'] == "selection" && (!question['selections'].present? || question['selections'].length == 0 || !question['selections'].all? {|s| s['key'].present? } || !question['selections'].all? { |s| s['text'].present? })}
151 151
         errors.add(:base, "all questions of type 'selection' must have a selections array with selections that set 'key' and 'name'")
152 152
       end
153 153
 
154
-      if take_majority? && options[:hit][:questions].any? { |question| question[:type] != "selection" }
154
+      if take_majority? && options['hit']['questions'].any? { |question| question['type'] != "selection" }
155 155
         errors.add(:base, "all questions must be of type 'selection' to use the 'take_majority' option")
156 156
       end
157 157
 
158 158
       if create_poll?
159
-        errors.add(:base, "poll_options is required when combination_mode is set to 'poll' and must have the keys 'title', 'instructions', 'row_template', and 'assignments'") unless options[:poll_options].is_a?(Hash) && options[:poll_options][:title].present? &&  options[:poll_options][:instructions].present? && options[:poll_options][:row_template].present? && options[:poll_options][:assignments].to_i > 0
159
+        errors.add(:base, "poll_options is required when combination_mode is set to 'poll' and must have the keys 'title', 'instructions', 'row_template', and 'assignments'") unless options['poll_options'].is_a?(Hash) && options['poll_options']['title'].present? && options['poll_options']['instructions'].present? && options['poll_options']['row_template'].present? && options['poll_options']['assignments'].to_i > 0
160 160
       end
161 161
     end
162 162
 
163 163
     def default_options
164 164
       {
165
-        :expected_receive_period_in_days => 2,
166
-        :trigger_on => "event",
167
-        :hit =>
165
+        'expected_receive_period_in_days' => 2,
166
+        'trigger_on' => "event",
167
+        'hit' =>
168 168
           {
169
-            :assignments => 1,
170
-            :title => "Sentiment evaluation",
171
-            :description => "Please rate the sentiment of this message: '<$.message>'",
172
-            :reward => 0.05,
173
-            :lifetime_in_seconds => 24 * 60 * 60,
174
-            :questions =>
169
+            'assignments' => 1,
170
+            'title' => "Sentiment evaluation",
171
+            'description' => "Please rate the sentiment of this message: '<$.message>'",
172
+            'reward' => 0.05,
173
+            'lifetime_in_seconds' => 24 * 60 * 60,
174
+            'questions' =>
175 175
               [
176 176
                 {
177
-                  :type => "selection",
178
-                  :key => "sentiment",
179
-                  :name => "Sentiment",
180
-                  :required => "true",
181
-                  :question => "Please select the best sentiment value:",
182
-                  :selections =>
177
+                  'type' => "selection",
178
+                  'key' => "sentiment",
179
+                  'name' => "Sentiment",
180
+                  'required' => "true",
181
+                  'question' => "Please select the best sentiment value:",
182
+                  'selections' =>
183 183
                     [
184
-                      { :key => "happy", :text => "Happy" },
185
-                      { :key => "sad", :text => "Sad" },
186
-                      { :key => "neutral", :text => "Neutral" }
184
+                      { 'key' => "happy", 'text' => "Happy" },
185
+                      { 'key' => "sad", 'text' => "Sad" },
186
+                      { 'key' => "neutral", 'text' => "Neutral" }
187 187
                     ]
188 188
                 },
189 189
                 {
190
-                  :type => "free_text",
191
-                  :key => "feedback",
192
-                  :name => "Have any feedback for us?",
193
-                  :required => "false",
194
-                  :question => "Feedback",
195
-                  :default => "Type here...",
196
-                  :min_length => "2",
197
-                  :max_length => "2000"
190
+                  'type' => "free_text",
191
+                  'key' => "feedback",
192
+                  'name' => "Have any feedback for us?",
193
+                  'required' => "false",
194
+                  'question' => "Feedback",
195
+                  'default' => "Type here...",
196
+                  'min_length' => "2",
197
+                  'max_length' => "2000"
198 198
                 }
199 199
               ]
200 200
           }
@@ -202,20 +202,20 @@ module Agents
202 202
     end
203 203
 
204 204
     def working?
205
-      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
205
+      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
206 206
     end
207 207
 
208 208
     def check
209 209
       review_hits
210 210
 
211
-      if options[:trigger_on] == "schedule" && (memory[:last_schedule] || 0) <= Time.now.to_i - options[:submission_period].to_i * 60 * 60
212
-        memory[:last_schedule] = Time.now.to_i
211
+      if options['trigger_on'] == "schedule" && (memory['last_schedule'] || 0) <= Time.now.to_i - options['submission_period'].to_i * 60 * 60
212
+        memory['last_schedule'] = Time.now.to_i
213 213
         create_basic_hit
214 214
       end
215 215
     end
216 216
 
217 217
     def receive(incoming_events)
218
-      if options[:trigger_on] == "event"
218
+      if options['trigger_on'] == "event"
219 219
         incoming_events.each do |event|
220 220
           create_basic_hit event
221 221
         end
@@ -225,33 +225,32 @@ module Agents
225 225
     protected
226 226
 
227 227
     def take_majority?
228
-      options[:combination_mode] == "take_majority" || options[:take_majority] == "true"
228
+      options['combination_mode'] == "take_majority" || options['take_majority'] == "true"
229 229
     end
230 230
 
231 231
     def create_poll?
232
-      options[:combination_mode] == "poll"
232
+      options['combination_mode'] == "poll"
233 233
     end
234 234
 
235 235
     def event_for_hit(hit_id)
236
-      if memory[:hits][hit_id.to_sym].is_a?(Hash)
237
-        Event.find_by_id(memory[:hits][hit_id.to_sym][:event_id])
236
+      if memory['hits'][hit_id].is_a?(Hash)
237
+        Event.find_by_id(memory['hits'][hit_id]['event_id'])
238 238
       else
239 239
         nil
240 240
       end
241 241
     end
242 242
 
243 243
     def hit_type(hit_id)
244
-      # Fix this: the Ruby process will slowly run out of RAM by symbolizing these unique keys.
245
-      if memory[:hits][hit_id.to_sym].is_a?(Hash) && memory[:hits][hit_id.to_sym][:type]
246
-        memory[:hits][hit_id.to_sym][:type].to_sym
244
+      if memory['hits'][hit_id].is_a?(Hash) && memory['hits'][hit_id]['type']
245
+        memory['hits'][hit_id]['type']
247 246
       else
248
-        :user
247
+        'user'
249 248
       end
250 249
     end
251 250
 
252 251
     def review_hits
253 252
       reviewable_hit_ids = RTurk::GetReviewableHITs.create.hit_ids
254
-      my_reviewed_hit_ids = reviewable_hit_ids & (memory[:hits] || {}).keys.map(&:to_s)
253
+      my_reviewed_hit_ids = reviewable_hit_ids & (memory['hits'] || {}).keys
255 254
       if reviewable_hit_ids.length > 0
256 255
         log "MTurk reports #{reviewable_hit_ids.length} HITs, of which I own [#{my_reviewed_hit_ids.to_sentence}]"
257 256
       end
@@ -264,7 +263,7 @@ module Agents
264 263
         if assignments.length == hit.max_assignments && assignments.all? { |assignment| assignment.status == "Submitted" }
265 264
           inbound_event = event_for_hit(hit_id)
266 265
 
267
-          if hit_type(hit_id) == :poll
266
+          if hit_type(hit_id) == 'poll'
268 267
             # handle completed polls
269 268
 
270 269
             log "Handling a poll: #{hit_id}"
@@ -280,35 +279,35 @@ module Agents
280 279
             top_answer = scores.to_a.sort {|b, a| a.last <=> b.last }.first.first
281 280
 
282 281
             payload = {
283
-              :answers => memory[:hits][hit_id.to_sym][:answers],
284
-              :poll => assignments.map(&:answers),
285
-              :best_answer => memory[:hits][hit_id.to_sym][:answers][top_answer.to_i - 1]
282
+              'answers' => memory['hits'][hit_id]['answers'],
283
+              'poll' => assignments.map(&:answers),
284
+              'best_answer' => memory['hits'][hit_id]['answers'][top_answer.to_i - 1]
286 285
             }
287 286
 
288 287
             event = create_event :payload => payload
289 288
             log "Event emitted with answer(s) for poll", :outbound_event => event, :inbound_event => inbound_event
290 289
           else
291 290
             # handle normal completed HITs
292
-            payload = { :answers => assignments.map(&:answers) }
291
+            payload = { 'answers' => assignments.map(&:answers) }
293 292
 
294 293
             if take_majority?
295 294
               counts = {}
296
-              options[:hit][:questions].each do |question|
297
-                question_counts = question[:selections].inject({}) { |memo, selection| memo[selection[:key]] = 0; memo }
295
+              options['hit']['questions'].each do |question|
296
+                question_counts = question['selections'].inject({}) { |memo, selection| memo[selection['key']] = 0; memo }
298 297
                 assignments.each do |assignment|
299 298
                   answers = ActiveSupport::HashWithIndifferentAccess.new(assignment.answers)
300
-                  answer = answers[question[:key]]
299
+                  answer = answers[question['key']]
301 300
                   question_counts[answer] += 1
302 301
                 end
303
-                counts[question[:key]] = question_counts
302
+                counts[question['key']] = question_counts
304 303
               end
305
-              payload[:counts] = counts
304
+              payload['counts'] = counts
306 305
 
307 306
               majority_answer = counts.inject({}) do |memo, (key, question_counts)|
308 307
                 memo[key] = question_counts.to_a.sort {|a, b| a.last <=> b.last }.last.first
309 308
                 memo
310 309
               end
311
-              payload[:majority_answer] = majority_answer
310
+              payload['majority_answer'] = majority_answer
312 311
 
313 312
               if all_questions_are_numeric?
314 313
                 average_answer = counts.inject({}) do |memo, (key, question_counts)|
@@ -320,35 +319,35 @@ module Agents
320 319
                   memo[key] = sum / divisor.to_f
321 320
                   memo
322 321
                 end
323
-                payload[:average_answer] = average_answer
322
+                payload['average_answer'] = average_answer
324 323
               end
325 324
             end
326 325
 
327 326
             if create_poll?
328 327
               questions = []
329
-              selections = 5.times.map { |i| { :key => i+1, :text => i+1 } }.reverse
328
+              selections = 5.times.map { |i| { 'key' => i+1, 'text' => i+1 } }.reverse
330 329
               assignments.length.times do |index|
331 330
                 questions << {
332
-                  :type => "selection",
333
-                  :name => "Item #{index + 1}",
334
-                  :key => index,
335
-                  :required => "true",
336
-                  :question => Utils.interpolate_jsonpaths(options[:poll_options][:row_template], assignments[index].answers),
337
-                  :selections => selections
331
+                  'type' => "selection",
332
+                  'name' => "Item #{index + 1}",
333
+                  'key' => index,
334
+                  'required' => "true",
335
+                  'question' => Utils.interpolate_jsonpaths(options['poll_options']['row_template'], assignments[index].answers),
336
+                  'selections' => selections
338 337
                 }
339 338
               end
340 339
 
341
-              poll_hit = create_hit :title => options[:poll_options][:title],
342
-                                    :description => options[:poll_options][:instructions],
343
-                                    :questions => questions,
344
-                                    :assignments => options[:poll_options][:assignments],
345
-                                    :lifetime_in_seconds => options[:poll_options][:lifetime_in_seconds],
346
-                                    :reward => options[:poll_options][:reward],
347
-                                    :payload => inbound_event && inbound_event.payload,
348
-                                    :metadata => { :type => :poll,
349
-                                                   :original_hit => hit_id,
350
-                                                   :answers => assignments.map(&:answers),
351
-                                                   :event_id => inbound_event && inbound_event.id }
340
+              poll_hit = create_hit 'title' => options['poll_options']['title'],
341
+                                    'description' => options['poll_options']['instructions'],
342
+                                    'questions' => questions,
343
+                                    'assignments' => options['poll_options']['assignments'],
344
+                                    'lifetime_in_seconds' => options['poll_options']['lifetime_in_seconds'],
345
+                                    'reward' => options['poll_options']['reward'],
346
+                                    'payload' => inbound_event && inbound_event.payload,
347
+                                    'metadata' => { 'type' => 'poll',
348
+                                                    'original_hit' => hit_id,
349
+                                                    'answers' => assignments.map(&:answers),
350
+                                                    'event_id' => inbound_event && inbound_event.id }
352 351
 
353 352
               log "Poll HIT created with ID #{poll_hit.id} and URL #{poll_hit.url}.  Original HIT: #{hit_id}", :inbound_event => inbound_event
354 353
             else
@@ -360,47 +359,47 @@ module Agents
360 359
           assignments.each(&:approve!)
361 360
           hit.dispose!
362 361
 
363
-          memory[:hits].delete(hit_id.to_sym)
362
+          memory['hits'].delete(hit_id)
364 363
         end
365 364
       end
366 365
     end
367 366
 
368 367
     def all_questions_are_numeric?
369
-      options[:hit][:questions].all? do |question|
370
-        question[:selections].all? do |selection|
371
-          selection[:key] == selection[:key].to_f.to_s || selection[:key] == selection[:key].to_i.to_s
368
+      options['hit']['questions'].all? do |question|
369
+        question['selections'].all? do |selection|
370
+          selection['key'] == selection['key'].to_f.to_s || selection['key'] == selection['key'].to_i.to_s
372 371
         end
373 372
       end
374 373
     end
375 374
 
376 375
     def create_basic_hit(event = nil)
377
-      hit = create_hit :title => options[:hit][:title],
378
-                       :description => options[:hit][:description],
379
-                       :questions => options[:hit][:questions],
380
-                       :assignments => options[:hit][:assignments],
381
-                       :lifetime_in_seconds => options[:hit][:lifetime_in_seconds],
382
-                       :reward => options[:hit][:reward],
383
-                       :payload => event && event.payload,
384
-                       :metadata => { :event_id => event && event.id }
376
+      hit = create_hit 'title' => options['hit']['title'],
377
+                       'description' => options['hit']['description'],
378
+                       'questions' => options['hit']['questions'],
379
+                       'assignments' => options['hit']['assignments'],
380
+                       'lifetime_in_seconds' => options['hit']['lifetime_in_seconds'],
381
+                       'reward' => options['hit']['reward'],
382
+                       'payload' => event && event.payload,
383
+                       'metadata' => { 'event_id' => event && event.id }
385 384
 
386 385
       log "HIT created with ID #{hit.id} and URL #{hit.url}", :inbound_event => event
387 386
     end
388 387
 
389 388
     def create_hit(opts = {})
390
-      payload = opts[:payload] || {}
391
-      title = Utils.interpolate_jsonpaths(opts[:title], payload).strip
392
-      description = Utils.interpolate_jsonpaths(opts[:description], payload).strip
393
-      questions = Utils.recursively_interpolate_jsonpaths(opts[:questions], payload)
389
+      payload = opts['payload'] || {}
390
+      title = Utils.interpolate_jsonpaths(opts['title'], payload).strip
391
+      description = Utils.interpolate_jsonpaths(opts['description'], payload).strip
392
+      questions = Utils.recursively_interpolate_jsonpaths(opts['questions'], payload)
394 393
       hit = RTurk::Hit.create(:title => title) do |hit|
395
-        hit.max_assignments = (opts[:assignments] || 1).to_i
394
+        hit.max_assignments = (opts['assignments'] || 1).to_i
396 395
         hit.description = description
397
-        hit.lifetime = (opts[:lifetime_in_seconds] || 24 * 60 * 60).to_i
396
+        hit.lifetime = (opts['lifetime_in_seconds'] || 24 * 60 * 60).to_i
398 397
         hit.question_form AgentQuestionForm.new(:title => title, :description => description, :questions => questions)
399
-        hit.reward = (opts[:reward] || 0.05).to_f
398
+        hit.reward = (opts['reward'] || 0.05).to_f
400 399
         #hit.qualifications.add :approval_rate, { :gt => 80 }
401 400
       end
402
-      memory[:hits] ||= {}
403
-      memory[:hits][hit.id] = opts[:metadata] || {}
401
+      memory['hits'] ||= {}
402
+      memory['hits'][hit.id] = opts['metadata'] || {}
404 403
       hit
405 404
     end
406 405
 
@@ -422,34 +421,34 @@ module Agents
422 421
         @questions.each.with_index do |question, index|
423 422
           Question do
424 423
             QuestionIdentifier do
425
-              text question[:key] || "question_#{index}"
424
+              text question['key'] || "question_#{index}"
426 425
             end
427 426
             DisplayName do
428
-              text question[:name] || "Question ##{index}"
427
+              text question['name'] || "Question ##{index}"
429 428
             end
430 429
             IsRequired do
431
-              text question[:required] || 'true'
430
+              text question['required'] || 'true'
432 431
             end
433 432
             QuestionContent do
434 433
               Text do
435
-                text question[:question]
434
+                text question['question']
436 435
               end
437 436
             end
438 437
             AnswerSpecification do
439
-              if question[:type] == "selection"
438
+              if question['type'] == "selection"
440 439
 
441 440
                 SelectionAnswer do
442 441
                   StyleSuggestion do
443 442
                     text 'radiobutton'
444 443
                   end
445 444
                   Selections do
446
-                    question[:selections].each do |selection|
445
+                    question['selections'].each do |selection|
447 446
                       Selection do
448 447
                         SelectionIdentifier do
449
-                          text selection[:key]
448
+                          text selection['key']
450 449
                         end
451 450
                         Text do
452
-                          text selection[:text]
451
+                          text selection['text']
453 452
                         end
454 453
                       end
455 454
                     end
@@ -459,18 +458,18 @@ module Agents
459 458
               else
460 459
 
461 460
                 FreeTextAnswer do
462
-                  if question[:min_length].present? || question[:max_length].present?
461
+                  if question['min_length'].present? || question['max_length'].present?
463 462
                     Constraints do
464 463
                       lengths = {}
465
-                      lengths[:minLength] = question[:min_length].to_s if question[:min_length].present?
466
-                      lengths[:maxLength] = question[:max_length].to_s if question[:max_length].present?
464
+                      lengths['minLength'] = question['min_length'].to_s if question['min_length'].present?
465
+                      lengths['maxLength'] = question['max_length'].to_s if question['max_length'].present?
467 466
                       Length lengths
468 467
                     end
469 468
                   end
470 469
 
471
-                  if question[:default].present?
470
+                  if question['default'].present?
472 471
                     DefaultText do
473
-                      text question[:default]
472
+                      text question['default']
474 473
                     end
475 474
                   end
476 475
                 end
@@ -482,4 +481,4 @@ module Agents
482 481
       end
483 482
     end
484 483
   end
485
-end
484
+end

+ 2 - 2
app/models/agents/manual_event_agent.rb

@@ -14,8 +14,8 @@ module Agents
14 14
     end
15 15
 
16 16
     def handle_details_post(params)
17
-      if params[:payload]
18
-        create_event(:payload => params[:payload])
17
+      if params['payload']
18
+        create_event(:payload => params['payload'])
19 19
         { :success => true }
20 20
       else
21 21
         { :success => false, :error => "You must provide a JSON payload" }

+ 27 - 29
app/models/agents/peak_detector_agent.rb

@@ -28,22 +28,22 @@ module Agents
28 28
     MD
29 29
 
30 30
     def validate_options
31
-      unless options[:expected_receive_period_in_days].present? && options[:message].present? && options[:value_path].present?
31
+      unless options['expected_receive_period_in_days'].present? && options['message'].present? && options['value_path'].present?
32 32
         errors.add(:base, "expected_receive_period_in_days, value_path, and message are required")
33 33
       end
34 34
     end
35 35
 
36 36
     def default_options
37 37
       {
38
-          :expected_receive_period_in_days => "2",
39
-          :group_by_path => "filter",
40
-          :value_path => "count",
41
-          :message => "A peak was found"
38
+        'expected_receive_period_in_days' => "2",
39
+        'group_by_path' => "filter",
40
+        'value_path' => "count",
41
+        'message' => "A peak was found"
42 42
       }
43 43
     end
44 44
 
45 45
     def working?
46
-      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
46
+      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
47 47
     end
48 48
 
49 49
     def receive(incoming_events)
@@ -57,25 +57,23 @@ module Agents
57 57
     private
58 58
 
59 59
     def check_for_peak(group, event)
60
-      memory[:peaks] ||= {}
61
-      memory[:peaks][group] ||= []
60
+      memory['peaks'] ||= {}
61
+      memory['peaks'][group] ||= []
62 62
 
63
-      if memory[:data][group].length > 4 && (memory[:peaks][group].empty? || memory[:peaks][group].last < event.created_at.to_i - peak_spacing)
63
+      if memory['data'][group].length > 4 && (memory['peaks'][group].empty? || memory['peaks'][group].last < event.created_at.to_i - peak_spacing)
64 64
         average_value, standard_deviation = stats_for(group, :skip_last => 1)
65
-        newest_value, newest_time = memory[:data][group][-1].map(&:to_f)
66
-
67
-        #p [newest_value, average_value, average_value + std_multiple * standard_deviation, standard_deviation]
65
+        newest_value, newest_time = memory['data'][group][-1].map(&:to_f)
68 66
 
69 67
         if newest_value > average_value + std_multiple * standard_deviation
70
-          memory[:peaks][group] << newest_time
71
-          memory[:peaks][group].reject! { |p| p <= newest_time - window_duration }
72
-          create_event :payload => {:message => options[:message], :peak => newest_value, :peak_time => newest_time, :grouped_by => group.to_s}
68
+          memory['peaks'][group] << newest_time
69
+          memory['peaks'][group].reject! { |p| p <= newest_time - window_duration }
70
+          create_event :payload => { 'message' => options['message'], 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s }
73 71
         end
74 72
       end
75 73
     end
76 74
 
77 75
     def stats_for(group, options = {})
78
-      data = memory[:data][group].map { |d| d.first.to_f }
76
+      data = memory['data'][group].map { |d| d.first.to_f }
79 77
       data = data[0...(data.length - (options[:skip_last] || 0))]
80 78
       length = data.length.to_f
81 79
       mean = 0
@@ -94,39 +92,39 @@ module Agents
94 92
     end
95 93
 
96 94
     def window_duration
97
-      if options[:window_duration].present? # The older option
98
-        options[:window_duration].to_i
95
+      if options['window_duration'].present? # The older option
96
+        options['window_duration'].to_i
99 97
       else
100
-        (options[:window_duration_in_days] || 14).to_f.days
98
+        (options['window_duration_in_days'] || 14).to_f.days
101 99
       end
102 100
     end
103 101
 
104 102
     def std_multiple
105
-      (options[:std_multiple] || 3).to_f
103
+      (options['std_multiple'] || 3).to_f
106 104
     end
107 105
 
108 106
     def peak_spacing
109
-      if options[:peak_spacing].present? # The older option
110
-        options[:peak_spacing].to_i
107
+      if options['peak_spacing'].present? # The older option
108
+        options['peak_spacing'].to_i
111 109
       else
112
-        (options[:min_peak_spacing_in_days] || 2).to_f.days
110
+        (options['min_peak_spacing_in_days'] || 2).to_f.days
113 111
       end
114 112
     end
115 113
 
116 114
     def group_for(event)
117
-      ((options[:group_by_path].present? && Utils.value_at(event.payload, options[:group_by_path])) || 'no_group').to_sym
115
+      ((options['group_by_path'].present? && Utils.value_at(event.payload, options['group_by_path'])) || 'no_group')
118 116
     end
119 117
 
120 118
     def remember(group, event)
121
-      memory[:data] ||= {}
122
-      memory[:data][group] ||= []
123
-      memory[:data][group] << [Utils.value_at(event.payload, options[:value_path]), event.created_at.to_i]
119
+      memory['data'] ||= {}
120
+      memory['data'][group] ||= []
121
+      memory['data'][group] << [ Utils.value_at(event.payload, options['value_path']), event.created_at.to_i ]
124 122
       cleanup group
125 123
     end
126 124
 
127 125
     def cleanup(group)
128
-      newest_time = memory[:data][group].last.last
129
-      memory[:data][group].reject! { |value, time| time <= newest_time - window_duration }
126
+      newest_time = memory['data'][group].last.last
127
+      memory['data'][group].reject! { |value, time| time <= newest_time - window_duration }
130 128
     end
131 129
   end
132 130
 end

+ 4 - 4
app/models/agents/post_agent.rb

@@ -11,17 +11,17 @@ module Agents
11 11
 
12 12
     def default_options
13 13
       {
14
-        :post_url => "http://www.example.com",
15
-        :expected_receive_period_in_days => 1
14
+        'post_url' => "http://www.example.com",
15
+        'expected_receive_period_in_days' => 1
16 16
       }
17 17
     end
18 18
 
19 19
     def working?
20
-      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
20
+      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
21 21
     end
22 22
 
23 23
     def validate_options
24
-      unless options[:post_url].present? && options[:expected_receive_period_in_days].present?
24
+      unless options['post_url'].present? && options['expected_receive_period_in_days'].present?
25 25
         errors.add(:base, "post_url and expected_receive_period_in_days are required fields")
26 26
       end
27 27
     end

+ 10 - 10
app/models/agents/sentiment_agent.rb

@@ -28,31 +28,31 @@ module Agents
28 28
 
29 29
     def default_options
30 30
       {
31
-        :content => "$.message.text[*]",
32
-        :expected_receive_period_in_days => 1
31
+        'content' => "$.message.text[*]",
32
+        'expected_receive_period_in_days' => 1
33 33
       }
34 34
     end
35 35
 
36 36
     def working?
37
-      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
37
+      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
38 38
     end
39 39
 
40 40
     def receive(incoming_events)
41 41
       anew = self.class.sentiment_hash
42 42
       incoming_events.each do |event|
43
-        Utils.values_at(event.payload, options[:content]).each do |content|
43
+        Utils.values_at(event.payload, options['content']).each do |content|
44 44
           sent_values = sentiment_values anew, content
45
-          create_event :payload => { :content => content,
46
-                                     :valence => sent_values[0],
47
-                                     :arousal => sent_values[1],
48
-                                     :dominance => sent_values[2],
49
-                                     :original_event => event.payload }
45
+          create_event :payload => { 'content' => content,
46
+                                     'valence' => sent_values[0],
47
+                                     'arousal' => sent_values[1],
48
+                                     'dominance' => sent_values[2],
49
+                                     'original_event' => event.payload }
50 50
         end
51 51
       end
52 52
     end
53 53
 
54 54
     def validate_options
55
-      errors.add(:base, "content and expected_receive_period_in_days must be present") unless options[:content].present? && options[:expected_receive_period_in_days].present?
55
+      errors.add(:base, "content and expected_receive_period_in_days must be present") unless options['content'].present? && options['expected_receive_period_in_days'].present?
56 56
     end
57 57
 
58 58
     def self.sentiment_hash

+ 15 - 15
app/models/agents/translation_agent.rb

@@ -17,26 +17,26 @@ module Agents
17 17
 
18 18
     def default_options
19 19
       {
20
-        :client_id => "xxxxxx",
21
-        :client_secret => "xxxxxx",
22
-        :to => "fi",
23
-        :expected_receive_period_in_days => 1,
24
-        :content => {
25
-          :text => "$.message.text",
26
-          :content => "$.xyz"
20
+        'client_id' => "xxxxxx",
21
+        'client_secret' => "xxxxxx",
22
+        'to' => "fi",
23
+        'expected_receive_period_in_days' => 1,
24
+        'content' => {
25
+          'text' => "$.message.text",
26
+          'content' => "$.xyz"
27 27
         }
28 28
       }
29 29
     end
30 30
 
31 31
     def working?
32
-      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
32
+      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
33 33
     end
34 34
 
35 35
     def translate(text, to, access_token)
36 36
       translate_uri = URI 'http://api.microsofttranslator.com/v2/Ajax.svc/Translate'
37 37
       params = {
38
-          :text => text,
39
-          :to => to
38
+        'text' => text,
39
+        'to' => to
40 40
       }
41 41
       translate_uri.query = URI.encode_www_form params
42 42
       request = Net::HTTP::Get.new translate_uri.request_uri
@@ -47,7 +47,7 @@ module Agents
47 47
     end
48 48
 
49 49
     def validate_options
50
-      unless options[:client_id].present? && options[:client_secret].present? && options[:to].present? && options[:content].present? && options[:expected_receive_period_in_days].present?
50
+      unless options['client_id'].present? && options['client_secret'].present? && options['to'].present? && options['content'].present? && options['expected_receive_period_in_days'].present?
51 51
         errors.add :base, "client_id,client_secret,to,expected_receive_period_in_days and content are all required"
52 52
       end
53 53
     end
@@ -60,16 +60,16 @@ module Agents
60 60
 
61 61
     def receive(incoming_events)
62 62
       auth_uri = URI "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
63
-      response = postform auth_uri, :client_id => options[:client_id],
64
-                                    :client_secret => options[:client_secret],
63
+      response = postform auth_uri, :client_id => options['client_id'],
64
+                                    :client_secret => options['client_secret'],
65 65
                                     :scope => "http://api.microsofttranslator.com",
66 66
                                     :grant_type => "client_credentials"
67 67
       access_token = JSON.parse(response.body)["access_token"]
68 68
       incoming_events.each do |event|
69 69
         translated_event = {}
70
-        options[:content].each_pair do |key, value|
70
+        options['content'].each_pair do |key, value|
71 71
           to_be_translated = Utils.values_at event.payload, value
72
-          translated_event[key] = translate to_be_translated.first, options[:to], access_token
72
+          translated_event[key] = translate to_be_translated.first, options['to'], access_token
73 73
         end
74 74
         create_event :payload => translated_event
75 75
       end

+ 24 - 24
app/models/agents/trigger_agent.rb

@@ -23,57 +23,57 @@ module Agents
23 23
     MD
24 24
 
25 25
     def validate_options
26
-      unless options[:expected_receive_period_in_days].present? && options[:message].present? && options[:rules].present? &&
27
-          options[:rules].all? { |rule| rule[:type].present? && VALID_COMPARISON_TYPES.include?(rule[:type]) && rule[:value].present? && rule[:path].present? }
26
+      unless options['expected_receive_period_in_days'].present? && options['message'].present? && options['rules'].present? &&
27
+          options['rules'].all? { |rule| rule['type'].present? && VALID_COMPARISON_TYPES.include?(rule['type']) && rule['value'].present? && rule['path'].present? }
28 28
         errors.add(:base, "expected_receive_period_in_days, message, and rules, with a type, value, and path for every rule, are required")
29 29
       end
30 30
     end
31 31
 
32 32
     def default_options
33 33
       {
34
-          :expected_receive_period_in_days => "2",
35
-          :rules => [{
36
-                         :type => "regex",
37
-                         :value => "foo\\d+bar",
38
-                         :path => "topkey.subkey.subkey.goal",
39
-                     }],
40
-          :message => "Looks like your pattern matched in '<value>'!"
34
+        'expected_receive_period_in_days' => "2",
35
+        'rules' => [{
36
+                      'type' => "regex",
37
+                      'value' => "foo\\d+bar",
38
+                      'path' => "topkey.subkey.subkey.goal",
39
+                    }],
40
+        'message' => "Looks like your pattern matched in '<value>'!"
41 41
       }
42 42
     end
43 43
 
44 44
     def working?
45
-      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
45
+      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
46 46
     end
47 47
 
48 48
     def receive(incoming_events)
49 49
       incoming_events.each do |event|
50
-        match = options[:rules].all? do |rule|
51
-          value_at_path = Utils.value_at(event[:payload], rule[:path])
52
-          case rule[:type]
50
+        match = options['rules'].all? do |rule|
51
+          value_at_path = Utils.value_at(event['payload'], rule['path'])
52
+          case rule['type']
53 53
             when "regex"
54
-              value_at_path.to_s =~ Regexp.new(rule[:value], Regexp::IGNORECASE)
54
+              value_at_path.to_s =~ Regexp.new(rule['value'], Regexp::IGNORECASE)
55 55
             when "!regex"
56
-              value_at_path.to_s !~ Regexp.new(rule[:value], Regexp::IGNORECASE)
56
+              value_at_path.to_s !~ Regexp.new(rule['value'], Regexp::IGNORECASE)
57 57
             when "field>value"
58
-              value_at_path.to_f > rule[:value].to_f
58
+              value_at_path.to_f > rule['value'].to_f
59 59
             when "field>=value"
60
-              value_at_path.to_f >= rule[:value].to_f
60
+              value_at_path.to_f >= rule['value'].to_f
61 61
             when "field<value"
62
-              value_at_path.to_f < rule[:value].to_f
62
+              value_at_path.to_f < rule['value'].to_f
63 63
             when "field<=value"
64
-              value_at_path.to_f <= rule[:value].to_f
64
+              value_at_path.to_f <= rule['value'].to_f
65 65
             when "field==value"
66
-              value_at_path.to_s == rule[:value].to_s
66
+              value_at_path.to_s == rule['value'].to_s
67 67
             when "field!=value"
68
-              value_at_path.to_s != rule[:value].to_s
68
+              value_at_path.to_s != rule['value'].to_s
69 69
             else
70
-              raise "Invalid :type of #{rule[:type]} in TriggerAgent##{id}"
70
+              raise "Invalid type of #{rule['type']} in TriggerAgent##{id}"
71 71
           end
72 72
         end
73 73
 
74 74
         if match
75
-          create_event :payload => { :message => make_message(event[:payload]) } # Maybe this should include the
76
-                                                                                 # original event as well?
75
+          create_event :payload => { 'message' => make_message(event[:payload]) } # Maybe this should include the
76
+                                                                                  # original event as well?
77 77
         end
78 78
       end
79 79
     end

+ 25 - 25
app/models/agents/twilio_agent.rb

@@ -9,7 +9,7 @@ module Agents
9 9
     description <<-MD
10 10
       The TwilioAgent receives and collects events and sends them via text message or gives you a call when scheduled.
11 11
 
12
-      It is assumed that events have a `:message`, `:text`, or `:sms` key, the value of which is sent as the content of the text message/call. You can use Event Formatting Agent if your event does not provide these keys.
12
+      It is assumed that events have a `message`, `text`, or `sms` key, the value of which is sent as the content of the text message/call. You can use Event Formatting Agent if your event does not provide these keys.
13 13
 
14 14
       Set `receiver_cell` to the number to receive text messages/call and `sender_cell` to the number sending them.
15 15
 
@@ -22,35 +22,35 @@ module Agents
22 22
 
23 23
     def default_options
24 24
       {
25
-        :account_sid => 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
26
-        :auth_token => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
27
-        :sender_cell => 'xxxxxxxxxx',
28
-        :receiver_cell => 'xxxxxxxxxx',
29
-        :server_url    => 'http://somename.com:3000',
30
-        :receive_text  => 'true',
31
-        :receive_call  => 'false',
32
-        :expected_receive_period_in_days => '1'
25
+        'account_sid' => 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
26
+        'auth_token' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
27
+        'sender_cell' => 'xxxxxxxxxx',
28
+        'receiver_cell' => 'xxxxxxxxxx',
29
+        'server_url'    => 'http://somename.com:3000',
30
+        'receive_text'  => 'true',
31
+        'receive_call'  => 'false',
32
+        'expected_receive_period_in_days' => '1'
33 33
       }
34 34
     end
35 35
 
36 36
     def validate_options
37
-      unless options[:account_sid].present? && options[:auth_token].present? && options[:sender_cell].present? && options[:receiver_cell].present? && options[:expected_receive_period_in_days].present? && options[:receive_call].present? && options[:receive_text].present?
37
+      unless options['account_sid'].present? && options['auth_token'].present? && options['sender_cell'].present? && options['receiver_cell'].present? && options['expected_receive_period_in_days'].present? && options['receive_call'].present? && options['receive_text'].present?
38 38
         errors.add(:base, 'account_sid, auth_token, sender_cell, receiver_cell, receive_text, receive_call and expected_receive_period_in_days are all required')
39 39
       end
40 40
     end
41 41
 
42 42
     def receive(incoming_events)
43
-      @client = Twilio::REST::Client.new options[:account_sid], options[:auth_token]
44
-      memory[:pending_calls] ||= {}
43
+      @client = Twilio::REST::Client.new options['account_sid'], options['auth_token']
44
+      memory['pending_calls'] ||= {}
45 45
       incoming_events.each do |event|
46
-        message = (event.payload[:message] || event.payload[:text] || event.payload[:sms]).to_s
46
+        message = (event.payload['message'] || event.payload['text'] || event.payload['sms']).to_s
47 47
         if message != ""
48
-          if options[:receive_call].to_s == 'true'
48
+          if options['receive_call'].to_s == 'true'
49 49
             secret = SecureRandom.hex 3
50
-            memory[:pending_calls][secret] = message
50
+            memory['pending_calls'][secret] = message
51 51
             make_call secret
52 52
           end
53
-          if options[:receive_text].to_s == 'true'
53
+          if options['receive_text'].to_s == 'true'
54 54
             message = message.slice 0..160
55 55
             send_message message
56 56
           end
@@ -59,19 +59,19 @@ module Agents
59 59
     end
60 60
 
61 61
     def working?
62
-      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago && !recent_error_logs?
62
+      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
63 63
     end
64 64
 
65 65
     def send_message(message)
66
-      @client.account.sms.messages.create :from => options[:sender_cell],
67
-                                          :to => options[:receiver_cell],
66
+      @client.account.sms.messages.create :from => options['sender_cell'],
67
+                                          :to => options['receiver_cell'],
68 68
                                           :body => message
69 69
     end
70 70
 
71 71
     def make_call(secret)
72
-      @client.account.calls.create :from => options[:sender_cell],
73
-                                   :to => options[:receiver_cell],
74
-                                   :url => post_url(options[:server_url],secret)
72
+      @client.account.calls.create :from => options['sender_cell'],
73
+                                   :to => options['receiver_cell'],
74
+                                   :url => post_url(options['server_url'],secret)
75 75
     end
76 76
 
77 77
     def post_url(server_url,secret)
@@ -79,9 +79,9 @@ module Agents
79 79
     end
80 80
 
81 81
     def receive_webhook(params)
82
-      if memory[:pending_calls].has_key? params[:secret].to_sym
83
-        response = Twilio::TwiML::Response.new {|r| r.Say memory[:pending_calls][params[:secret].to_sym], :voice => 'woman'}
84
-        memory[:pending_calls].delete params[:secret].to_sym
82
+      if memory['pending_calls'].has_key? params['secret']
83
+        response = Twilio::TwiML::Response.new {|r| r.Say memory['pending_calls'][params['secret']], :voice => 'woman'}
84
+        memory['pending_calls'].delete params['secret']
85 85
         [response.text, 200]
86 86
       end
87 87
     end

+ 20 - 20
app/models/agents/twitter_publish_agent.rb

@@ -19,25 +19,25 @@ module Agents
19 19
     MD
20 20
 
21 21
     def validate_options
22
-      unless options[:username].present? &&
23
-        options[:expected_update_period_in_days].present?
22
+      unless options['username'].present? &&
23
+        options['expected_update_period_in_days'].present?
24 24
         errors.add(:base, "username and expected_update_period_in_days are required")
25 25
       end      
26 26
     end
27 27
 
28 28
     def working?
29
-      (event = event_created_within(options[:expected_update_period_in_days])) && event.payload[:success] == true && !recent_error_logs?
29
+      event_created_within?(options['expected_update_period_in_days']) && most_recent_event.payload['success'] == true && !recent_error_logs?
30 30
     end
31 31
 
32 32
     def default_options
33 33
       {
34
-          :username => "",
35
-          :expected_update_period_in_days => "10",
36
-          :consumer_key => "---",
37
-          :consumer_secret => "---",
38
-          :oauth_token => "---",
39
-          :oauth_token_secret => "---",
40
-          :message_path => "text"
34
+        'username' => "",
35
+        'expected_update_period_in_days' => "10",
36
+        'consumer_key' => "---",
37
+        'consumer_secret' => "---",
38
+        'oauth_token' => "---",
39
+        'oauth_token_secret' => "---",
40
+        'message_path' => "text"
41 41
       }
42 42
     end
43 43
 
@@ -47,22 +47,22 @@ module Agents
47 47
         incoming_events = incoming_events.first(20)
48 48
       end
49 49
       incoming_events.each do |event|
50
-        tweet_text = Utils.value_at(event.payload, options[:message_path])
50
+        tweet_text = Utils.value_at(event.payload, options['message_path'])
51 51
         begin
52 52
           publish_tweet tweet_text
53 53
           create_event :payload => {
54
-            :success => true,
55
-            :published_tweet => tweet_text,
56
-            :agent_id => event.agent_id,
57
-            :event_id => event.id
54
+            'success' => true,
55
+            'published_tweet' => tweet_text,
56
+            'agent_id' => event.agent_id,
57
+            'event_id' => event.id
58 58
           }
59 59
         rescue Twitter::Error => e
60 60
           create_event :payload => {
61
-            :success => false,
62
-            :error => e.message,
63
-            :failed_tweet => tweet_text,
64
-            :agent_id => event.agent_id,
65
-            :event_id => event.id
61
+            'success' => false,
62
+            'error' => e.message,
63
+            'failed_tweet' => tweet_text,
64
+            'agent_id' => event.agent_id,
65
+            'event_id' => event.id
66 66
           }
67 67
         end
68 68
       end

+ 23 - 23
app/models/agents/twitter_stream_agent.rb

@@ -54,26 +54,26 @@ module Agents
54 54
     default_schedule "11pm"
55 55
 
56 56
     def validate_options
57
-      unless options[:filters].present? &&
58
-             options[:expected_update_period_in_days].present? &&
59
-             options[:generate].present?
57
+      unless options['filters'].present? &&
58
+             options['expected_update_period_in_days'].present? &&
59
+             options['generate'].present?
60 60
         errors.add(:base, "expected_update_period_in_days, generate, and filters are required fields")
61 61
       end
62 62
     end
63 63
 
64 64
     def working?
65
-      event_created_within(options[:expected_update_period_in_days]) && !recent_error_logs?
65
+      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
66 66
     end
67 67
 
68 68
     def default_options
69 69
       {
70
-          :consumer_key => "---",
71
-          :consumer_secret => "---",
72
-          :oauth_token => "---",
73
-          :oauth_token_secret => "---",
74
-          :filters => %w[keyword1 keyword2],
75
-          :expected_update_period_in_days => "2",
76
-          :generate => "events"
70
+          'consumer_key' => "---",
71
+          'consumer_secret' => "---",
72
+          'oauth_token' => "---",
73
+          'oauth_token_secret' => "---",
74
+          'filters' => %w[keyword1 keyword2],
75
+          'expected_update_period_in_days' => "2",
76
+          'generate' => "events"
77 77
       }
78 78
     end
79 79
 
@@ -81,33 +81,33 @@ module Agents
81 81
       filter = lookup_filter(filter)
82 82
 
83 83
       if filter
84
-        if options[:generate] == "counts"
84
+        if options['generate'] == "counts"
85 85
           # Avoid memory pollution by reloading the Agent.
86 86
           agent = Agent.find(id)
87
-          agent.memory[:filter_counts] ||= {}
88
-          agent.memory[:filter_counts][filter.to_sym] ||= 0
89
-          agent.memory[:filter_counts][filter.to_sym] += 1
90
-          remove_unused_keys!(agent, :filter_counts)
87
+          agent.memory['filter_counts'] ||= {}
88
+          agent.memory['filter_counts'][filter] ||= 0
89
+          agent.memory['filter_counts'][filter] += 1
90
+          remove_unused_keys!(agent, 'filter_counts')
91 91
           agent.save!
92 92
         else
93
-          create_event :payload => status.merge(:filter => filter.to_s)
93
+          create_event :payload => status.merge('filter' => filter)
94 94
         end
95 95
       end
96 96
     end
97 97
 
98 98
     def check
99
-      if options[:generate] == "counts" && memory[:filter_counts] && memory[:filter_counts].length > 0
100
-        memory[:filter_counts].each do |filter, count|
101
-          create_event :payload => { :filter => filter.to_s, :count => count, :time => Time.now.to_i }
99
+      if options['generate'] == "counts" && memory['filter_counts'] && memory['filter_counts'].length > 0
100
+        memory['filter_counts'].each do |filter, count|
101
+          create_event :payload => { 'filter' => filter, 'count' => count, 'time' => Time.now.to_i }
102 102
         end
103 103
       end
104
-      memory[:filter_counts] = {}
104
+      memory['filter_counts'] = {}
105 105
     end
106 106
 
107 107
     protected
108 108
 
109 109
     def lookup_filter(filter)
110
-      options[:filters].each do |known_filter|
110
+      options['filters'].each do |known_filter|
111 111
         if known_filter == filter
112 112
           return filter
113 113
         elsif known_filter.is_a?(Array)
@@ -120,7 +120,7 @@ module Agents
120 120
 
121 121
     def remove_unused_keys!(agent, base)
122 122
       if agent.memory[base]
123
-        (agent.memory[base].keys - agent.options[:filters].map {|f| f.is_a?(Array) ? f.first.to_sym : f.to_sym }).each do |removed_key|
123
+        (agent.memory[base].keys - agent.options['filters'].map {|f| f.is_a?(Array) ? f.first.to_s : f.to_s }).each do |removed_key|
124 124
           agent.memory[base].delete(removed_key)
125 125
         end
126 126
       end

+ 12 - 12
app/models/agents/twitter_user_agent.rb

@@ -41,36 +41,36 @@ module Agents
41 41
     default_schedule "every_1h"
42 42
 
43 43
     def validate_options
44
-      unless options[:username].present? &&
45
-        options[:expected_update_period_in_days].present?
44
+      unless options['username'].present? &&
45
+        options['expected_update_period_in_days'].present?
46 46
         errors.add(:base, "username and expected_update_period_in_days are required")
47 47
       end      
48 48
     end
49 49
 
50 50
     def working?
51
-      event_created_within(options[:expected_update_period_in_days]) && !recent_error_logs?
51
+      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
52 52
     end
53 53
 
54 54
     def default_options
55 55
       {
56
-          :username => "tectonic",
57
-          :expected_update_period_in_days => "2",
58
-          :consumer_key => "---",
59
-          :consumer_secret => "---",
60
-          :oauth_token => "---",
61
-          :oauth_token_secret => "---"
56
+          'username' => "tectonic",
57
+          'expected_update_period_in_days' => "2",
58
+          'consumer_key' => "---",
59
+          'consumer_secret' => "---",
60
+          'oauth_token' => "---",
61
+          'oauth_token_secret' => "---"
62 62
       }
63 63
     end
64 64
 
65 65
     def check
66
-      since_id = memory[:since_id] || nil
66
+      since_id = memory['since_id'] || nil
67 67
       opts = {:count => 200, :include_rts => true, :exclude_replies => false, :include_entities => true, :contributor_details => true}
68 68
       opts.merge! :since_id => since_id unless since_id.nil?
69 69
 
70
-      tweets = Twitter.user_timeline(options[:username], opts)
70
+      tweets = Twitter.user_timeline(options['username'], opts)
71 71
 
72 72
       tweets.each do |tweet|
73
-        memory[:since_id] = tweet.id if !memory[:since_id] || (tweet.id > memory[:since_id])
73
+        memory['since_id'] = tweet.id if !memory['since_id'] || (tweet.id > memory['since_id'])
74 74
 
75 75
         create_event :payload => tweet.attrs
76 76
       end

+ 3 - 3
app/models/agents/user_location_agent.rb

@@ -30,15 +30,15 @@ module Agents
30 30
     MD
31 31
 
32 32
     def working?
33
-      event_created_within(2) && !recent_error_logs?
33
+      event_created_within?(2) && !recent_error_logs?
34 34
     end
35 35
 
36 36
     def default_options
37
-      { :secret => SecureRandom.hex(7) }
37
+      { 'secret' => SecureRandom.hex(7) }
38 38
     end
39 39
 
40 40
     def validate_options
41
-      errors.add(:base, "secret is required and must be longer than 4 characters") unless options[:secret].present? && options[:secret].length > 4
41
+      errors.add(:base, "secret is required and must be longer than 4 characters") unless options['secret'].present? && options['secret'].length > 4
42 42
     end
43 43
   end
44 44
 end

+ 9 - 9
app/models/agents/weather_agent.rb

@@ -41,34 +41,34 @@ module Agents
41 41
     default_schedule "8pm"
42 42
 
43 43
     def working?
44
-      event_created_within(2) && !recent_error_logs?
44
+      event_created_within?(2) && !recent_error_logs?
45 45
     end
46 46
 
47 47
     def wunderground
48
-      Wunderground.new(options[:api_key]) if key_setup?
48
+      Wunderground.new(options['api_key']) if key_setup?
49 49
     end
50 50
 
51 51
     def key_setup?
52
-      options[:api_key] && options[:api_key] != "your-key"
52
+      options['api_key'] && options['api_key'] != "your-key"
53 53
     end
54 54
 
55 55
     def default_options
56 56
       {
57
-        :api_key => "your-key",
58
-        :location => "94103"
57
+        'api_key' => "your-key",
58
+        'location' => "94103"
59 59
       }
60 60
     end
61 61
 
62 62
     def validate_options
63
-      errors.add(:base, "location is required") unless options[:location].present? || options[:zipcode].present?
64
-      errors.add(:base, "api_key is required") unless options[:api_key].present?
63
+      errors.add(:base, "location is required") unless options['location'].present? || options['zipcode'].present?
64
+      errors.add(:base, "api_key is required") unless options['api_key'].present?
65 65
     end
66 66
 
67 67
     def check
68 68
       if key_setup?
69
-        wunderground.forecast_for(options[:location] || options[:zipcode])["forecast"]["simpleforecast"]["forecastday"].each do |day|
69
+        wunderground.forecast_for(options['location'] || options['zipcode'])["forecast"]["simpleforecast"]["forecastday"].each do |day|
70 70
           if is_tomorrow?(day)
71
-            create_event :payload => day.merge(:location => options[:location] || options[:zipcode])
71
+            create_event :payload => day.merge('location' => options['location'] || options['zipcode'])
72 72
           end
73 73
         end
74 74
       end

+ 8 - 8
app/models/agents/webhook_agent.rb

@@ -18,7 +18,7 @@ module Agents
18 18
           * `secret` - A token that the host will provide for authentication.
19 19
           * `expected_receive_period_in_days` - How often you expect to receive
20 20
             events this way. Used to determine if the agent is working.
21
-          * `payload_path` - JSONPath of the attribute of the POST body to be
21
+          * `payload_path` - JSONPath of the attribute in the POST body to be
22 22
             used as the Event payload.
23 23
       MD
24 24
     end
@@ -26,7 +26,7 @@ module Agents
26 26
     event_description do
27 27
       <<-MD
28 28
         The event payload is base on the value of the `payload_path` option,
29
-        which is set to `#{options[:payload_path]}`.
29
+        which is set to `#{options['payload_path']}`.
30 30
       MD
31 31
     end
32 32
 
@@ -37,8 +37,8 @@ module Agents
37 37
     end
38 38
 
39 39
     def receive_webhook(params)
40
-      secret = params.delete(:secret)
41
-      return ["Not Authorized", 401] unless secret == options[:secret]
40
+      secret = params.delete('secret')
41
+      return ["Not Authorized", 401] unless secret == options['secret']
42 42
 
43 43
       create_event(:payload => payload_for(params))
44 44
 
@@ -46,17 +46,17 @@ module Agents
46 46
     end
47 47
 
48 48
     def working?
49
-      event_created_within(options[:expected_receive_period_in_days]) && !recent_error_logs?
49
+      event_created_within(options['expected_receive_period_in_days']) && !recent_error_logs?
50 50
     end
51 51
 
52 52
     def validate_options
53
-      unless options[:secret].present?
54
-        errors.add(:base, "Must specify a :secret for 'Authenticating' requests")
53
+      unless options['secret'].present?
54
+        errors.add(:base, "Must specify a secret for 'Authenticating' requests")
55 55
       end
56 56
     end
57 57
 
58 58
     def payload_for(params)
59
-      Utils.values_at(params, options[:payload_path]) || {}
59
+      Utils.value_at(params, options['payload_path']) || {}
60 60
     end
61 61
   end
62 62
 end

+ 39 - 39
app/models/agents/website_agent.rb

@@ -15,19 +15,19 @@ module Agents
15 15
 
16 16
       To tell the Agent how to parse the content, specify `extract` as a hash with keys naming the extractions and values of hashes.
17 17
 
18
-      When parsing HTML or XML, these sub-hashes specify how to extract with a `:css` CSS selector and either `:text => true` or `attr` pointing to an attribute name to grab.  An example:
18
+      When parsing HTML or XML, these sub-hashes specify how to extract with a `css` CSS selector and either `'text': true` or `attr` pointing to an attribute name to grab.  An example:
19 19
 
20
-          :extract => {
21
-            :url => { :css => "#comic img", :attr => "src" },
22
-            :title => { :css => "#comic img", :attr => "title" },
23
-            :body_text => { :css => "div.main", :text => true }
20
+          'extract': {
21
+            'url': { 'css': "#comic img", 'attr': "src" },
22
+            'title': { 'css': "#comic img", 'attr': "title" },
23
+            'body_text': { 'css': "div.main", 'text': true }
24 24
           }
25 25
 
26 26
       When parsing JSON, these sub-hashes specify [JSONPaths](http://goessner.net/articles/JsonPath/) to the values that you care about.  For example:
27 27
 
28
-          :extract => {
29
-            :title => { :path => "results.data[*].title" },
30
-            :description => { :path => "results.data[*].description" }
28
+          'extract': {
29
+            'title': { 'path': "results.data[*].title" },
30
+            'description': { 'path': "results.data[*].description" }
31 31
           }
32 32
 
33 33
       Note that for all of the formats, whatever you extract MUST have the same number of matches for each extractor.  E.g., if you're extracting rows, all extractors must match all rows.  For generating CSS selectors, something like [SelectorGadget](http://selectorgadget.com) may be helpful.
@@ -36,7 +36,7 @@ module Agents
36 36
     MD
37 37
 
38 38
     event_description do
39
-      "Events will have the fields you specified.  Your options look like:\n\n    #{Utils.pretty_print options[:extract]}"
39
+      "Events will have the fields you specified.  Your options look like:\n\n    #{Utils.pretty_print options['extract']}"
40 40
     end
41 41
 
42 42
     default_schedule "every_12h"
@@ -44,33 +44,33 @@ module Agents
44 44
     UNIQUENESS_LOOK_BACK = 30
45 45
 
46 46
     def working?
47
-      event_created_within(options[:expected_update_period_in_days]) && !recent_error_logs?
47
+      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
48 48
     end
49 49
 
50 50
     def default_options
51 51
       {
52
-          :expected_update_period_in_days => "2",
53
-          :url => "http://xkcd.com",
54
-          :type => "html",
55
-          :mode => :on_change,
56
-          :extract => {
57
-              :url => {:css => "#comic img", :attr => "src"},
58
-              :title => {:css => "#comic img", :attr => "title"}
52
+          'expected_update_period_in_days' => "2",
53
+          'url' => "http://xkcd.com",
54
+          'type' => "html",
55
+          'mode' => :on_change,
56
+          'extract' => {
57
+            'url' => {'css' => "#comic img", 'attr' => "src"},
58
+            'title' => {'css' => "#comic img", 'attr' => "title"}
59 59
           }
60 60
       }
61 61
     end
62 62
 
63 63
     def validate_options
64
-      errors.add(:base, "url and expected_update_period_in_days are required") unless options[:expected_update_period_in_days].present? && options[:url].present?
65
-      if !options[:extract].present? && extraction_type != "json"
64
+      errors.add(:base, "url and expected_update_period_in_days are required") unless options['expected_update_period_in_days'].present? && options['url'].present?
65
+      if !options['extract'].present? && extraction_type != "json"
66 66
         errors.add(:base, "extract is required for all types except json")
67 67
       end
68 68
     end
69 69
 
70 70
     def check
71 71
       hydra = Typhoeus::Hydra.new
72
-      log "Fetching #{options[:url]}"
73
-      request = Typhoeus::Request.new(options[:url], :followlocation => true)
72
+      log "Fetching #{options['url']}"
73
+      request = Typhoeus::Request.new(options['url'], :followlocation => true)
74 74
       request.on_failure do |response|
75 75
         error "Failed: #{response.inspect}"
76 76
       end
@@ -85,37 +85,37 @@ module Agents
85 85
           end
86 86
         else
87 87
           output = {}
88
-          options[:extract].each do |name, extraction_details|
88
+          options['extract'].each do |name, extraction_details|
89 89
             result = if extraction_type == "json"
90
-                       output[name] = Utils.values_at(doc, extraction_details[:path])
90
+                       output[name] = Utils.values_at(doc, extraction_details['path'])
91 91
                      else
92
-                       output[name] = doc.css(extraction_details[:css]).map { |node|
93
-                         if extraction_details[:attr]
94
-                           node.attr(extraction_details[:attr])
95
-                         elsif extraction_details[:text]
92
+                       output[name] = doc.css(extraction_details['css']).map { |node|
93
+                         if extraction_details['attr']
94
+                           node.attr(extraction_details['attr'])
95
+                         elsif extraction_details['text']
96 96
                            node.text()
97 97
                          else
98
-                           error ":attr or :text is required on HTML or XML extraction patterns"
98
+                           error "'attr' or 'text' is required on HTML or XML extraction patterns"
99 99
                            return
100 100
                          end
101 101
                        }
102 102
                      end
103
-            log "Extracting #{extraction_type} at #{extraction_details[:path] || extraction_details[:css]}: #{result}"
103
+            log "Extracting #{extraction_type} at #{extraction_details['path'] || extraction_details['css']}: #{result}"
104 104
           end
105 105
 
106
-          num_unique_lengths = options[:extract].keys.map { |name| output[name].length }.uniq
106
+          num_unique_lengths = options['extract'].keys.map { |name| output[name].length }.uniq
107 107
 
108 108
           if num_unique_lengths.length != 1
109
-            error "Got an uneven number of matches for #{options[:name]}: #{options[:extract].inspect}"
109
+            error "Got an uneven number of matches for #{options['name']}: #{options['extract'].inspect}"
110 110
             return
111 111
           end
112 112
       
113 113
           num_unique_lengths.first.times do |index|
114 114
             result = {}
115
-            options[:extract].keys.each do |name|
115
+            options['extract'].keys.each do |name|
116 116
               result[name] = output[name][index]
117 117
               if name.to_s == 'url'
118
-                result[name] = URI.join(options[:url], result[name]).to_s if (result[name] =~ URI::DEFAULT_PARSER.regexp[:ABS_URI]).nil?
118
+                result[name] = URI.join(options['url'], result[name]).to_s if (result[name] =~ URI::DEFAULT_PARSER.regexp[:ABS_URI]).nil?
119 119
               end
120 120
             end
121 121
 
@@ -133,22 +133,22 @@ module Agents
133 133
     private
134 134
 
135 135
     def store_payload? result
136
-      !options[:mode] || options[:mode].to_s == "all" || (options[:mode].to_s == "on_change" && !previous_payloads.include?(result.to_json))
136
+      !options['mode'] || options['mode'].to_s == "all" || (options['mode'].to_s == "on_change" && !previous_payloads.include?(result.to_json))
137 137
     end
138 138
 
139 139
     def previous_payloads
140
-      events.order("id desc").limit(UNIQUENESS_LOOK_BACK).pluck(:payload).map(&:to_json) if options[:mode].to_s == "on_change"
140
+      events.order("id desc").limit(UNIQUENESS_LOOK_BACK).pluck(:payload).map(&:to_json) if options['mode'].to_s == "on_change"
141 141
     end
142 142
 
143 143
     def extract_full_json?
144
-      (!options[:extract].present? && extraction_type == "json")
144
+      (!options['extract'].present? && extraction_type == "json")
145 145
     end
146 146
 
147 147
     def extraction_type
148
-      (options[:type] || begin
149
-        if options[:url] =~ /\.(rss|xml)$/i
148
+      (options['type'] || begin
149
+        if options['url'] =~ /\.(rss|xml)$/i
150 150
           "xml"
151
-        elsif options[:url] =~ /\.json$/i
151
+        elsif options['url'] =~ /\.json$/i
152 152
           "json"
153 153
         else
154 154
           "html"

+ 19 - 19
app/models/agents/weibo_publish_agent.rb

@@ -20,24 +20,24 @@ module Agents
20 20
     MD
21 21
 
22 22
     def validate_options
23
-      unless options[:uid].present? &&
24
-        options[:expected_update_period_in_days].present?
23
+      unless options['uid'].present? &&
24
+        options['expected_update_period_in_days'].present?
25 25
         errors.add(:base, "expected_update_period_in_days and uid are required")
26 26
       end
27 27
     end
28 28
 
29 29
     def working?
30
-      (event = event_created_within(options[:expected_update_period_in_days])) && event.payload[:success] == true && !recent_error_logs?
30
+      event_created_within?(options['expected_update_period_in_days']) && most_recent_event.payload['success'] == true && !recent_error_logs?
31 31
     end
32 32
 
33 33
     def default_options
34 34
       {
35
-          :uid => "",
36
-          :access_token => "---",
37
-          :app_key => "---",
38
-          :app_secret => "---",
39
-          :expected_update_period_in_days => "10",
40
-          :message_path => "text"
35
+        'uid' => "",
36
+        'access_token' => "---",
37
+        'app_key' => "---",
38
+        'app_secret' => "---",
39
+        'expected_update_period_in_days' => "10",
40
+        'message_path' => "text"
41 41
       }
42 42
     end
43 43
 
@@ -47,25 +47,25 @@ module Agents
47 47
         incoming_events = incoming_events.first(20)
48 48
       end
49 49
       incoming_events.each do |event|
50
-        tweet_text = Utils.value_at(event.payload, options[:message_path])
50
+        tweet_text = Utils.value_at(event.payload, options['message_path'])
51 51
         if event.agent.type == "Agents::TwitterUserAgent"
52 52
           tweet_text = unwrap_tco_urls(tweet_text, event.payload)
53 53
         end
54 54
         begin
55 55
           publish_tweet tweet_text
56 56
           create_event :payload => {
57
-            :success => true,
58
-            :published_tweet => tweet_text,
59
-            :agent_id => event.agent_id,
60
-            :event_id => event.id
57
+            'success' => true,
58
+            'published_tweet' => tweet_text,
59
+            'agent_id' => event.agent_id,
60
+            'event_id' => event.id
61 61
           }
62 62
         rescue OAuth2::Error => e
63 63
           create_event :payload => {
64
-            :success => false,
65
-            :error => e.message,
66
-            :failed_tweet => tweet_text,
67
-            :agent_id => event.agent_id,
68
-            :event_id => event.id
64
+            'success' => false,
65
+            'error' => e.message,
66
+            'failed_tweet' => tweet_text,
67
+            'agent_id' => event.agent_id,
68
+            'event_id' => event.id
69 69
           }
70 70
         end
71 71
       end

+ 11 - 11
app/models/agents/weibo_user_agent.rb

@@ -70,29 +70,29 @@ module Agents
70 70
     default_schedule "every_1h"
71 71
 
72 72
     def validate_options
73
-      unless options[:uid].present? &&
74
-        options[:expected_update_period_in_days].present?
73
+      unless options['uid'].present? &&
74
+        options['expected_update_period_in_days'].present?
75 75
         errors.add(:base, "expected_update_period_in_days and uid are required")
76 76
       end
77 77
     end
78 78
 
79 79
     def working?
80
-      event_created_within(options[:expected_update_period_in_days]) && !recent_error_logs?
80
+      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
81 81
     end
82 82
 
83 83
     def default_options
84 84
       {
85
-          :uid => "",
86
-          :access_token => "---",
87
-          :app_key => "---",
88
-          :app_secret => "---",
89
-          :expected_update_period_in_days => "2"
85
+        'uid' => "",
86
+        'access_token' => "---",
87
+        'app_key' => "---",
88
+        'app_secret' => "---",
89
+        'expected_update_period_in_days' => "2"
90 90
       }
91 91
     end
92 92
 
93 93
     def check
94
-      since_id = memory[:since_id] || nil
95
-      opts = {:uid => options[:uid].to_i}
94
+      since_id = memory['since_id'] || nil
95
+      opts = {:uid => options['uid'].to_i}
96 96
       opts.merge! :since_id => since_id unless since_id.nil?
97 97
 
98 98
       # http://open.weibo.com/wiki/2/statuses/user_timeline/en
@@ -101,7 +101,7 @@ module Agents
101 101
 
102 102
 
103 103
         resp[:statuses].each do |status|
104
-          memory[:since_id] = status.id if !memory[:since_id] || (status.id > memory[:since_id])
104
+          memory['since_id'] = status.id if !memory['since_id'] || (status.id > memory['since_id'])
105 105
 
106 106
           create_event :payload => status.as_json
107 107
         end

+ 6 - 8
app/models/event.rb

@@ -1,23 +1,21 @@
1
+require 'json_serialized_field'
2
+
1 3
 class Event < ActiveRecord::Base
4
+  include JSONSerializedField
5
+
2 6
   attr_accessible :lat, :lng, :payload, :user_id, :user, :expires_at
3 7
 
4 8
   acts_as_mappable
5 9
 
6
-  serialize :payload
10
+  json_serialize :payload
7 11
 
8 12
   belongs_to :user
9
-  belongs_to :agent, :counter_cache => true
10
-
11
-  before_save :symbolize_payload
13
+  belongs_to :agent, :counter_cache => true, :touch => :last_event_at
12 14
 
13 15
   scope :recent, lambda { |timespan = 12.hours.ago|
14 16
     where("events.created_at > ?", timespan)
15 17
   }
16 18
 
17
-  def symbolize_payload
18
-    self.payload = payload.recursively_symbolize_keys if payload.is_a?(Hash)
19
-  end
20
-
21 19
   def reemit!
22 20
     agent.create_event :payload => payload, :lat => lat, :lng => lng
23 21
   end

+ 1 - 1
app/views/agents/show.html.erb

@@ -11,7 +11,7 @@
11 11
             <li class='disabled'><a><i class='icon-picture'></i> Summary</a></li>
12 12
             <li class='active'><a href="#details" data-toggle="tab"><i class='icon-indent-left'></i> Details</a></li>
13 13
           <% end %>
14
-          <li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>"><i class='icon-list-alt'></i> Logs</a></li>
14
+          <li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>" class='<%= @agent.recent_error_logs? ? 'recent-errors' : '' %>'><i class='icon-list-alt'></i> Logs</a></li>
15 15
 
16 16
           <% if @agent.can_create_events? && @agent.events.count > 0 %>
17 17
             <li><%= link_to '<i class="icon-random"></i> Events'.html_safe, events_path(:agent => @agent.to_param) %></li>

+ 1 - 1
app/views/layouts/application.html.erb

@@ -36,7 +36,7 @@
36 36
     <script>
37 37
       var agentPaths = {};
38 38
       <% if current_user -%>
39
-        var myAgents = <%= Utils.jsonify(current_user.agents.inject({}) {|m, a| m[a.name] = agent_path(a) unless a.new_record?; m }) %>;
39
+        var myAgents = <%= Utils.jsonify(current_user.agents.select([:name, :id, :schedule]).inject({}) {|m, a| m[a.name] = agent_path(a); m }) %>;
40 40
         $.extend(agentPaths, myAgents);
41 41
       <% end -%>
42 42
       agentPaths["All Agents Index"] = <%= Utils.jsonify agents_path %>;

+ 0 - 7
config/initializers/recursively_symbolize_keys.rb

@@ -1,7 +0,0 @@
1
-require 'utils'
2
-
3
-class Hash
4
-  def recursively_symbolize_keys
5
-    Utils.recursively_symbolize_keys self
6
-  end
7
-end

+ 67 - 0
db/migrate/20131223032112_switch_to_json_serialization.rb

@@ -0,0 +1,67 @@
1
+class SwitchToJsonSerialization < ActiveRecord::Migration
2
+  FIELDS = {
3
+    :agents => [:options, :memory],
4
+    :events => [:payload]
5
+  }
6
+
7
+  def up
8
+    if data_exists?
9
+      puts "This migration will update tables to use UTF-8 encoding and will update Agent and Event storage from YAML to JSON."
10
+      puts "It should work, but please make a backup before proceeding!"
11
+      print "Continue? (y/n) "
12
+      STDOUT.flush
13
+      exit unless STDIN.gets =~ /^y/i
14
+
15
+      set_to_utf8
16
+      translate YAML, JSON
17
+    end
18
+  end
19
+
20
+  def down
21
+    if data_exists?
22
+      translate JSON, YAML
23
+    end
24
+  end
25
+
26
+  def set_to_utf8
27
+    if mysql?
28
+      %w[agent_logs agents delayed_jobs events links users].each do |table_name|
29
+        quoted_table_name = ActiveRecord::Base.connection.quote_table_name(table_name)
30
+        execute "ALTER TABLE #{quoted_table_name} CONVERT TO CHARACTER SET utf8"
31
+      end
32
+    end
33
+  end
34
+
35
+  def mysql?
36
+    ActiveRecord::Base.connection.adapter_name =~ /mysql/i
37
+  end
38
+
39
+  def data_exists?
40
+    events = ActiveRecord::Base.connection.select_rows("SELECT count(*) FROM #{ActiveRecord::Base.connection.quote_table_name("events")}").first.first
41
+    agents = ActiveRecord::Base.connection.select_rows("SELECT count(*) FROM #{ActiveRecord::Base.connection.quote_table_name("agents")}").first.first
42
+    agents + events > 0
43
+  end
44
+
45
+  def translate(from, to)
46
+    FIELDS.each do |table, fields|
47
+      quoted_table_name = ActiveRecord::Base.connection.quote_table_name(table)
48
+      fields = fields.map { |f| ActiveRecord::Base.connection.quote_column_name(f) }
49
+
50
+      rows = ActiveRecord::Base.connection.select_rows("SELECT id, #{fields.join(", ")} FROM #{quoted_table_name}")
51
+      rows.each do |row|
52
+        id, *field_data = row
53
+
54
+        yaml_fields = field_data.map { |f| from.load(f) }.map { |f| to.dump(f) }
55
+
56
+        yaml_fields.map! {|f| f.encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '??') }
57
+
58
+        update_sql = "UPDATE #{quoted_table_name} SET #{fields.map {|f| "#{f}=?"}.join(", ")} WHERE id = ?"
59
+
60
+        sanitized_update_sql = ActiveRecord::Base.send :sanitize_sql_array, [update_sql, *yaml_fields, id]
61
+
62
+        ActiveRecord::Base.connection.execute sanitized_update_sql
63
+      end
64
+    end
65
+
66
+  end
67
+end

+ 14 - 0
db/migrate/20131227000021_add_cached_dates_to_agent.rb

@@ -0,0 +1,14 @@
1
+class AddCachedDatesToAgent < ActiveRecord::Migration
2
+  def up
3
+    add_column :agents, :last_event_at, :datetime
4
+    execute "UPDATE agents SET last_event_at = (SELECT created_at FROM events WHERE events.agent_id = agents.id ORDER BY id DESC LIMIT 1)"
5
+
6
+    add_column :agents, :last_error_log_at, :datetime
7
+    execute "UPDATE agents SET last_error_log_at = (SELECT created_at FROM agent_logs WHERE agent_logs.agent_id = agents.id AND agent_logs.level >= 4 ORDER BY id DESC LIMIT 1)"
8
+  end
9
+
10
+  def down
11
+    remove_column :agents, :last_event_at
12
+    remove_column :agents, :last_error_log_at
13
+  end
14
+end

+ 42 - 0
lib/json_serialized_field.rb

@@ -0,0 +1,42 @@
1
+require 'json_with_indifferent_access'
2
+
3
+module JSONSerializedField
4
+  extend ActiveSupport::Concern
5
+
6
+  module ClassMethods
7
+    def json_serialize(*fields)
8
+      fields.each do |field|
9
+        class_eval <<-CODE
10
+          serialize :#{field}, JSONWithIndifferentAccess
11
+
12
+          validate :#{field}_has_no_errors
13
+
14
+          def #{field}=(input)
15
+            @#{field}_assignment_error = false
16
+            case input
17
+              when String
18
+                if input.strip.length == 0
19
+                  self[:#{field}] = ActiveSupport::HashWithIndifferentAccess.new
20
+                else
21
+                  json = JSON.parse(input) rescue nil
22
+                  if json
23
+                    self[:#{field}] = ActiveSupport::HashWithIndifferentAccess.new(json)
24
+                  else
25
+                    @#{field}_assignment_error = "was assigned invalid JSON"
26
+                  end
27
+                end
28
+              when Hash
29
+                self[:#{field}] = ActiveSupport::HashWithIndifferentAccess.new(input)
30
+              else
31
+                @#{field}_assignment_error = "cannot be set to an instance of \#{input.class}"
32
+            end
33
+          end
34
+
35
+          def #{field}_has_no_errors
36
+            errors.add(:#{field}, @#{field}_assignment_error) if @#{field}_assignment_error
37
+          end
38
+        CODE
39
+      end
40
+    end
41
+  end
42
+end

+ 9 - 0
lib/json_with_indifferent_access.rb

@@ -0,0 +1,9 @@
1
+class JSONWithIndifferentAccess
2
+  def self.load(json)
3
+    ActiveSupport::HashWithIndifferentAccess.new(JSON.parse(json || '{}'))
4
+  end
5
+
6
+  def self.dump(hash)
7
+    JSON.dump(hash)
8
+  end
9
+end

+ 0 - 43
lib/serialize_and_symbolize.rb

@@ -1,43 +0,0 @@
1
-module SerializeAndSymbolize
2
-  extend ActiveSupport::Concern
3
-
4
-  module ClassMethods
5
-    def serialize_and_symbolize(*column_names)
6
-      column_names.flatten.uniq.compact.map(&:to_sym).each do |column_name|
7
-        setup_name = "setup_#{column_name}".to_sym
8
-        symbolize_name = "symbolize_#{column_name}".to_sym
9
-        validate_name = "validate_#{column_name}".to_sym
10
-
11
-        serialize column_name
12
-        after_initialize setup_name
13
-        before_validation symbolize_name
14
-        before_save symbolize_name
15
-        validate validate_name
16
-
17
-        class_eval <<-RUBY
18
-          def #{setup_name}
19
-            self[:#{column_name}] ||= {}
20
-          end
21
-
22
-          def #{validate_name}
23
-            # Implement me in your subclass.
24
-          end
25
-
26
-          def #{symbolize_name}
27
-            self.#{column_name} = self[:#{column_name}]
28
-          end
29
-
30
-          def #{column_name}=(data)
31
-            if data.is_a?(String)
32
-              self[:#{column_name}] = JSON.parse(data).recursively_symbolize_keys rescue {}
33
-            elsif data.is_a?(Hash)
34
-              self[:#{column_name}] = data.recursively_symbolize_keys
35
-            else
36
-              self[:#{column_name}] = data
37
-            end
38
-          end
39
-        RUBY
40
-      end
41
-    end
42
-  end
43
-end

+ 0 - 11
lib/utils.rb

@@ -21,17 +21,6 @@ module Utils
21 21
     end
22 22
   end
23 23
 
24
-  def self.recursively_symbolize_keys(object)
25
-    case object
26
-      when Hash
27
-        object.inject({}) {|memo, (k, v)| memo[String === k ? k.to_sym : k] = recursively_symbolize_keys(v); memo }
28
-      when Array
29
-        object.map { |item| recursively_symbolize_keys item }
30
-      else
31
-        object
32
-    end
33
-  end
34
-
35 24
   def self.interpolate_jsonpaths(value, data)
36 25
     value.gsub(/<[^>]+>/).each { |jsonpath|
37 26
       Utils.values_at(data, jsonpath[1..-2]).first.to_s

+ 2 - 2
spec/controllers/agents_controller_spec.rb

@@ -5,7 +5,7 @@ describe AgentsController do
5 5
     {
6 6
         :type => "Agents::WebsiteAgent",
7 7
         :name => "Something",
8
-        :options => agents(:bob_website_agent).options.to_json,
8
+        :options => agents(:bob_website_agent).options,
9 9
         :source_ids => [agents(:bob_weather_agent).id, ""]
10 10
     }.merge(options)
11 11
   end
@@ -23,7 +23,7 @@ describe AgentsController do
23 23
       sign_in users(:bob)
24 24
       post :handle_details_post, :id => agents(:bob_manual_event_agent).to_param, :payload => { :foo => "bar" }
25 25
       JSON.parse(response.body).should == { "success" => true }
26
-      agents(:bob_manual_event_agent).events.last.payload.should == { :foo => "bar" }
26
+      agents(:bob_manual_event_agent).events.last.payload.should == { 'foo' => "bar" }
27 27
     end
28 28
 
29 29
     it "can only be accessed by the Agent's owner" do

+ 3 - 1
spec/controllers/logs_controller_spec.rb

@@ -19,12 +19,14 @@ describe LogsController do
19 19
 
20 20
   describe "DELETE clear" do
21 21
     it "deletes all logs for a specific Agent" do
22
+      agents(:bob_weather_agent).last_error_log_at = 2.hours.ago
22 23
       sign_in users(:bob)
23 24
       lambda {
24 25
         delete :clear, :agent_id => agents(:bob_weather_agent).id
25 26
       }.should change { AgentLog.count }.by(-1 * agents(:bob_weather_agent).logs.count)
26 27
       assigns(:logs).length.should == 0
27
-      agents(:bob_weather_agent).logs.count.should == 0
28
+      agents(:bob_weather_agent).reload.logs.count.should == 0
29
+      agents(:bob_weather_agent).last_error_log_at.should be_nil
28 30
     end
29 31
 
30 32
     it "only deletes logs for an Agent owned by the current user" do

+ 3 - 3
spec/controllers/user_location_updates_controller_spec.rb

@@ -8,7 +8,7 @@ describe UserLocationUpdatesController do
8 8
 
9 9
   it "should create events without requiring login" do
10 10
     post :create, :user_id => users(:bob).to_param, :secret => "my_secret", :longitude => 123, :latitude => 45, :something => "else"
11
-    @agent.events.last.payload.should == { :longitude => "123", :latitude => "45", :something => "else" }
11
+    @agent.events.last.payload.should == { 'longitude' => "123", 'latitude' => "45", 'something' => "else" }
12 12
     @agent.events.last.lat.should == 45
13 13
     @agent.events.last.lng.should == 123
14 14
   end
@@ -18,7 +18,7 @@ describe UserLocationUpdatesController do
18 18
     @jane_agent.save!
19 19
 
20 20
     post :create, :user_id => users(:bob).to_param, :secret => "my_secret", :longitude => 123, :latitude => 45, :something => "else"
21
-    @agent.events.last.payload.should == { :longitude => "123", :latitude => "45", :something => "else" }
21
+    @agent.events.last.payload.should == { 'longitude' => "123", 'latitude' => "45", 'something' => "else" }
22 22
     @jane_agent.events.should be_empty
23 23
   end
24 24
 
@@ -33,7 +33,7 @@ describe UserLocationUpdatesController do
33 33
 
34 34
     lambda {
35 35
       post :create, :user_id => users(:bob).to_param, :secret => "my_secret2", :longitude => 123, :latitude => 45, :something => "else"
36
-      @agent2.events.last.payload.should == { :longitude => "123", :latitude => "45", :something => "else" }
36
+      @agent2.events.last.payload.should == { 'longitude' => "123", 'latitude' => "45", 'something' => "else" }
37 37
     }.should_not change { @agent.events.count }
38 38
   end
39 39
 end

+ 2 - 2
spec/controllers/webhooks_controller_spec.rb

@@ -32,12 +32,12 @@ describe WebhooksController do
32 32
 
33 33
   it "should call receive_webhook" do
34 34
     post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"
35
-    @agent.reload.memory[:webhook_values].should == { :key => "value", :another_key => "5" }
35
+    @agent.reload.memory[:webhook_values].should == { 'key' => "value", 'another_key' => "5" }
36 36
     response.body.should == "success"
37 37
     response.should be_success
38 38
 
39 39
     post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go"
40
-    @agent.reload.memory[:webhook_values].should_not == { :no => "go" }
40
+    @agent.reload.memory[:webhook_values].should_not == { 'no' => "go" }
41 41
     response.body.should == "failure"
42 42
     response.should be_missing
43 43
   end

+ 7 - 7
spec/fixtures/agents.yml

@@ -11,7 +11,7 @@ jane_website_agent:
11 11
                      :title => {:css => "item title", :text => true},
12 12
                      :url => {:css => "item link", :text => true}
13 13
                  }
14
-               }.to_yaml.inspect %>
14
+               }.to_json.inspect %>
15 15
 
16 16
 bob_website_agent:
17 17
   type: Agents::WebsiteAgent
@@ -26,7 +26,7 @@ bob_website_agent:
26 26
                    :url => {:css => "#comic img", :attr => "src"},
27 27
                    :title => {:css => "#comic img", :attr => "title"}
28 28
                  }
29
-               }.to_yaml.inspect %>
29
+               }.to_json.inspect %>
30 30
 
31 31
 bob_weather_agent:
32 32
   type: Agents::WeatherAgent
@@ -38,7 +38,7 @@ bob_weather_agent:
38 38
                  :lat => 37.779329,
39 39
                  :lng => -122.41915,
40 40
                  :api_key => 'test'
41
-               }.to_yaml.inspect %>
41
+               }.to_json.inspect %>
42 42
 
43 43
 jane_weather_agent:
44 44
   type: Agents::WeatherAgent
@@ -50,7 +50,7 @@ jane_weather_agent:
50 50
                  :lat => 37.779329,
51 51
                  :lng => -122.41915,
52 52
                  :api_key => 'test'
53
-               }.to_yaml.inspect %>
53
+               }.to_json.inspect %>
54 54
 
55 55
 jane_rain_notifier_agent:
56 56
   type: Agents::TriggerAgent
@@ -64,7 +64,7 @@ jane_rain_notifier_agent:
64 64
                    :path => "conditions"
65 65
                  }],
66 66
                  :message => "Just so you know, it looks like '<conditions>' tomorrow in <location>"
67
-               }.to_yaml.inspect %>
67
+               }.to_json.inspect %>
68 68
 
69 69
 bob_rain_notifier_agent:
70 70
   type: Agents::TriggerAgent
@@ -78,7 +78,7 @@ bob_rain_notifier_agent:
78 78
                    :path => "conditions"
79 79
                   }],
80 80
                  :message => "Just so you know, it looks like '<conditions>' tomorrow in <location>"
81
-               }.to_yaml.inspect %>
81
+               }.to_json.inspect %>
82 82
 
83 83
 bob_twitter_user_agent:
84 84
   type: Agents::TwitterUserAgent
@@ -91,7 +91,7 @@ bob_twitter_user_agent:
91 91
       :consumer_secret => "---",
92 92
       :oauth_token => "---",
93 93
       :oauth_token_secret => "---"
94
-    }.to_yaml.inspect %>
94
+    }.to_json.inspect %>
95 95
 
96 96
 bob_manual_event_agent:
97 97
   type: Agents::ManualEventAgent

+ 2 - 2
spec/fixtures/events.yml

@@ -1,9 +1,9 @@
1 1
 bob_website_agent_event:
2 2
   user: bob
3 3
   agent: bob_website_agent
4
-  payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_yaml.inspect %>
4
+  payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_json.inspect %>
5 5
 
6 6
 jane_website_agent_event:
7 7
   user: jane
8 8
   agent: jane_website_agent
9
-  payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_yaml.inspect %>
9
+  payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_json.inspect %>

+ 8 - 0
spec/models/agent_log_spec.rb

@@ -67,6 +67,14 @@ describe AgentLog do
67 67
       agents(:jane_website_agent).logs.order("agent_logs.id desc").first.message.should == "message 6"
68 68
       agents(:jane_website_agent).logs.order("agent_logs.id desc").last.message.should == "message 3"
69 69
     end
70
+
71
+    it "updates Agents' last_error_log_at when an error is logged" do
72
+      AgentLog.log_for_agent(agents(:jane_website_agent), "some message", :level => 3, :outbound_event => events(:jane_website_agent_event))
73
+      agents(:jane_website_agent).reload.last_error_log_at.should be_nil
74
+
75
+      AgentLog.log_for_agent(agents(:jane_website_agent), "some message", :level => 4, :outbound_event => events(:jane_website_agent_event))
76
+      agents(:jane_website_agent).reload.last_error_log_at.to_i.should be_within(2).of(Time.now.to_i)
77
+    end
70 78
   end
71 79
 
72 80
   describe "#log_length" do

+ 70 - 2
spec/models/agent_spec.rb

@@ -261,9 +261,51 @@ describe Agent do
261 261
       it "symbolizes memory before validating" do
262 262
         agent = Agents::SomethingSource.new(:name => "something")
263 263
         agent.user = users(:bob)
264
-        agent.memory["bad"] = :hello
264
+        agent.memory["bad"] = 2
265 265
         agent.save
266
-        agent.memory[:bad].should == :hello
266
+        agent.memory[:bad].should == 2
267
+      end
268
+
269
+      it "should work when assigned a hash or JSON string" do
270
+        agent = Agents::SomethingSource.new(:name => "something")
271
+        agent.memory = {}
272
+        agent.memory.should == {}
273
+        agent.memory["foo"].should be_nil
274
+
275
+        agent.memory = ""
276
+        agent.memory["foo"].should be_nil
277
+        agent.memory.should == {}
278
+
279
+        agent.memory = '{"hi": "there"}'
280
+        agent.memory.should == { "hi" => "there" }
281
+
282
+        agent.memory = '{invalid}'
283
+        agent.memory.should == { "hi" => "there" }
284
+        agent.should have(1).errors_on(:memory)
285
+
286
+        agent.memory = "{}"
287
+        agent.memory["foo"].should be_nil
288
+        agent.memory.should == {}
289
+        agent.should have(0).errors_on(:memory)
290
+
291
+        agent.options = "{}"
292
+        agent.options["foo"].should be_nil
293
+        agent.options.should == {}
294
+        agent.should have(0).errors_on(:options)
295
+
296
+        agent.options = '{"hi": 2}'
297
+        agent.options["hi"].should == 2
298
+        agent.should have(0).errors_on(:options)
299
+
300
+        agent.options = '{"hi": wut}'
301
+        agent.options["hi"].should == 2
302
+        agent.should have(1).errors_on(:options)
303
+        agent.errors_on(:options).should include("was assigned invalid JSON")
304
+
305
+        agent.options = 5
306
+        agent.options["hi"].should == 2
307
+        agent.should have(1).errors_on(:options)
308
+        agent.errors_on(:options).should include("cannot be set to an instance of Fixnum")
267 309
       end
268 310
 
269 311
       it "should not allow agents owned by other people" do
@@ -279,6 +321,32 @@ describe Agent do
279 321
     end
280 322
   end
281 323
 
324
+  describe "recent_error_logs?" do
325
+    it "returns true if last_error_log_at is near last_event_at" do
326
+      agent = Agent.new
327
+
328
+      agent.last_error_log_at = 10.minutes.ago
329
+      agent.last_event_at = 10.minutes.ago
330
+      agent.recent_error_logs?.should be_true
331
+
332
+      agent.last_error_log_at = 11.minutes.ago
333
+      agent.last_event_at = 10.minutes.ago
334
+      agent.recent_error_logs?.should be_true
335
+
336
+      agent.last_error_log_at = 5.minutes.ago
337
+      agent.last_event_at = 10.minutes.ago
338
+      agent.recent_error_logs?.should be_true
339
+
340
+      agent.last_error_log_at = 15.minutes.ago
341
+      agent.last_event_at = 10.minutes.ago
342
+      agent.recent_error_logs?.should be_false
343
+
344
+      agent.last_error_log_at = 2.days.ago
345
+      agent.last_event_at = 10.minutes.ago
346
+      agent.recent_error_logs?.should be_false
347
+    end
348
+  end
349
+
282 350
   describe "scopes" do
283 351
     describe "of_type" do
284 352
       it "should accept classes" do

+ 5 - 5
spec/models/agents/digest_email_agent_spec.rb

@@ -19,16 +19,16 @@ describe Agents::DigestEmailAgent do
19 19
     it "queues any payloads it receives" do
20 20
       event1 = Event.new
21 21
       event1.agent = agents(:bob_rain_notifier_agent)
22
-      event1.payload = "Something you should know about"
22
+      event1.payload = { :data => "Something you should know about" }
23 23
       event1.save!
24 24
 
25 25
       event2 = Event.new
26 26
       event2.agent = agents(:bob_weather_agent)
27
-      event2.payload = "Something else you should know about"
27
+      event2.payload = { :data => "Something else you should know about" }
28 28
       event2.save!
29 29
 
30 30
       Agents::DigestEmailAgent.async_receive(@checker.id, [event1.id, event2.id])
31
-      @checker.reload.memory[:queue].should == ["Something you should know about", "Something else you should know about"]
31
+      @checker.reload.memory[:queue].should == [{ 'data' => "Something you should know about" }, { 'data' => "Something else you should know about" }]
32 32
     end
33 33
   end
34 34
 
@@ -37,7 +37,7 @@ describe Agents::DigestEmailAgent do
37 37
       Agents::DigestEmailAgent.async_check(@checker.id)
38 38
       ActionMailer::Base.deliveries.should == []
39 39
 
40
-      @checker.memory[:queue] = ["Something you should know about",
40
+      @checker.memory[:queue] = [{ :data => "Something you should know about" },
41 41
                                  { :title => "Foo", :url => "http://google.com", :bar => 2 },
42 42
                                  { "message" => "hi", :woah => "there" },
43 43
                                  { "test" => 2 }]
@@ -47,7 +47,7 @@ describe Agents::DigestEmailAgent do
47 47
       Agents::DigestEmailAgent.async_check(@checker.id)
48 48
       ActionMailer::Base.deliveries.last.to.should == ["bob@example.com"]
49 49
       ActionMailer::Base.deliveries.last.subject.should == "something interesting"
50
-      get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip.should == "Something you should know about\n\nFoo\n  bar: 2\n  url: http://google.com\n\nhi\n  woah: there\n\nEvent\n  test: 2"
50
+      get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip.should == "Event\n  data: Something you should know about\n\nFoo\n  bar: 2\n  url: http://google.com\n\nhi\n  woah: there\n\nEvent\n  test: 2"
51 51
       @checker.reload.memory[:queue].should be_empty
52 52
     end
53 53
 

+ 4 - 4
spec/models/agents/email_agent_spec.rb

@@ -21,12 +21,12 @@ describe Agents::EmailAgent do
21 21
 
22 22
       event1 = Event.new
23 23
       event1.agent = agents(:bob_rain_notifier_agent)
24
-      event1.payload = "Something you should know about"
24
+      event1.payload = { :data => "Something you should know about" }
25 25
       event1.save!
26 26
 
27 27
       event2 = Event.new
28 28
       event2.agent = agents(:bob_weather_agent)
29
-      event2.payload = "Something else you should know about"
29
+      event2.payload = { :data => "Something else you should know about" }
30 30
       event2.save!
31 31
 
32 32
       Agents::EmailAgent.async_receive(@checker.id, [event1.id])
@@ -35,8 +35,8 @@ describe Agents::EmailAgent do
35 35
       ActionMailer::Base.deliveries.count.should == 2
36 36
       ActionMailer::Base.deliveries.last.to.should == ["bob@example.com"]
37 37
       ActionMailer::Base.deliveries.last.subject.should == "something interesting"
38
-      get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip.should == "Something else you should know about"
39
-      get_message_part(ActionMailer::Base.deliveries.first, /plain/).strip.should == "Something you should know about"
38
+      get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip.should == "Event\n  data: Something else you should know about"
39
+      get_message_part(ActionMailer::Base.deliveries.first, /plain/).strip.should == "Event\n  data: Something you should know about"
40 40
     end
41 41
 
42 42
     it "can receive complex events and send them on" do

+ 162 - 162
spec/models/agents/human_task_agent_spec.rb

@@ -9,8 +9,8 @@ describe Agents::HumanTaskAgent do
9 9
 
10 10
     @event = Event.new
11 11
     @event.agent = agents(:bob_rain_notifier_agent)
12
-    @event.payload = { :foo => { "bar" => { :baz => "a2b" } },
13
-                       :name => "Joe" }
12
+    @event.payload = { 'foo' => { "bar" => { 'baz' => "a2b" } },
13
+                       'name' => "Joe" }
14 14
     @event.id = 345
15 15
 
16 16
     @checker.should be_valid
@@ -18,146 +18,146 @@ describe Agents::HumanTaskAgent do
18 18
 
19 19
   describe "validations" do
20 20
     it "validates that trigger_on is 'schedule' or 'event'" do
21
-      @checker.options[:trigger_on] = "foo"
21
+      @checker.options['trigger_on'] = "foo"
22 22
       @checker.should_not be_valid
23 23
     end
24 24
 
25 25
     it "requires expected_receive_period_in_days when trigger_on is set to 'event'" do
26
-      @checker.options[:trigger_on] = "event"
27
-      @checker.options[:expected_receive_period_in_days] = nil
26
+      @checker.options['trigger_on'] = "event"
27
+      @checker.options['expected_receive_period_in_days'] = nil
28 28
       @checker.should_not be_valid
29
-      @checker.options[:expected_receive_period_in_days] = 2
29
+      @checker.options['expected_receive_period_in_days'] = 2
30 30
       @checker.should be_valid
31 31
     end
32 32
 
33 33
     it "requires a positive submission_period when trigger_on is set to 'schedule'" do
34
-      @checker.options[:trigger_on] = "schedule"
35
-      @checker.options[:submission_period] = nil
34
+      @checker.options['trigger_on'] = "schedule"
35
+      @checker.options['submission_period'] = nil
36 36
       @checker.should_not be_valid
37
-      @checker.options[:submission_period] = 2
37
+      @checker.options['submission_period'] = 2
38 38
       @checker.should be_valid
39 39
     end
40 40
 
41 41
     it "requires a hit.title" do
42
-      @checker.options[:hit][:title] = ""
42
+      @checker.options['hit']['title'] = ""
43 43
       @checker.should_not be_valid
44 44
     end
45 45
 
46 46
     it "requires a hit.description" do
47
-      @checker.options[:hit][:description] = ""
47
+      @checker.options['hit']['description'] = ""
48 48
       @checker.should_not be_valid
49 49
     end
50 50
 
51 51
     it "requires hit.assignments" do
52
-      @checker.options[:hit][:assignments] = ""
52
+      @checker.options['hit']['assignments'] = ""
53 53
       @checker.should_not be_valid
54
-      @checker.options[:hit][:assignments] = 0
54
+      @checker.options['hit']['assignments'] = 0
55 55
       @checker.should_not be_valid
56
-      @checker.options[:hit][:assignments] = "moose"
56
+      @checker.options['hit']['assignments'] = "moose"
57 57
       @checker.should_not be_valid
58
-      @checker.options[:hit][:assignments] = "2"
58
+      @checker.options['hit']['assignments'] = "2"
59 59
       @checker.should be_valid
60 60
     end
61 61
 
62 62
     it "requires hit.questions" do
63
-      old_questions = @checker.options[:hit][:questions]
64
-      @checker.options[:hit][:questions] = nil
63
+      old_questions = @checker.options['hit']['questions']
64
+      @checker.options['hit']['questions'] = nil
65 65
       @checker.should_not be_valid
66
-      @checker.options[:hit][:questions] = []
66
+      @checker.options['hit']['questions'] = []
67 67
       @checker.should_not be_valid
68
-      @checker.options[:hit][:questions] = [old_questions[0]]
68
+      @checker.options['hit']['questions'] = [old_questions[0]]
69 69
       @checker.should be_valid
70 70
     end
71 71
 
72 72
     it "requires that all questions have key, name, required, type, and question" do
73
-      old_questions = @checker.options[:hit][:questions]
74
-      @checker.options[:hit][:questions].first[:key] = ""
73
+      old_questions = @checker.options['hit']['questions']
74
+      @checker.options['hit']['questions'].first['key'] = ""
75 75
       @checker.should_not be_valid
76 76
 
77
-      @checker.options[:hit][:questions] = old_questions
78
-      @checker.options[:hit][:questions].first[:name] = ""
77
+      @checker.options['hit']['questions'] = old_questions
78
+      @checker.options['hit']['questions'].first['name'] = ""
79 79
       @checker.should_not be_valid
80 80
 
81
-      @checker.options[:hit][:questions] = old_questions
82
-      @checker.options[:hit][:questions].first[:required] = nil
81
+      @checker.options['hit']['questions'] = old_questions
82
+      @checker.options['hit']['questions'].first['required'] = nil
83 83
       @checker.should_not be_valid
84 84
 
85
-      @checker.options[:hit][:questions] = old_questions
86
-      @checker.options[:hit][:questions].first[:type] = ""
85
+      @checker.options['hit']['questions'] = old_questions
86
+      @checker.options['hit']['questions'].first['type'] = ""
87 87
       @checker.should_not be_valid
88 88
 
89
-      @checker.options[:hit][:questions] = old_questions
90
-      @checker.options[:hit][:questions].first[:question] = ""
89
+      @checker.options['hit']['questions'] = old_questions
90
+      @checker.options['hit']['questions'].first['question'] = ""
91 91
       @checker.should_not be_valid
92 92
     end
93 93
 
94 94
     it "requires that all questions of type 'selection' have a selections array with keys and text" do
95
-      @checker.options[:hit][:questions][0][:selections] = []
95
+      @checker.options['hit']['questions'][0]['selections'] = []
96 96
       @checker.should_not be_valid
97
-      @checker.options[:hit][:questions][0][:selections] = [{}]
97
+      @checker.options['hit']['questions'][0]['selections'] = [{}]
98 98
       @checker.should_not be_valid
99
-      @checker.options[:hit][:questions][0][:selections] = [{ :key => "", :text => "" }]
99
+      @checker.options['hit']['questions'][0]['selections'] = [{ 'key' => "", 'text' => "" }]
100 100
       @checker.should_not be_valid
101
-      @checker.options[:hit][:questions][0][:selections] = [{ :key => "", :text => "hi" }]
101
+      @checker.options['hit']['questions'][0]['selections'] = [{ 'key' => "", 'text' => "hi" }]
102 102
       @checker.should_not be_valid
103
-      @checker.options[:hit][:questions][0][:selections] = [{ :key => "hi", :text => "" }]
103
+      @checker.options['hit']['questions'][0]['selections'] = [{ 'key' => "hi", 'text' => "" }]
104 104
       @checker.should_not be_valid
105
-      @checker.options[:hit][:questions][0][:selections] = [{ :key => "hi", :text => "hi" }]
105
+      @checker.options['hit']['questions'][0]['selections'] = [{ 'key' => "hi", 'text' => "hi" }]
106 106
       @checker.should be_valid
107
-      @checker.options[:hit][:questions][0][:selections] = [{ :key => "hi", :text => "hi" }, {}]
107
+      @checker.options['hit']['questions'][0]['selections'] = [{ 'key' => "hi", 'text' => "hi" }, {}]
108 108
       @checker.should_not be_valid
109 109
     end
110 110
 
111 111
     it "requires that 'poll_options' be present and populated when 'combination_mode' is set to 'poll'" do
112
-      @checker.options[:combination_mode] = "poll"
112
+      @checker.options['combination_mode'] = "poll"
113 113
       @checker.should_not be_valid
114
-      @checker.options[:poll_options] = {}
114
+      @checker.options['poll_options'] = {}
115 115
       @checker.should_not be_valid
116
-      @checker.options[:poll_options] = { :title => "Take a poll about jokes",
117
-                                          :instructions => "Rank these by how funny they are",
118
-                                          :assignments => 3,
119
-                                          :row_template => "<$.joke>" }
116
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
117
+                                           'instructions' => "Rank these by how funny they are",
118
+                                           'assignments' => 3,
119
+                                           'row_template' => "<$.joke>" }
120 120
       @checker.should be_valid
121
-      @checker.options[:poll_options] = { :instructions => "Rank these by how funny they are",
122
-                                          :assignments => 3,
123
-                                          :row_template => "<$.joke>" }
121
+      @checker.options['poll_options'] = { 'instructions' => "Rank these by how funny they are",
122
+                                           'assignments' => 3,
123
+                                           'row_template' => "<$.joke>" }
124 124
       @checker.should_not be_valid
125
-      @checker.options[:poll_options] = { :title => "Take a poll about jokes",
126
-                                          :assignments => 3,
127
-                                          :row_template => "<$.joke>" }
125
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
126
+                                           'assignments' => 3,
127
+                                           'row_template' => "<$.joke>" }
128 128
       @checker.should_not be_valid
129
-      @checker.options[:poll_options] = { :title => "Take a poll about jokes",
130
-                                          :instructions => "Rank these by how funny they are",
131
-                                          :row_template => "<$.joke>" }
129
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
130
+                                           'instructions' => "Rank these by how funny they are",
131
+                                           'row_template' => "<$.joke>" }
132 132
       @checker.should_not be_valid
133
-      @checker.options[:poll_options] = { :title => "Take a poll about jokes",
134
-                                          :instructions => "Rank these by how funny they are",
135
-                                          :assignments => 3}
133
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
134
+                                           'instructions' => "Rank these by how funny they are",
135
+                                           'assignments' => 3}
136 136
       @checker.should_not be_valid
137 137
     end
138 138
 
139 139
     it "requires that all questions be of type 'selection' when 'combination_mode' is 'take_majority'" do
140
-      @checker.options[:combination_mode] = "take_majority"
140
+      @checker.options['combination_mode'] = "take_majority"
141 141
       @checker.should_not be_valid
142
-      @checker.options[:hit][:questions][1][:type] = "selection"
143
-      @checker.options[:hit][:questions][1][:selections] = @checker.options[:hit][:questions][0][:selections]
142
+      @checker.options['hit']['questions'][1]['type'] = "selection"
143
+      @checker.options['hit']['questions'][1]['selections'] = @checker.options['hit']['questions'][0]['selections']
144 144
       @checker.should be_valid
145 145
     end
146 146
 
147 147
     it "accepts 'take_majority': 'true' for legacy support" do
148
-      @checker.options[:take_majority] = "true"
148
+      @checker.options['take_majority'] = "true"
149 149
       @checker.should_not be_valid
150
-      @checker.options[:hit][:questions][1][:type] = "selection"
151
-      @checker.options[:hit][:questions][1][:selections] = @checker.options[:hit][:questions][0][:selections]
150
+      @checker.options['hit']['questions'][1]['type'] = "selection"
151
+      @checker.options['hit']['questions'][1]['selections'] = @checker.options['hit']['questions'][0]['selections']
152 152
       @checker.should be_valid
153 153
     end
154 154
   end
155 155
 
156 156
   describe "when 'trigger_on' is set to 'schedule'" do
157 157
     before do
158
-      @checker.options[:trigger_on] = "schedule"
159
-      @checker.options[:submission_period] = "2"
160
-      @checker.options.delete(:expected_receive_period_in_days)
158
+      @checker.options['trigger_on'] = "schedule"
159
+      @checker.options['submission_period'] = "2"
160
+      @checker.options.delete('expected_receive_period_in_days')
161 161
     end
162 162
 
163 163
     it "should check for reviewable HITs frequently" do
@@ -187,7 +187,7 @@ describe Agents::HumanTaskAgent do
187 187
 
188 188
   describe "when 'trigger_on' is set to 'event'" do
189 189
     it "should not create HITs during check but should check for reviewable HITs" do
190
-      @checker.options[:submission_period] = "2"
190
+      @checker.options['submission_period'] = "2"
191 191
       now = Time.now
192 192
       stub(Time).now { now }
193 193
       mock(@checker).review_hits.times(3)
@@ -207,9 +207,9 @@ describe Agents::HumanTaskAgent do
207 207
 
208 208
   describe "creating hits" do
209 209
     it "can create HITs based on events, interpolating their values" do
210
-      @checker.options[:hit][:title] = "Hi <.name>"
211
-      @checker.options[:hit][:description] = "Make something for <.name>"
212
-      @checker.options[:hit][:questions][0][:name] = "<.name> Question 1"
210
+      @checker.options['hit']['title'] = "Hi <.name>"
211
+      @checker.options['hit']['description'] = "Make something for <.name>"
212
+      @checker.options['hit']['questions'][0]['name'] = "<.name> Question 1"
213 213
 
214 214
       question_form = nil
215 215
       hitInterface = OpenStruct.new
@@ -219,8 +219,8 @@ describe Agents::HumanTaskAgent do
219 219
 
220 220
       @checker.send :create_basic_hit, @event
221 221
 
222
-      hitInterface.max_assignments.should == @checker.options[:hit][:assignments]
223
-      hitInterface.reward.should == @checker.options[:hit][:reward]
222
+      hitInterface.max_assignments.should == @checker.options['hit']['assignments']
223
+      hitInterface.reward.should == @checker.options['hit']['reward']
224 224
       hitInterface.description.should == "Make something for Joe"
225 225
 
226 226
       xml = question_form.to_xml
@@ -228,18 +228,18 @@ describe Agents::HumanTaskAgent do
228 228
       xml.should include("<Text>Make something for Joe</Text>")
229 229
       xml.should include("<DisplayName>Joe Question 1</DisplayName>")
230 230
 
231
-      @checker.memory[:hits][123][:event_id].should == @event.id
231
+      @checker.memory['hits'][123]['event_id'].should == @event.id
232 232
     end
233 233
 
234 234
     it "works without an event too" do
235
-      @checker.options[:hit][:title] = "Hi <.name>"
235
+      @checker.options['hit']['title'] = "Hi <.name>"
236 236
       hitInterface = OpenStruct.new
237 237
       hitInterface.id = 123
238 238
       mock(hitInterface).question_form(instance_of Agents::HumanTaskAgent::AgentQuestionForm)
239 239
       mock(RTurk::Hit).create(:title => "Hi").yields(hitInterface) { hitInterface }
240 240
       @checker.send :create_basic_hit
241
-      hitInterface.max_assignments.should == @checker.options[:hit][:assignments]
242
-      hitInterface.reward.should == @checker.options[:hit][:reward]
241
+      hitInterface.max_assignments.should == @checker.options['hit']['assignments']
242
+      hitInterface.reward.should == @checker.options['hit']['reward']
243 243
     end
244 244
   end
245 245
 
@@ -289,14 +289,14 @@ describe Agents::HumanTaskAgent do
289 289
     it "should work on multiple HITs" do
290 290
       event2 = Event.new
291 291
       event2.agent = agents(:bob_rain_notifier_agent)
292
-      event2.payload = { :foo2 => { "bar2" => { :baz2 => "a2b2" } },
293
-                          :name2 => "Joe2" }
292
+      event2.payload = { 'foo2' => { "bar2" => { 'baz2' => "a2b2" } },
293
+                         'name2' => "Joe2" }
294 294
       event2.id = 3452
295 295
 
296 296
       # It knows about two HITs from two different events.
297
-      @checker.memory[:hits] = {}
298
-      @checker.memory[:hits][:"JH3132836336DHG"] = { :event_id => @event.id }
299
-      @checker.memory[:hits][:"JH39AA63836DHG"] = { :event_id => event2.id }
297
+      @checker.memory['hits'] = {}
298
+      @checker.memory['hits']["JH3132836336DHG"] = { 'event_id' => @event.id }
299
+      @checker.memory['hits']["JH39AA63836DHG"] = { 'event_id' => event2.id }
300 300
 
301 301
       hit_ids = %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345]
302 302
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { hit_ids } } # It sees 3 HITs.
@@ -309,7 +309,7 @@ describe Agents::HumanTaskAgent do
309 309
     end
310 310
 
311 311
     it "shouldn't do anything if an assignment isn't ready" do
312
-      @checker.memory[:hits] = { :"JH3132836336DHG" => { :event_id => @event.id } }
312
+      @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
313 313
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
314 314
       assignments = [
315 315
         FakeAssignment.new(:status => "Accepted", :answers => {}),
@@ -324,11 +324,11 @@ describe Agents::HumanTaskAgent do
324 324
       @checker.send :review_hits
325 325
 
326 326
       assignments.all? {|a| a.approved == true }.should be_false
327
-      @checker.memory[:hits].should == { :"JH3132836336DHG" => { :event_id => @event.id } }
327
+      @checker.memory['hits'].should == { "JH3132836336DHG" => { 'event_id' => @event.id } }
328 328
     end
329 329
 
330 330
     it "shouldn't do anything if an assignment is missing" do
331
-      @checker.memory[:hits] = { :"JH3132836336DHG" => { :event_id => @event.id } }
331
+      @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
332 332
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
333 333
       assignments = [
334 334
         FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"happy", "feedback"=>"Take 2"})
@@ -342,11 +342,11 @@ describe Agents::HumanTaskAgent do
342 342
       @checker.send :review_hits
343 343
 
344 344
       assignments.all? {|a| a.approved == true }.should be_false
345
-      @checker.memory[:hits].should == { :"JH3132836336DHG" => { :event_id => @event.id } }
345
+      @checker.memory['hits'].should == { "JH3132836336DHG" => { 'event_id' => @event.id } }
346 346
     end
347 347
 
348 348
     it "should create events when all assignments are ready" do
349
-      @checker.memory[:hits] = { :"JH3132836336DHG" => { :event_id => @event.id } }
349
+      @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
350 350
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
351 351
       assignments = [
352 352
         FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"neutral", "feedback"=>""}),
@@ -363,32 +363,32 @@ describe Agents::HumanTaskAgent do
363 363
       assignments.all? {|a| a.approved == true }.should be_true
364 364
       hit.should be_disposed
365 365
 
366
-      @checker.events.last.payload[:answers].should == [
367
-        {:sentiment => "neutral", :feedback => ""},
368
-        {:sentiment => "happy", :feedback => "Take 2"}
366
+      @checker.events.last.payload['answers'].should == [
367
+        {'sentiment' => "neutral", 'feedback' => ""},
368
+        {'sentiment' => "happy", 'feedback' => "Take 2"}
369 369
       ]
370 370
 
371
-      @checker.memory[:hits].should == {}
371
+      @checker.memory['hits'].should == {}
372 372
     end
373 373
 
374 374
     describe "taking majority votes" do
375 375
       before do
376
-        @checker.options[:combination_mode] = "take_majority"
377
-        @checker.memory[:hits] = { :"JH3132836336DHG" => { :event_id => @event.id } }
376
+        @checker.options['combination_mode'] = "take_majority"
377
+        @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
378 378
         mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
379 379
       end
380 380
 
381 381
       it "should take the majority votes of all questions" do
382
-        @checker.options[:hit][:questions][1] = {
383
-          :type => "selection",
384
-          :key => "age_range",
385
-          :name => "Age Range",
386
-          :required => "true",
387
-          :question => "Please select your age range:",
388
-          :selections =>
382
+        @checker.options['hit']['questions'][1] = {
383
+          'type' => "selection",
384
+          'key' => "age_range",
385
+          'name' => "Age Range",
386
+          'required' => "true",
387
+          'question' => "Please select your age range:",
388
+          'selections' =>
389 389
             [
390
-              { :key => "<50", :text => "50 years old or younger" },
391
-              { :key => ">50", :text => "Over 50 years old" }
390
+              { 'key' => "<50", 'text' => "50 years old or younger" },
391
+              { 'key' => ">50", 'text' => "Over 50 years old" }
392 392
             ]
393 393
         }
394 394
 
@@ -407,39 +407,39 @@ describe Agents::HumanTaskAgent do
407 407
 
408 408
         assignments.all? {|a| a.approved == true }.should be_true
409 409
 
410
-        @checker.events.last.payload[:answers].should == [
411
-          { :sentiment => "sad", :age_range => "<50" },
412
-          { :sentiment => "neutral", :age_range => ">50" },
413
-          { :sentiment => "happy", :age_range => ">50" },
414
-          { :sentiment => "happy", :age_range => ">50" }
410
+        @checker.events.last.payload['answers'].should == [
411
+          { 'sentiment' => "sad", 'age_range' => "<50" },
412
+          { 'sentiment' => "neutral", 'age_range' => ">50" },
413
+          { 'sentiment' => "happy", 'age_range' => ">50" },
414
+          { 'sentiment' => "happy", 'age_range' => ">50" }
415 415
         ]
416 416
 
417
-        @checker.events.last.payload[:counts].should == { :sentiment => { :happy => 2, :sad => 1, :neutral => 1 }, :age_range => { :">50" => 3, :"<50" => 1 } }
418
-        @checker.events.last.payload[:majority_answer].should == { :sentiment => "happy", :age_range => ">50" }
419
-        @checker.events.last.payload.should_not have_key(:average_answer)
417
+        @checker.events.last.payload['counts'].should == { 'sentiment' => { 'happy' => 2, 'sad' => 1, 'neutral' => 1 }, 'age_range' => { ">50" => 3, "<50" => 1 } }
418
+        @checker.events.last.payload['majority_answer'].should == { 'sentiment' => "happy", 'age_range' => ">50" }
419
+        @checker.events.last.payload.should_not have_key('average_answer')
420 420
 
421
-        @checker.memory[:hits].should == {}
421
+        @checker.memory['hits'].should == {}
422 422
       end
423 423
 
424 424
       it "should also provide an average answer when all questions are numeric" do
425 425
         # it should accept 'take_majority': 'true' as well for legacy support.  Demonstrating that here.
426 426
         @checker.options.delete :combination_mode
427
-        @checker.options[:take_majority] = "true"
427
+        @checker.options['take_majority'] = "true"
428 428
 
429
-        @checker.options[:hit][:questions] = [
429
+        @checker.options['hit']['questions'] = [
430 430
           {
431
-            :type => "selection",
432
-            :key => "rating",
433
-            :name => "Rating",
434
-            :required => "true",
435
-            :question => "Please select a rating:",
436
-            :selections =>
431
+            'type' => "selection",
432
+            'key' => "rating",
433
+            'name' => "Rating",
434
+            'required' => "true",
435
+            'question' => "Please select a rating:",
436
+            'selections' =>
437 437
               [
438
-                { :key => "1", :text => "One" },
439
-                { :key => "2", :text => "Two" },
440
-                { :key => "3", :text => "Three" },
441
-                { :key => "4", :text => "Four" },
442
-                { :key => "5.1", :text => "Five Point One" }
438
+                { 'key' => "1", 'text' => "One" },
439
+                { 'key' => "2", 'text' => "Two" },
440
+                { 'key' => "3", 'text' => "Three" },
441
+                { 'key' => "4", 'text' => "Four" },
442
+                { 'key' => "5.1", 'text' => "Five Point One" }
443 443
               ]
444 444
           }
445 445
         ]
@@ -460,37 +460,37 @@ describe Agents::HumanTaskAgent do
460 460
 
461 461
         assignments.all? {|a| a.approved == true }.should be_true
462 462
 
463
-        @checker.events.last.payload[:answers].should == [
464
-          { :rating => "1" },
465
-          { :rating => "3" },
466
-          { :rating => "5.1" },
467
-          { :rating => "2" },
468
-          { :rating => "2" }
463
+        @checker.events.last.payload['answers'].should == [
464
+          { 'rating' => "1" },
465
+          { 'rating' => "3" },
466
+          { 'rating' => "5.1" },
467
+          { 'rating' => "2" },
468
+          { 'rating' => "2" }
469 469
         ]
470 470
 
471
-        @checker.events.last.payload[:counts].should == { :rating => { :"1" => 1, :"2" => 2, :"3" => 1, :"4" => 0, :"5.1" => 1 } }
472
-        @checker.events.last.payload[:majority_answer].should == { :rating => "2" }
473
-        @checker.events.last.payload[:average_answer].should == { :rating => (1 + 2 + 2 + 3 + 5.1) / 5.0 }
471
+        @checker.events.last.payload['counts'].should == { 'rating' => { "1" => 1, "2" => 2, "3" => 1, "4" => 0, "5.1" => 1 } }
472
+        @checker.events.last.payload['majority_answer'].should == { 'rating' => "2" }
473
+        @checker.events.last.payload['average_answer'].should == { 'rating' => (1 + 2 + 2 + 3 + 5.1) / 5.0 }
474 474
 
475
-        @checker.memory[:hits].should == {}
475
+        @checker.memory['hits'].should == {}
476 476
       end
477 477
     end
478 478
 
479 479
     describe "creating and reviewing polls" do
480 480
       before do
481
-        @checker.options[:combination_mode] = "poll"
482
-        @checker.options[:poll_options] = {
483
-          :title => "Hi!",
484
-          :instructions => "hello!",
485
-          :assignments => 2,
486
-          :row_template => "This is <.sentiment>"
481
+        @checker.options['combination_mode'] = "poll"
482
+        @checker.options['poll_options'] = {
483
+          'title' => "Hi!",
484
+          'instructions' => "hello!",
485
+          'assignments' => 2,
486
+          'row_template' => "This is <.sentiment>"
487 487
         }
488 488
         @event.save!
489 489
         mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
490 490
       end
491 491
 
492 492
       it "creates a poll using the row_template, message, and correct number of assignments" do
493
-        @checker.memory[:hits] = { :"JH3132836336DHG" => { :event_id => @event.id } }
493
+        @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
494 494
 
495 495
         # Mock out the HIT's submitted assignments.
496 496
         assignments = [
@@ -502,7 +502,7 @@ describe Agents::HumanTaskAgent do
502 502
         hit = FakeHit.new(:max_assignments => 4, :assignments => assignments)
503 503
         mock(RTurk::Hit).new("JH3132836336DHG") { hit }
504 504
 
505
-        @checker.memory[:hits][:"JH3132836336DHG"].should be_present
505
+        @checker.memory['hits']["JH3132836336DHG"].should be_present
506 506
 
507 507
         # Setup mocks for HIT creation
508 508
 
@@ -525,8 +525,8 @@ describe Agents::HumanTaskAgent do
525 525
 
526 526
         # it creates a new HIT for the poll
527 527
 
528
-        hitInterface.max_assignments.should == @checker.options[:poll_options][:assignments]
529
-        hitInterface.description.should == @checker.options[:poll_options][:instructions]
528
+        hitInterface.max_assignments.should == @checker.options['poll_options']['assignments']
529
+        hitInterface.description.should == @checker.options['poll_options']['instructions']
530 530
 
531 531
         xml = question_form.to_xml
532 532
         xml.should include("<Text>This is happy</Text>")
@@ -535,28 +535,28 @@ describe Agents::HumanTaskAgent do
535 535
 
536 536
         @checker.save
537 537
         @checker.reload
538
-        @checker.memory[:hits][:"JH3132836336DHG"].should_not be_present
539
-        @checker.memory[:hits][:"JH39AA63836DH12345"].should be_present
540
-        @checker.memory[:hits][:"JH39AA63836DH12345"][:event_id].should == @event.id
541
-        @checker.memory[:hits][:"JH39AA63836DH12345"][:type].should == :poll
542
-        @checker.memory[:hits][:"JH39AA63836DH12345"][:original_hit].should == "JH3132836336DHG"
543
-        @checker.memory[:hits][:"JH39AA63836DH12345"][:answers].length.should == 4
538
+        @checker.memory['hits']["JH3132836336DHG"].should_not be_present
539
+        @checker.memory['hits']["JH39AA63836DH12345"].should be_present
540
+        @checker.memory['hits']["JH39AA63836DH12345"]['event_id'].should == @event.id
541
+        @checker.memory['hits']["JH39AA63836DH12345"]['type'].should == "poll"
542
+        @checker.memory['hits']["JH39AA63836DH12345"]['original_hit'].should == "JH3132836336DHG"
543
+        @checker.memory['hits']["JH39AA63836DH12345"]['answers'].length.should == 4
544 544
       end
545 545
 
546 546
       it "emits an event when all poll results are in, containing the data from the best answer, plus all others" do
547 547
         original_answers = [
548
-          {:sentiment => "sad",     :feedback => "This is my feedback 1"},
549
-          {:sentiment => "neutral", :feedback => "This is my feedback 2"},
550
-          {:sentiment => "happy",   :feedback => "This is my feedback 3"},
551
-          {:sentiment => "happy",   :feedback => "This is my feedback 4"}
548
+          { 'sentiment' => "sad",     'feedback' => "This is my feedback 1"},
549
+          { 'sentiment' => "neutral", 'feedback' => "This is my feedback 2"},
550
+          { 'sentiment' => "happy",   'feedback' => "This is my feedback 3"},
551
+          { 'sentiment' => "happy",   'feedback' => "This is my feedback 4"}
552 552
         ]
553 553
 
554
-        @checker.memory[:hits] = {
555
-          :JH39AA63836DH12345 => {
556
-            :type => :poll,
557
-            :original_hit => "JH3132836336DHG",
558
-            :answers => original_answers,
559
-            :event_id => 345
554
+        @checker.memory['hits'] = {
555
+          'JH39AA63836DH12345' => {
556
+            'type' => 'poll',
557
+            'original_hit' => "JH3132836336DHG",
558
+            'answers' => original_answers,
559
+            'event_id' => 345
560 560
           }
561 561
         }
562 562
 
@@ -568,7 +568,7 @@ describe Agents::HumanTaskAgent do
568 568
         hit = FakeHit.new(:max_assignments => 2, :assignments => assignments)
569 569
         mock(RTurk::Hit).new("JH39AA63836DH12345") { hit }
570 570
 
571
-        @checker.memory[:hits][:"JH39AA63836DH12345"].should be_present
571
+        @checker.memory['hits']["JH39AA63836DH12345"].should be_present
572 572
 
573 573
         lambda {
574 574
           @checker.send :review_hits
@@ -576,17 +576,17 @@ describe Agents::HumanTaskAgent do
576 576
 
577 577
         # It emits an event
578 578
 
579
-        @checker.events.last.payload[:answers].should == original_answers
580
-        @checker.events.last.payload[:poll].should == [{:"1" => "2", :"2" => "5", :"3" => "3", :"4" => "2"}, {:"1" => "3", :"2" => "4", :"3" => "1", :"4" => "4"}]
581
-        @checker.events.last.payload[:best_answer].should == {:sentiment => "neutral", :feedback => "This is my feedback 2"}
579
+        @checker.events.last.payload['answers'].should == original_answers
580
+        @checker.events.last.payload['poll'].should == [{"1" => "2", "2" => "5", "3" => "3", "4" => "2"}, {"1" => "3", "2" => "4", "3" => "1", "4" => "4"}]
581
+        @checker.events.last.payload['best_answer'].should == {'sentiment' => "neutral", 'feedback' => "This is my feedback 2"}
582 582
 
583 583
         # it approves the existing assignments
584 584
 
585 585
         assignments.all? {|a| a.approved == true }.should be_true
586 586
         hit.should be_disposed
587 587
 
588
-        @checker.memory[:hits].should be_empty
588
+        @checker.memory['hits'].should be_empty
589 589
       end
590 590
     end
591 591
   end
592
-end
592
+end

+ 29 - 29
spec/models/agents/peak_detector_agent_spec.rb

@@ -3,12 +3,12 @@ require 'spec_helper'
3 3
 describe Agents::PeakDetectorAgent do
4 4
   before do
5 5
     @valid_params = {
6
-        :name => "my peak detector agent",
7
-        :options => {
8
-            :expected_receive_period_in_days => "2",
9
-            :group_by_path => "filter",
10
-            :value_path => "count",
11
-            :message => "A peak was found"
6
+        'name' => "my peak detector agent",
7
+        'options' => {
8
+          'expected_receive_period_in_days' => "2",
9
+          'group_by_path' => "filter",
10
+          'value_path' => "count",
11
+          'message' => "A peak was found"
12 12
         }
13 13
     }
14 14
 
@@ -19,54 +19,54 @@ describe Agents::PeakDetectorAgent do
19 19
 
20 20
   describe "#receive" do
21 21
     it "tracks and groups by the group_by_path" do
22
-      events = build_events(:keys => [:count, :filter],
22
+      events = build_events(:keys => ['count', 'filter'],
23 23
                             :values => [[1, "something"], [2, "something"], [3, "else"]])
24 24
       @agent.receive events
25
-      @agent.memory[:data][:something].map(&:first).should == [1, 2]
26
-      @agent.memory[:data][:something].last.last.should be_within(10).of((100 - 1).hours.ago.to_i)
27
-      @agent.memory[:data][:else].first.first.should == 3
28
-      @agent.memory[:data][:else].first.last.should be_within(10).of((100 - 2).hours.ago.to_i)
25
+      @agent.memory['data']['something'].map(&:first).should == [1, 2]
26
+      @agent.memory['data']['something'].last.last.should be_within(10).of((100 - 1).hours.ago.to_i)
27
+      @agent.memory['data']['else'].first.first.should == 3
28
+      @agent.memory['data']['else'].first.last.should be_within(10).of((100 - 2).hours.ago.to_i)
29 29
     end
30 30
 
31 31
     it "works without a group_by_path as well" do
32
-      @agent.options[:group_by_path] = ""
33
-      events = build_events(:keys => [:count], :values => [[1], [2]])
32
+      @agent.options['group_by_path'] = ""
33
+      events = build_events(:keys => ['count'], :values => [[1], [2]])
34 34
       @agent.receive events
35
-      @agent.memory[:data][:no_group].map(&:first).should == [1, 2]
35
+      @agent.memory['data']['no_group'].map(&:first).should == [1, 2]
36 36
     end
37 37
 
38 38
     it "keeps a rolling window of data" do
39
-      @agent.options[:window_duration_in_days] = 5/24.0
40
-      @agent.receive build_events(:keys => [:count],
39
+      @agent.options['window_duration_in_days'] = 5/24.0
40
+      @agent.receive build_events(:keys => ['count'],
41 41
                                   :values => [1, 2, 3, 4, 5, 6, 7, 8].map {|i| [i]},
42
-                                  :pattern => { :filter => "something" })
43
-      @agent.memory[:data][:something].map(&:first).should == [4, 5, 6, 7, 8]
42
+                                  :pattern => { 'filter' => "something" })
43
+      @agent.memory['data']['something'].map(&:first).should == [4, 5, 6, 7, 8]
44 44
     end
45 45
 
46 46
     it "finds peaks" do
47
-      build_events(:keys => [:count],
47
+      build_events(:keys => ['count'],
48 48
                    :values => [5, 6,
49 49
                                4, 5,
50 50
                                4, 5,
51 51
                                15, 11, # peak
52 52
                                8, 50, # ignored because it's too close to the first peak
53 53
                                4, 5].map {|i| [i]},
54
-                   :pattern => { :filter => "something" }).each.with_index do |event, index|
54
+                   :pattern => { 'filter' => "something" }).each.with_index do |event, index|
55 55
         lambda {
56 56
           @agent.receive([event])
57 57
         }.should change { @agent.events.count }.by( index == 6 ? 1 : 0 )
58 58
       end
59 59
 
60
-      @agent.events.last.payload[:peak].should == 15.0
61
-      @agent.memory[:peaks][:something].length.should == 1
60
+      @agent.events.last.payload['peak'].should == 15.0
61
+      @agent.memory['peaks']['something'].length.should == 1
62 62
     end
63 63
 
64 64
     it "keeps a rolling window of peaks" do
65
-      @agent.options[:min_peak_spacing_in_days] = 1/24.0
66
-      @agent.receive build_events(:keys => [:count],
65
+      @agent.options['min_peak_spacing_in_days'] = 1/24.0
66
+      @agent.receive build_events(:keys => ['count'],
67 67
                                   :values => [1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 10, 1].map {|i| [i]},
68
-                                  :pattern => { :filter => "something" })
69
-      @agent.memory[:peaks][:something].length.should == 2
68
+                                  :pattern => { 'filter' => "something" })
69
+      @agent.memory['peaks']['something'].length.should == 2
70 70
     end
71 71
   end
72 72
 
@@ -76,17 +76,17 @@ describe Agents::PeakDetectorAgent do
76 76
     end
77 77
 
78 78
     it "should validate presence of message" do
79
-      @agent.options[:message] = nil
79
+      @agent.options['message'] = nil
80 80
       @agent.should_not be_valid
81 81
     end
82 82
 
83 83
     it "should validate presence of expected_receive_period_in_days" do
84
-      @agent.options[:expected_receive_period_in_days] = ""
84
+      @agent.options['expected_receive_period_in_days'] = ""
85 85
       @agent.should_not be_valid
86 86
     end
87 87
 
88 88
     it "should validate presence of value_path" do
89
-      @agent.options[:value_path] = ""
89
+      @agent.options['value_path'] = ""
90 90
       @agent.should_not be_valid
91 91
     end
92 92
   end

+ 1 - 1
spec/models/agents/sentiment_agent_spec.rb

@@ -53,7 +53,7 @@ describe Agents::SentimentAgent do
53 53
         it "checks if content key is working fine" do
54 54
             @checker.receive([@event])
55 55
             Event.last.payload[:content].should == "value1"
56
-            Event.last.payload[:original_event].should == {:message => "value1"}
56
+            Event.last.payload[:original_event].should == { 'message' => "value1" }
57 57
         end
58 58
         it "should handle multiple events" do
59 59
             event1 = Event.new

+ 53 - 53
spec/models/agents/trigger_agent_spec.rb

@@ -3,16 +3,16 @@ require 'spec_helper'
3 3
 describe Agents::TriggerAgent do
4 4
   before do
5 5
     @valid_params = {
6
-        :name => "my trigger agent",
7
-        :options => {
8
-            :expected_receive_period_in_days => 2,
9
-            :rules => [{
10
-                           :type => "regex",
11
-                           'value' => "a\\db",
12
-                           :path => "foo.bar.baz",
13
-                       }],
14
-            :message => "I saw '<foo.bar.baz>' from <name>"
15
-        }
6
+      'name' => "my trigger agent",
7
+      'options' => {
8
+        'expected_receive_period_in_days' => 2,
9
+        'rules' => [{
10
+                      'type' => "regex",
11
+                      'value' => "a\\db",
12
+                      'path' => "foo.bar.baz",
13
+                    }],
14
+        'message' => "I saw '<foo.bar.baz>' from <name>"
15
+      }
16 16
     }
17 17
 
18 18
     @checker = Agents::TriggerAgent.new(@valid_params)
@@ -21,8 +21,8 @@ describe Agents::TriggerAgent do
21 21
 
22 22
     @event = Event.new
23 23
     @event.agent = agents(:bob_rain_notifier_agent)
24
-    @event.payload = { :foo => { "bar" => { :baz => "a2b" }},
25
-                       :name => "Joe" }
24
+    @event.payload = { 'foo' => { "bar" => { 'baz' => "a2b" }},
25
+                       'name' => "Joe" }
26 26
   end
27 27
 
28 28
   describe "validation" do
@@ -31,22 +31,22 @@ describe Agents::TriggerAgent do
31 31
     end
32 32
 
33 33
     it "should validate presence of options" do
34
-      @checker.options[:message] = nil
34
+      @checker.options['message'] = nil
35 35
       @checker.should_not be_valid
36 36
     end
37 37
 
38 38
     it "should validate the three fields in each rule" do
39
-      @checker.options[:rules] << { :path => "foo", :type => "fake", :value => "6" }
39
+      @checker.options['rules'] << { 'path' => "foo", 'type' => "fake", 'value' => "6" }
40 40
       @checker.should_not be_valid
41
-      @checker.options[:rules].last[:type] = "field>=value"
41
+      @checker.options['rules'].last['type'] = "field>=value"
42 42
       @checker.should be_valid
43
-      @checker.options[:rules].last.delete(:value)
43
+      @checker.options['rules'].last.delete('value')
44 44
       @checker.should_not be_valid
45 45
     end
46 46
   end
47 47
 
48 48
   describe "#working?" do
49
-    it "checks to see if the Agent has received any events in the last :expected_receive_period_in_days days" do
49
+    it "checks to see if the Agent has received any events in the last 'expected_receive_period_in_days' days" do
50 50
       @event.save!
51 51
 
52 52
       @checker.should_not be_working # no events have ever been received
@@ -60,30 +60,30 @@ describe Agents::TriggerAgent do
60 60
 
61 61
   describe "#receive" do
62 62
     it "handles regex" do
63
-      @event.payload[:foo]["bar"][:baz] = "a222b"
63
+      @event.payload['foo']['bar']['baz'] = "a222b"
64 64
       lambda {
65 65
         @checker.receive([@event])
66 66
       }.should_not change { Event.count }
67 67
 
68
-      @event.payload[:foo]["bar"][:baz] = "a2b"
68
+      @event.payload['foo']['bar']['baz'] = "a2b"
69 69
       lambda {
70 70
         @checker.receive([@event])
71 71
       }.should change { Event.count }.by(1)
72 72
     end
73 73
 
74 74
     it "handles negated regex" do
75
-      @event.payload[:foo]["bar"][:baz] = "a2b"
76
-      @checker.options[:rules][0] = {
77
-                                      :type => "!regex",
78
-                                      :value => "a\\db",
79
-                                      :path => "foo.bar.baz",
80
-                                    }
75
+      @event.payload['foo']['bar']['baz'] = "a2b"
76
+      @checker.options['rules'][0] = {
77
+        'type' => "!regex",
78
+        'value' => "a\\db",
79
+        'path' => "foo.bar.baz",
80
+      }
81 81
 
82 82
       lambda {
83 83
         @checker.receive([@event])
84 84
       }.should_not change { Event.count }
85 85
 
86
-      @event.payload[:foo]["bar"][:baz] = "a22b"
86
+      @event.payload['foo']['bar']['baz'] = "a22b"
87 87
       lambda {
88 88
         @checker.receive([@event])
89 89
       }.should change { Event.count }.by(1)
@@ -91,49 +91,49 @@ describe Agents::TriggerAgent do
91 91
 
92 92
     it "puts can extract values into the message based on paths" do
93 93
       @checker.receive([@event])
94
-      Event.last.payload[:message].should == "I saw 'a2b' from Joe"
94
+      Event.last.payload['message'].should == "I saw 'a2b' from Joe"
95 95
     end
96 96
 
97 97
     it "handles numerical comparisons" do
98
-      @event.payload[:foo]["bar"][:baz] = "5"
99
-      @checker.options[:rules].first[:value] = 6
100
-      @checker.options[:rules].first[:type] = "field<value"
98
+      @event.payload['foo']['bar']['baz'] = "5"
99
+      @checker.options['rules'].first['value'] = 6
100
+      @checker.options['rules'].first['type'] = "field<value"
101 101
 
102 102
       lambda {
103 103
         @checker.receive([@event])
104 104
       }.should change { Event.count }.by(1)
105 105
 
106
-      @checker.options[:rules].first[:value] = 3
106
+      @checker.options['rules'].first['value'] = 3
107 107
       lambda {
108 108
         @checker.receive([@event])
109 109
       }.should_not change { Event.count }
110 110
     end
111 111
 
112 112
     it "handles exact comparisons" do
113
-      @event.payload[:foo]["bar"][:baz] = "hello world"
114
-      @checker.options[:rules].first[:type] = "field==value"
113
+      @event.payload['foo']['bar']['baz'] = "hello world"
114
+      @checker.options['rules'].first['type'] = "field==value"
115 115
 
116
-      @checker.options[:rules].first[:value] = "hello there"
116
+      @checker.options['rules'].first['value'] = "hello there"
117 117
       lambda {
118 118
         @checker.receive([@event])
119 119
       }.should_not change { Event.count }
120 120
 
121
-      @checker.options[:rules].first[:value] = "hello world"
121
+      @checker.options['rules'].first['value'] = "hello world"
122 122
       lambda {
123 123
         @checker.receive([@event])
124 124
       }.should change { Event.count }.by(1)
125 125
     end
126 126
 
127 127
     it "handles negated comparisons" do
128
-      @event.payload[:foo]["bar"][:baz] = "hello world"
129
-      @checker.options[:rules].first[:type] = "field!=value"
130
-      @checker.options[:rules].first[:value] = "hello world"
128
+      @event.payload['foo']['bar']['baz'] = "hello world"
129
+      @checker.options['rules'].first['type'] = "field!=value"
130
+      @checker.options['rules'].first['value'] = "hello world"
131 131
 
132 132
       lambda {
133 133
         @checker.receive([@event])
134 134
       }.should_not change { Event.count }
135 135
 
136
-      @checker.options[:rules].first[:value] = "hello there"
136
+      @checker.options['rules'].first['value'] = "hello there"
137 137
 
138 138
       lambda {
139 139
         @checker.receive([@event])
@@ -141,20 +141,20 @@ describe Agents::TriggerAgent do
141 141
     end
142 142
 
143 143
     it "does fine without dots in the path" do
144
-      @event.payload = { :hello => "world" }
145
-      @checker.options[:rules].first[:type] = "field==value"
146
-      @checker.options[:rules].first[:path] = "hello"
147
-      @checker.options[:rules].first[:value] = "world"
144
+      @event.payload = { 'hello' => "world" }
145
+      @checker.options['rules'].first['type'] = "field==value"
146
+      @checker.options['rules'].first['path'] = "hello"
147
+      @checker.options['rules'].first['value'] = "world"
148 148
       lambda {
149 149
         @checker.receive([@event])
150 150
       }.should change { Event.count }.by(1)
151 151
 
152
-      @checker.options[:rules].first[:path] = "foo"
152
+      @checker.options['rules'].first['path'] = "foo"
153 153
       lambda {
154 154
         @checker.receive([@event])
155 155
       }.should_not change { Event.count }
156 156
 
157
-      @checker.options[:rules].first[:value] = "hi"
157
+      @checker.options['rules'].first['value'] = "hi"
158 158
       lambda {
159 159
         @checker.receive([@event])
160 160
       }.should_not change { Event.count }
@@ -163,11 +163,11 @@ describe Agents::TriggerAgent do
163 163
     it "handles multiple events" do
164 164
       event2 = Event.new
165 165
       event2.agent = agents(:bob_weather_agent)
166
-      event2.payload = { :foo => { "bar" => { :baz => "a2b" }}}
166
+      event2.payload = { 'foo' => { 'bar' => { 'baz' => "a2b" }}}
167 167
 
168 168
       event3 = Event.new
169 169
       event3.agent = agents(:bob_weather_agent)
170
-      event3.payload = { :foo => { "bar" => { :baz => "a222b" }}}
170
+      event3.payload = { 'foo' => { 'bar' => { 'baz' => "a222b" }}}
171 171
 
172 172
       lambda {
173 173
         @checker.receive([@event, event2, event3])
@@ -175,19 +175,19 @@ describe Agents::TriggerAgent do
175 175
     end
176 176
 
177 177
     it "handles ANDing rules together" do
178
-      @checker.options[:rules] << {
179
-          :type => "field>=value",
180
-          :value => "4",
181
-          :path => "foo.bing"
178
+      @checker.options['rules'] << {
179
+        'type' => "field>=value",
180
+        'value' => "4",
181
+        'path' => "foo.bing"
182 182
       }
183 183
 
184
-      @event.payload[:foo]["bing"] = "5"
184
+      @event.payload['foo']["bing"] = "5"
185 185
 
186 186
       lambda {
187 187
         @checker.receive([@event])
188 188
       }.should change { Event.count }.by(1)
189 189
 
190
-      @checker.options[:rules].last[:value] = 6
190
+      @checker.options['rules'].last['value'] = 6
191 191
       lambda {
192 192
         @checker.receive([@event])
193 193
       }.should_not change { Event.count }

+ 7 - 7
spec/models/agents/twitter_stream_agent_spec.rb

@@ -53,7 +53,7 @@ describe Agents::TwitterStreamAgent do
53 53
         @agent.memory[:filter_counts] = {:keyword1 => 2, :keyword2 => 3, :keyword3 => 4}
54 54
         @agent.save!
55 55
         @agent.process_tweet('keyword1', {:text => "something", :user => {:name => "Mr. Someone"}})
56
-        @agent.reload.memory[:filter_counts].should == {:keyword1 => 3, :keyword2 => 3}
56
+        @agent.reload.memory[:filter_counts].should == { 'keyword1' => 3, 'keyword2' => 3 }
57 57
       end
58 58
     end
59 59
 
@@ -64,9 +64,9 @@ describe Agents::TwitterStreamAgent do
64 64
         }.should change { @agent.events.count }.by(1)
65 65
 
66 66
         @agent.events.last.payload.should == {
67
-          :filter => 'keyword1',
68
-          :text => "something",
69
-          :user => {:name => "Mr. Someone"}
67
+          'filter' => 'keyword1',
68
+          'text' => "something",
69
+          'user' => { 'name' => "Mr. Someone" }
70 70
         }
71 71
       end
72 72
 
@@ -79,9 +79,9 @@ describe Agents::TwitterStreamAgent do
79 79
         }.should change { @agent.events.count }.by(1)
80 80
 
81 81
         @agent.events.last.payload.should == {
82
-          :filter => 'keyword1-1',
83
-          :text => "something",
84
-          :user => {:name => "Mr. Someone"}
82
+          'filter' => 'keyword1-1',
83
+          'text' => "something",
84
+          'user' => { 'name' => "Mr. Someone" }
85 85
         }
86 86
       end
87 87
     end

+ 4 - 6
spec/models/agents/webhook_agent_spec.rb

@@ -3,29 +3,27 @@ require 'spec_helper'
3 3
 describe Agents::WebhookAgent do
4 4
   let(:agent) do
5 5
     _agent = Agents::WebhookAgent.new(:name => 'webhook',
6
-             :options => {:secret => :foobar, :payload_path => '$'})
6
+                                      :options => { 'secret' => 'foobar', 'payload_path' => 'payload' })
7 7
     _agent.user = users(:bob)
8 8
     _agent.save!
9 9
     _agent
10 10
   end
11 11
   let(:payload) { {'some' => 'info'} }
12 12
 
13
-  after { agent.destroy }
14
-
15 13
   describe 'receive_webhook' do
16 14
     it 'should create event if secret matches' do
17 15
       out = nil
18 16
       lambda {
19
-        out = agent.receive_webhook({:secret => :foobar, :payload => payload})
17
+        out = agent.receive_webhook('secret' => 'foobar', 'payload' => payload)
20 18
       }.should change { Event.count }.by(1)
21 19
       out.should eq(['Event Created', 201])
22
-      Event.last.payload.should eq([{'payload' => payload}])
20
+      Event.last.payload.should eq(payload)
23 21
     end
24 22
 
25 23
     it 'should not create event if secrets dont match' do
26 24
       out = nil
27 25
       lambda {
28
-        out = agent.receive_webhook({:secret => :bazbat, :payload => payload})
26
+        out = agent.receive_webhook('secret' => 'bazbat', 'payload' => payload)
29 27
       }.should change { Event.count }.by(0)
30 28
       out.should eq(['Not Authorized', 401])
31 29
     end

+ 9 - 5
spec/models/agents/website_agent_spec.rb

@@ -44,18 +44,22 @@ describe Agents::WebsiteAgent do
44 44
 
45 45
   describe '#working?' do
46 46
     it 'checks if events have been received within the expected receive period' do
47
+      stubbed_time = Time.now
48
+      stub(Time).now { stubbed_time }
49
+
47 50
       @checker.should_not be_working # No events created
48 51
       @checker.check
49 52
       @checker.reload.should be_working # Just created events
50 53
 
51 54
       @checker.error "oh no!"
52
-      @checker.reload.should_not be_working # The most recent log is an error
55
+      @checker.reload.should_not be_working # There is a recent error
53 56
 
54
-      @checker.log "ok now"
55
-      @checker.reload.should be_working # The most recent log is no longer an error
57
+      stubbed_time = 20.minutes.from_now
58
+      @checker.events.delete_all
59
+      @checker.check
60
+      @checker.reload.should be_working # There is a newer event now
56 61
 
57
-      two_days_from_now = 2.days.from_now
58
-      stub(Time).now { two_days_from_now }
62
+      stubbed_time = 2.days.from_now
59 63
       @checker.reload.should_not be_working # Two days have passed without a new event having been created
60 64
     end
61 65
   end