conversational 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/.gitignore +8 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.markdown +239 -0
  4. data/Rakefile +38 -0
  5. data/VERSION +1 -0
  6. data/conversational.gemspec +78 -0
  7. data/features/configure_blank_topic.feature +9 -0
  8. data/features/configure_exclusion_conversations.feature +20 -0
  9. data/features/configure_unknown_topic.feature +9 -0
  10. data/features/find_existing_conversation.feature +21 -0
  11. data/features/find_or_create_with.feature +33 -0
  12. data/features/retrieve_conversation_details.feature +13 -0
  13. data/features/step_definitions/conversation_steps.rb +60 -0
  14. data/features/step_definitions/pickle_steps.rb +87 -0
  15. data/features/support/conversation.rb +2 -0
  16. data/features/support/email_spec.rb +1 -0
  17. data/features/support/env.rb +58 -0
  18. data/features/support/mail.rb +6 -0
  19. data/features/support/paths.rb +33 -0
  20. data/features/support/pickle.rb +24 -0
  21. data/features/support/sample_conversation.rb +3 -0
  22. data/lib/conversational/active_record_additions.rb +121 -0
  23. data/lib/conversational/conversation.rb +188 -0
  24. data/lib/conversational/conversation_definition.rb +86 -0
  25. data/lib/conversational.rb +4 -0
  26. data/lib/generators/conversational/migration/USAGE +5 -0
  27. data/lib/generators/conversational/migration/migration_generator.rb +23 -0
  28. data/lib/generators/conversational/migration/templates/migration.rb +14 -0
  29. data/lib/generators/conversational/skeleton/USAGE +6 -0
  30. data/lib/generators/conversational/skeleton/skeleton_generator.rb +17 -0
  31. data/lib/generators/conversational/skeleton/templates/conversation.rb +3 -0
  32. data/spec/active_record_additions_spec.rb +120 -0
  33. data/spec/conversation_definition_spec.rb +248 -0
  34. data/spec/conversation_spec.rb +34 -0
  35. data/spec/spec_helper.rb +24 -0
  36. data/spec/support/conversation.rb +3 -0
  37. metadata +101 -0
@@ -0,0 +1,120 @@
1
+ # See support/conversation.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Conversational::ActiveRecordAdditions do
6
+ let(:valid_attributes) { {:with => "someone"} }
7
+ describe "scopes" do
8
+ let!(:conversation) { Conversation.create!(valid_attributes) }
9
+ describe ".converser" do
10
+ it "should find the conversation with someone" do
11
+ Conversation.converser("someone").last.should == conversation
12
+ end
13
+ end
14
+ describe ".recent" do
15
+ context "the conversation was updated less than 24 hours ago" do
16
+ context "passing no arguments" do
17
+ it "should find the conversation" do
18
+ Conversation.recent.last.should == conversation
19
+ end
20
+ end
21
+ end
22
+ context "the conversation was updated more than 24 hours ago" do
23
+ before {
24
+ Conversation.record_timestamps = false
25
+ conversation.updated_at = 24.hours.ago
26
+ conversation.save!
27
+ Conversation.record_timestamps = true
28
+ }
29
+ context "passing no arguments" do
30
+ it "should not find the conversation" do
31
+ Conversation.recent.last.should be_nil
32
+ end
33
+ end
34
+ context "passing an argument" do
35
+ it "should find the conversation" do
36
+ Conversation.recent(25.hours.ago).last.should == conversation
37
+ end
38
+ end
39
+ end
40
+ end
41
+ describe ".in_progress" do
42
+ context "conversation is in progress" do
43
+ it "should find the conversation" do
44
+ Conversation.in_progress.last.should == conversation
45
+ end
46
+ end
47
+ context "conversation is finished" do
48
+ before {
49
+ conversation.state = "finished"
50
+ conversation.save!
51
+ }
52
+ it "should not find the conversation" do
53
+ Conversation.in_progress.last.should be_nil
54
+ end
55
+ end
56
+ end
57
+ describe ".with" do
58
+ context "conversation is in progress and recent" do
59
+ it "should find the conversation" do
60
+ Conversation.with("someone").last.should == conversation
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ describe ".find_or_create_with" do
67
+ context "when no existing conversation exists with 'someone'" do
68
+ context "but a subclass for this topic exists and has not been excluded" do
69
+ let(:subclass) { mock("Subclass") }
70
+ before {
71
+ Conversational::ConversationDefinition.stub!(
72
+ :find_subclass_by_topic
73
+ ).with("something").and_return(subclass)
74
+ }
75
+ it "should create an instance of the subclass" do
76
+ subclass.should_receive(:create!).with(
77
+ :with => "someone", :topic => "something"
78
+ )
79
+ Conversation.find_or_create_with("someone", "something")
80
+ end
81
+ end
82
+ context "and a subclass for this topic also does not exist" do
83
+ before {
84
+ Conversational::ConversationDefinition.stub!(
85
+ :find_subclass_by_topic
86
+ ).and_return(nil)
87
+ }
88
+ context "and the topic is blank" do
89
+ it "should raise an error" do
90
+ lambda {
91
+ Conversation.find_or_create_with("someone", nil)
92
+ }.should raise_error(/not defined a blank_topic_subclass/)
93
+ end
94
+ end
95
+ context "though the topic is not blank" do
96
+ it "should still raise an error" do
97
+ lambda {
98
+ Conversation.find_or_create_with(
99
+ "someone", "something"
100
+ )
101
+ }.should raise_error(/not defined SomethingConversation/)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ context "a conversation already exists" do
107
+ let(:conversation) { Conversation.new(valid_attributes) }
108
+ let(:defined_conversation) { mock("defined_conversation") }
109
+ before {
110
+ Conversation.stub_chain(:with, :last).and_return(conversation)
111
+ }
112
+ it "should return the conversation as an instance of the subclass" do
113
+ conversation.should_receive(:details).and_return(defined_conversation)
114
+ Conversation.find_or_create_with(
115
+ "someone", "something"
116
+ ).should == defined_conversation
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,248 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conversational::ConversationDefinition do
4
+ def define_conversation(params=nil)
5
+ defined_conversation = {}
6
+ unless params && (params[:blank] || params[:unknown])
7
+ defined_conversation[:class] = mock("DefinedConversation")
8
+ defined_conversation[:instance] = mock("defined_conversation")
9
+ topic = "defined"
10
+ topic.stub!(:+).and_return(topic)
11
+ topic.stub!(:classify).and_return(topic)
12
+ topic.stub!(:constantize).and_return(defined_conversation[:class])
13
+ defined_conversation[:topic] = topic
14
+ defined_conversation[:class].stub!(:<=).and_return(true)
15
+ else
16
+ if params[:unknown]
17
+ defined_conversation[:class] = mock("UnknownConversation")
18
+ defined_conversation[:instance] = mock("unknown_conversation")
19
+ Conversational::ConversationDefinition.unknown_topic_subclass = defined_conversation[:class]
20
+ end
21
+ if params[:blank]
22
+ defined_conversation[:class] = mock("BlankConversation")
23
+ defined_conversation[:instance] = mock("blank_conversation")
24
+ Conversational::ConversationDefinition.blank_topic_subclass = defined_conversation[:class]
25
+ end
26
+ end
27
+ defined_conversation
28
+ end
29
+
30
+ describe ".exclude" do
31
+ it "should accept a string" do
32
+ lambda {
33
+ Conversational::ConversationDefinition.exclude "something"
34
+ }.should_not raise_error
35
+ end
36
+ it "should accept a symbol" do
37
+ lambda {
38
+ Conversational::ConversationDefinition.exclude :defined_conversation
39
+ }.should_not raise_error
40
+ end
41
+ it "should accept a regex" do
42
+ lambda {
43
+ Conversational::ConversationDefinition.exclude /something/i
44
+ }.should_not raise_error
45
+ end
46
+ it "should accept a Class" do
47
+ some_class = mock("SomeClass", :superclass => Class)
48
+ some_class.stub!(:is_a?).with(Class).and_return(true)
49
+ lambda {
50
+ Conversational::ConversationDefinition.exclude some_class
51
+ }.should_not raise_error
52
+ end
53
+ it "should accept an Array where the elements are a Class, String, Symbol or Regexp" do
54
+ some_class = mock("SomeClass", :superclass => Class)
55
+ some_class.stub!(:is_a?).with(Class).and_return(true)
56
+ lambda {
57
+ Conversational::ConversationDefinition.exclude ["Something", some_class, /something/i, :something]
58
+ }.should_not raise_error
59
+ end
60
+ it "should accept nil" do
61
+ lambda {
62
+ Conversational::ConversationDefinition.exclude nil
63
+ }.should_not raise_error
64
+ end
65
+ it "should not accept anything else" do
66
+ some_class = mock("SomeClass")
67
+ lambda {
68
+ Conversational::ConversationDefinition.exclude some_class
69
+ }.should raise_error(/You specified a /)
70
+ end
71
+ end
72
+
73
+ describe ".find_subclass_by_topic" do
74
+ context "a conversation definition with this topic has been defined" do
75
+ let(:defined_conversation) { define_conversation }
76
+ context "and it subclasses conversation" do
77
+ before {
78
+ defined_conversation[:class].stub!(:<=).and_return(true)
79
+ }
80
+ context "and it has not been excluded" do
81
+ before {
82
+ Conversational::ConversationDefinition.exclude nil
83
+ }
84
+ it "should return the conversation definition class" do
85
+ Conversational::ConversationDefinition.find_subclass_by_topic(
86
+ defined_conversation[:topic]
87
+ ).should == defined_conversation[:class]
88
+ end
89
+ end
90
+ context "but it has been excluded" do
91
+ before {
92
+ Conversational::ConversationDefinition.stub!(:exclude?).and_return(true)
93
+ }
94
+ context "and it has not been explicitly included" do
95
+ context "and an unknown topic subclass has been defined" do
96
+ let!(:unknown_conversation) { define_conversation(:unknown => true) }
97
+ it "should return the unknown conversation subclass" do
98
+ Conversational::ConversationDefinition.find_subclass_by_topic(
99
+ defined_conversation[:topic]
100
+ ).should == unknown_conversation[:class]
101
+ end
102
+ end
103
+ context "and an unknown topic subclass has not been defined" do
104
+ before {
105
+ Conversational::ConversationDefinition.unknown_topic_subclass = nil
106
+ }
107
+ it "should return nil" do
108
+ Conversational::ConversationDefinition.find_subclass_by_topic(
109
+ defined_conversation[:topic]
110
+ ).should be_nil
111
+ end
112
+ end
113
+ end
114
+ context "but it was explicitly included for this query" do
115
+ it "should return the conversation definition class" do
116
+ Conversational::ConversationDefinition.find_subclass_by_topic(
117
+ defined_conversation[:topic], :include_all => true
118
+ ).should == defined_conversation[:class]
119
+ end
120
+ end
121
+ end
122
+ context "and an unknown topic subclass has not been defined" do
123
+ before {
124
+ Conversational::ConversationDefinition.unknown_topic_subclass = nil
125
+ }
126
+ context "and the conversation has been excluded" do
127
+ context "by setting Conversation.exclude 'defined_conversation'" do
128
+ before {
129
+ excluded_class = "defined_conversation"
130
+ excluded_class.stub_chain(
131
+ :classify,
132
+ :constantize).and_return(
133
+ defined_conversation[:class]
134
+ )
135
+ Conversational::ConversationDefinition.exclude excluded_class
136
+ }
137
+ it "should return nil" do
138
+ Conversational::ConversationDefinition.find_subclass_by_topic(
139
+ defined_conversation[:topic]
140
+ ).should be_nil
141
+ end
142
+ end
143
+ context "by setting Conversation.exclude DefinedConversation" do
144
+ before {
145
+ excluded_class = defined_conversation[:class]
146
+ defined_conversation[:class].stub!(:superclass).and_return(Class)
147
+ defined_conversation[:class].stub!(:is_a?).with(
148
+ Class
149
+ ).and_return(true)
150
+ Conversational::ConversationDefinition.exclude(
151
+ defined_conversation[:class]
152
+ )
153
+ }
154
+ it "should return nil" do
155
+ Conversational::ConversationDefinition.find_subclass_by_topic(
156
+ defined_conversation[:topic]
157
+ ).should be_nil
158
+ end
159
+ end
160
+ context "by setting Conversation.exclude /defined/i" do
161
+ before {
162
+ Conversational::ConversationDefinition.exclude /defined/i
163
+ }
164
+ it "should return nil" do
165
+ Conversational::ConversationDefinition.find_subclass_by_topic(
166
+ defined_conversation[:topic]
167
+ ).should be_nil
168
+ end
169
+ end
170
+ context "by setting Conversation.exclude [/defined/i]" do
171
+ before {
172
+ Conversational::ConversationDefinition.exclude [/defined/i]
173
+ }
174
+ it "should return nil" do
175
+ Conversational::ConversationDefinition.find_subclass_by_topic(
176
+ defined_conversation[:topic]
177
+ ).should be_nil
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ context "but it is not a type of conversation" do
184
+ before {
185
+ defined_conversation[:class].stub!(:<=).and_return(false)
186
+ }
187
+ context "but an unknown topic subclass has been defined" do
188
+ let!(:unknown_conversation) { define_conversation(:unknown => true) }
189
+ it "should return the unknown conversation subclass" do
190
+ Conversational::ConversationDefinition.find_subclass_by_topic(
191
+ defined_conversation[:topic]
192
+ ).should == unknown_conversation[:class]
193
+ end
194
+ end
195
+ context "and an unknown topic subclass has not been defined" do
196
+ before {
197
+ Conversational::ConversationDefinition.unknown_topic_subclass = nil
198
+ }
199
+ it "should return nil" do
200
+ Conversational::ConversationDefinition.find_subclass_by_topic(
201
+ defined_conversation[:topic]
202
+ ).should be_nil
203
+ end
204
+ end
205
+ end
206
+ end
207
+ context "and a conversation definition with this topic has not been defined" do
208
+ context "but an unknown conversation definition has been defined" do
209
+ let!(:unknown_conversation) { define_conversation(:unknown => true) }
210
+ it "should return the unknown conversation subclass" do
211
+ Conversational::ConversationDefinition.find_subclass_by_topic(
212
+ "something"
213
+ ).should == unknown_conversation[:class]
214
+ end
215
+ end
216
+ context "and an unknown conversation definition has not been defined" do
217
+ before {
218
+ Conversational::ConversationDefinition.unknown_topic_subclass = nil
219
+ }
220
+ it "should return nil" do
221
+ Conversational::ConversationDefinition.find_subclass_by_topic(
222
+ "someone"
223
+ ).should be_nil
224
+ end
225
+ end
226
+ end
227
+ context "and the topic for conversation is blank" do
228
+ context "but a blank conversation definition has been defined" do
229
+ let!(:blank_conversation) { define_conversation(:blank => true) }
230
+ it "should return the blank conversation subclass" do
231
+ Conversational::ConversationDefinition.find_subclass_by_topic(
232
+ nil
233
+ ).should == blank_conversation[:class]
234
+ end
235
+ end
236
+ context "and a blank conversation definition has not been defined" do
237
+ before(:each) do
238
+ Conversational::ConversationDefinition.blank_topic_subclass = nil
239
+ end
240
+ it "should return nil" do
241
+ Conversational::ConversationDefinition.find_subclass_by_topic(
242
+ nil
243
+ ).should be_nil
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conversational::Conversation do
4
+
5
+ describe "#details" do
6
+ let(:conversation) { Class.new }
7
+ before {
8
+ conversation.extend(Conversational::Conversation)
9
+ conversation.stub!(:topic).and_return("something")
10
+ }
11
+ context "a subclass for this topic exists" do
12
+ let(:subclass) { mock("Subclass") }
13
+ before {
14
+ Conversational::ConversationDefinition.stub!(
15
+ :find_subclass_by_topic
16
+ ).and_return(subclass)
17
+ }
18
+ it "should return the instance as a subclass" do
19
+ conversation.should_receive(:becomes).with(subclass)
20
+ conversation.details
21
+ end
22
+ end
23
+ context "a subclass for this topic does not exist" do
24
+ before {
25
+ Conversational::ConversationDefinition.stub!(
26
+ :find_subclass_by_topic
27
+ ).and_return(nil)
28
+ }
29
+ it "should return nil" do
30
+ conversation.details.should be_nil
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ # This file is copied to ~/spec when you run 'ruby script/generate rspec'
2
+ # from the project root directory.
3
+ ENV["RAILS_ENV"] ||= 'test'
4
+ require File.dirname(__FILE__) + "/../config/environment" unless defined?(Rails)
5
+ require 'rspec/rails'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ Rspec.configure do |config|
12
+ # == Mock Framework
13
+ #
14
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
15
+ #
16
+ # config.mock_with :mocha
17
+ # config.mock_with :flexmock
18
+ # config.mock_with :rr
19
+ config.mock_with :rspec
20
+
21
+ # If you'd prefer not to run each of your examples within a transaction,
22
+ # uncomment the following line.
23
+ # config.use_transactional_examples = false
24
+ end
@@ -0,0 +1,3 @@
1
+ class Conversation < ActiveRecord::Base
2
+ include Conversational::Conversation
3
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: conversational
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
+ platform: ruby
11
+ authors:
12
+ - David Wilkie
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-01 00:00:00 +07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: dwilkie@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.markdown
29
+ files:
30
+ - .gitignore
31
+ - MIT-LICENSE
32
+ - README.markdown
33
+ - Rakefile
34
+ - VERSION
35
+ - conversational.gemspec
36
+ - features/configure_blank_topic.feature
37
+ - features/configure_exclusion_conversations.feature
38
+ - features/configure_unknown_topic.feature
39
+ - features/find_existing_conversation.feature
40
+ - features/find_or_create_with.feature
41
+ - features/retrieve_conversation_details.feature
42
+ - features/step_definitions/conversation_steps.rb
43
+ - features/step_definitions/pickle_steps.rb
44
+ - features/support/conversation.rb
45
+ - features/support/email_spec.rb
46
+ - features/support/env.rb
47
+ - features/support/mail.rb
48
+ - features/support/paths.rb
49
+ - features/support/pickle.rb
50
+ - features/support/sample_conversation.rb
51
+ - lib/conversational.rb
52
+ - lib/conversational/active_record_additions.rb
53
+ - lib/conversational/conversation.rb
54
+ - lib/conversational/conversation_definition.rb
55
+ - lib/generators/conversational/migration/USAGE
56
+ - lib/generators/conversational/migration/migration_generator.rb
57
+ - lib/generators/conversational/migration/templates/migration.rb
58
+ - lib/generators/conversational/skeleton/USAGE
59
+ - lib/generators/conversational/skeleton/skeleton_generator.rb
60
+ - lib/generators/conversational/skeleton/templates/conversation.rb
61
+ - spec/active_record_additions_spec.rb
62
+ - spec/conversation_definition_spec.rb
63
+ - spec/conversation_spec.rb
64
+ - spec/spec_helper.rb
65
+ - spec/support/conversation.rb
66
+ has_rdoc: true
67
+ homepage: http://github.com/dwilkie/conversational
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --charset=UTF-8
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.3.6
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Have conversations with your users over SMS
96
+ test_files:
97
+ - spec/conversation_spec.rb
98
+ - spec/active_record_additions_spec.rb
99
+ - spec/conversation_definition_spec.rb
100
+ - spec/spec_helper.rb
101
+ - spec/support/conversation.rb