conversational 0.3.0

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