church-community-builder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +52 -0
- data/README.rdoc +54 -0
- data/Rakefile +32 -0
- data/ccb_api.gemspec +26 -0
- data/docs/batch_implement.pdf +0 -0
- data/docs/event_implement.pdf +0 -0
- data/docs/group_implement.pdf +0 -0
- data/docs/individual_profile_implement.pdf +0 -0
- data/docs/pwt_implement.pdf +0 -0
- data/docs/pwt_overview.pdf +0 -0
- data/examples/batch.rb +50 -0
- data/examples/calendar.rb +18 -0
- data/examples/campus.rb +13 -0
- data/examples/individual.rb +38 -0
- data/examples/sync_data.rb +48 -0
- data/lib/api/address.rb +28 -0
- data/lib/api/api_object.rb +116 -0
- data/lib/api/batch.rb +48 -0
- data/lib/api/batch_list.rb +50 -0
- data/lib/api/campus.rb +29 -0
- data/lib/api/campus_list.rb +65 -0
- data/lib/api/individual.rb +138 -0
- data/lib/api/individual_list.rb +65 -0
- data/lib/api/mergeable_individual_list.rb +92 -0
- data/lib/api/mergeable_transaction_list.rb +74 -0
- data/lib/api/search.rb +81 -0
- data/lib/api/transaction.rb +114 -0
- data/lib/api/transaction_list.rb +52 -0
- data/lib/api/valid_individual.rb +43 -0
- data/lib/api/valid_individual_list.rb +49 -0
- data/lib/auto_load.rb +17 -0
- data/lib/ccb_api.rb +33 -0
- data/lib/common.rb +41 -0
- data/lib/exceptions.rb +5 -0
- data/lib/readers/api_reader.rb +35 -0
- data/lib/readers/batch_list_reader.rb +17 -0
- data/lib/readers/batch_reader.rb +14 -0
- data/lib/readers/campus_list_reader.rb +17 -0
- data/lib/readers/individual_list_reader.rb +18 -0
- data/lib/readers/individual_reader.rb +14 -0
- data/lib/writers/api_writer.rb +64 -0
- data/lib/writers/user_writer.rb +46 -0
- data/spec/api/user_spec.rb +37 -0
- data/spec/factories/user.rb +51 -0
- data/spec/functional/ccb_spec.rb +20 -0
- data/spec/readers/user_reader_spec.rb +7 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/writers/user_writer_spec.rb +80 -0
- metadata +137 -0
data/lib/common.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
require 'cgi'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
def self.admin_request(method, params = {}, body = nil)
|
6
|
+
url = "https://#{ChurchCommunityBuilder::Api.api_subdomain}.ccbchurch.com/api.php"
|
7
|
+
username = ChurchCommunityBuilder::Api.api_username
|
8
|
+
password = ChurchCommunityBuilder::Api.api_password
|
9
|
+
|
10
|
+
response =
|
11
|
+
case method
|
12
|
+
when :post
|
13
|
+
Typhoeus::Request.post(url, {:headers => headers, :body => body})
|
14
|
+
when :get
|
15
|
+
Typhoeus::Request.get(url, params: params, userpwd: username+":"+password)
|
16
|
+
when :put
|
17
|
+
Typhoeus::Request.put(url, {:headers => headers, :body => body})
|
18
|
+
when :delete
|
19
|
+
Typhoeus::Request.delete(url, {:headers => headers, :params => params})
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
unless response.success?
|
24
|
+
if response.code > 0
|
25
|
+
raise ChurchCommunityBuilderExceptions::UnableToConnectToChurchCommunityBuilder.new(response.body)
|
26
|
+
else
|
27
|
+
begin
|
28
|
+
error_messages = JSON.parse(response.body)['error_message']
|
29
|
+
rescue
|
30
|
+
response_code_desc = response.headers.partition("\r\n")[0].sub(/^\S+/, '') rescue nil
|
31
|
+
raise ChurchCommunityBuilderExceptions::UnknownErrorConnectingToChurchCommunityBuilder.new("Unknown error when connecting to The City.#{response_code_desc}")
|
32
|
+
else
|
33
|
+
raise ChurchCommunityBuilderExceptions::ChurchCommunityBuilderResponseError.new(error_messages)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
response
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/lib/exceptions.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
# This adapter is the standard for all loading objects.
|
4
|
+
class ApiReader
|
5
|
+
attr_reader :headers
|
6
|
+
|
7
|
+
# Constructor
|
8
|
+
# def initialize
|
9
|
+
# end
|
10
|
+
|
11
|
+
# Loads the list
|
12
|
+
#
|
13
|
+
# @return the data loaded in a JSON object.
|
14
|
+
def load_feed
|
15
|
+
|
16
|
+
@url_data_params ||= {}
|
17
|
+
response = ChurchCommunityBuilder::admin_request(:get, @url_data_params)
|
18
|
+
data = _xml2json(response.body)
|
19
|
+
@headers = response.headers
|
20
|
+
|
21
|
+
return data
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def _xml2json(xml)
|
27
|
+
# {KeepRoot: true, ForceArray: false, SuppressEmpty: true} were set to
|
28
|
+
# maximize compatibility with Hash.from_xml, used previously.
|
29
|
+
#
|
30
|
+
XmlSimple.xml_in(xml, {KeepRoot: true, ForceArray: false, SuppressEmpty: true})
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
class BatchListReader < ApiReader
|
4
|
+
|
5
|
+
# Constructor.
|
6
|
+
#
|
7
|
+
def initialize(options = {})
|
8
|
+
filter = options[:filter]
|
9
|
+
@url_data_params = options[:url_data_params]
|
10
|
+
|
11
|
+
@url_data_params.merge!({:filter => filter}) if filter
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
class BatchReader < ApiReader
|
4
|
+
|
5
|
+
# Constructor.
|
6
|
+
#
|
7
|
+
# @param person_id The ID of the user to load.
|
8
|
+
def initialize(batch_id)
|
9
|
+
@url_data_params = {srv: "batch_profile_from_id", id: batch_id}
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
class CampusListReader < ApiReader
|
4
|
+
|
5
|
+
# Constructor.
|
6
|
+
#
|
7
|
+
def initialize(options = {})
|
8
|
+
filter = options[:filter]
|
9
|
+
@url_data_params = options[:url_data_params]
|
10
|
+
|
11
|
+
@url_data_params.merge!({:filter => filter}) if filter
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
# This reader works with both Individual List and ValidIndividualList
|
4
|
+
#
|
5
|
+
class IndividualListReader < ApiReader
|
6
|
+
|
7
|
+
# Constructor.
|
8
|
+
#
|
9
|
+
def initialize(options = {})
|
10
|
+
filter = options[:filter]
|
11
|
+
|
12
|
+
@url_data_params = options[:url_data_params]
|
13
|
+
@url_data_params.merge!({:filter => filter}) if filter
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
class IndividualReader < ApiReader
|
4
|
+
|
5
|
+
# Constructor.
|
6
|
+
#
|
7
|
+
# @param individual_id The ID of the user to load.
|
8
|
+
def initialize(individual_id)
|
9
|
+
@url_data_params = {srv: "individual_profile_from_id", individual_id: individual_id}
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
# This adapter is the standard for all saving objects.
|
4
|
+
class ApiWriter
|
5
|
+
attr_reader :error_messages, :response_code
|
6
|
+
|
7
|
+
# # Saves this object.
|
8
|
+
# #
|
9
|
+
# # @return True or ID on success, otherwise false.
|
10
|
+
# def save_object
|
11
|
+
# @url_data_params ||= {}
|
12
|
+
# success = true
|
13
|
+
|
14
|
+
# if @url_data_path.nil?
|
15
|
+
# @error_messages = ["#{@url_action.to_s.upcase} not implemented for #{self.class.to_s}"]
|
16
|
+
# return false
|
17
|
+
# end
|
18
|
+
|
19
|
+
# if @updatable_fields and !@updatable_fields.empty?
|
20
|
+
# fields_to_remove = @url_data_params.keys - @updatable_fields
|
21
|
+
# fields_to_remove.each { |ftr| @url_data_params.delete(ftr) }
|
22
|
+
# end
|
23
|
+
|
24
|
+
# begin
|
25
|
+
# response = ChurchCommunityBuilder::admin_request(@url_action, @url_data_path, nil, @url_data_params.to_json)
|
26
|
+
# @response_code = response.code
|
27
|
+
# # No content but is a success
|
28
|
+
# success = response.code == 204 ? {'success' => true} : JSON.parse(response.body)
|
29
|
+
# rescue Exception => e
|
30
|
+
# @error_messages = e.message.split(',')
|
31
|
+
# success = false
|
32
|
+
# end
|
33
|
+
|
34
|
+
# return success
|
35
|
+
# end
|
36
|
+
|
37
|
+
|
38
|
+
# # Deletes this object.
|
39
|
+
# #
|
40
|
+
# # @return True or ID on success, otherwise false.
|
41
|
+
# def delete_object
|
42
|
+
# success = true
|
43
|
+
|
44
|
+
# if @url_data_delete_path.nil?
|
45
|
+
# @error_messages = ["DELETE not implemented for #{self.class.to_s}"]
|
46
|
+
# return false
|
47
|
+
# end
|
48
|
+
|
49
|
+
# begin
|
50
|
+
# # @url_data_path should be the same as :put if this object is already
|
51
|
+
# # setup and mapped to an object that exists
|
52
|
+
# response = ChurchCommunityBuilder::admin_request(:delete, @url_data_delete_path)
|
53
|
+
# success = response.code == 204 ? true : false # No content but is a success
|
54
|
+
# rescue Exception => e
|
55
|
+
# @error_messages = e.message.split(',')
|
56
|
+
# success = false
|
57
|
+
# end
|
58
|
+
|
59
|
+
# return success
|
60
|
+
# end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ChurchCommunityBuilder
|
2
|
+
|
3
|
+
class UserWriter < ApiWriter
|
4
|
+
|
5
|
+
# Constructor.
|
6
|
+
#
|
7
|
+
# @param data The json object data to save.
|
8
|
+
def initialize(data)
|
9
|
+
if data[:id]
|
10
|
+
@url_action = :put
|
11
|
+
@url_data_path = "/users/#{data[:id]}"
|
12
|
+
else
|
13
|
+
@url_action = :post
|
14
|
+
@url_data_path = "/users"
|
15
|
+
end
|
16
|
+
@url_data_delete_path = "/users/#{data[:id]}"
|
17
|
+
|
18
|
+
@url_data_params = data
|
19
|
+
|
20
|
+
@updatable_fields = [:title,
|
21
|
+
:first,
|
22
|
+
:middle,
|
23
|
+
:last,
|
24
|
+
:nickname,
|
25
|
+
:gender,
|
26
|
+
:email,
|
27
|
+
:staff,
|
28
|
+
:primary_campus_id,
|
29
|
+
:member_since,
|
30
|
+
:birthdate,
|
31
|
+
:primary_phone,
|
32
|
+
:primary_phone_type,
|
33
|
+
:secondary_phone,
|
34
|
+
:secondary_phone_type,
|
35
|
+
:external_id_1,
|
36
|
+
:external_id_2,
|
37
|
+
:external_id_3,
|
38
|
+
:marital_status,
|
39
|
+
:is_an_organization]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# require File.join(Dir.pwd, 'spec', 'spec_helper')
|
2
|
+
|
3
|
+
# describe 'User' do
|
4
|
+
|
5
|
+
# before do
|
6
|
+
# simulate_connection_to_server
|
7
|
+
# end
|
8
|
+
|
9
|
+
# after do
|
10
|
+
|
11
|
+
# end
|
12
|
+
|
13
|
+
# it 'should pass retrieving a user from list' do
|
14
|
+
# request_data = FactoryGirl.attributes_for(:user_list, {
|
15
|
+
# :total_entries => 1,
|
16
|
+
# :total_pages => 1,
|
17
|
+
# :users => [FactoryGirl.attributes_for(:user)]
|
18
|
+
# }).to_json
|
19
|
+
# TheCity.stub(:admin_request).and_return( TheCityResponse.new(200, request_data) )
|
20
|
+
|
21
|
+
# user_list = TheCity::UserList.new
|
22
|
+
|
23
|
+
# user = user_list[0]
|
24
|
+
# user.full_name.should == "Sammy Shepherd"
|
25
|
+
# end
|
26
|
+
|
27
|
+
|
28
|
+
# it 'should pass retrieving a user' do
|
29
|
+
# request_data = FactoryGirl.attributes_for(:user).to_json
|
30
|
+
# TheCity.stub(:admin_request).and_return( TheCityResponse.new(200, request_data) )
|
31
|
+
# user = TheCity::User.load_by_id(123)
|
32
|
+
# user.full_name.should == "Sammy Shepherd"
|
33
|
+
# end
|
34
|
+
|
35
|
+
# end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# module TheCity
|
2
|
+
# FactoryGirl.define do
|
3
|
+
|
4
|
+
# factory :user, :class => TheCity::User do
|
5
|
+
# api_url "https://api.onthecity.org/users/946060874"
|
6
|
+
# updated_at "05/15/2012 07:24 AM (UTC)"
|
7
|
+
# last_logged_in "05/16/2012 04:52 AM (UTC)"
|
8
|
+
# secondary_phone ""
|
9
|
+
# last_engaged "05/16/2012 04:52 AM (UTC)"
|
10
|
+
# title "Pastor"
|
11
|
+
# internal_url "http://local.devthecity.org:3000/users/946060874"
|
12
|
+
# id 946060874
|
13
|
+
# first "Sam"
|
14
|
+
# primary_campus_name nil
|
15
|
+
# last "Shepherd"
|
16
|
+
# head_of_household true
|
17
|
+
# nickname "Sammy"
|
18
|
+
# active true
|
19
|
+
# primary_phone_type "home"
|
20
|
+
# primary_phone ""
|
21
|
+
# member_since "05/14/2012"
|
22
|
+
# birthdate "1974-11-09"
|
23
|
+
# email_bouncing false
|
24
|
+
# secondary_phone_type "home"
|
25
|
+
# primary_campus_id nil
|
26
|
+
# marital_status nil
|
27
|
+
# contact_updated_at "05/15/2012 12:24 AM (UTC)"
|
28
|
+
# type "User"
|
29
|
+
# staff true
|
30
|
+
# admin_url "http://church.onthecity.org/admin/users/946060874"
|
31
|
+
# created_at "05/14/2012 05:51 PM (UTC)"
|
32
|
+
# gender "Male"
|
33
|
+
# external_id_1 nil
|
34
|
+
# external_id_2 nil
|
35
|
+
# external_id_3 nil
|
36
|
+
# middle ""
|
37
|
+
# email "zack@church.org"
|
38
|
+
# last_checkin_date "05/14/2012 05:51 PM (UTC)"
|
39
|
+
# last_donation_date "05/14/2012 05:51 PM (UTC)"
|
40
|
+
# last_attendance_date "05/14/2012 05:51 PM (UTC)"
|
41
|
+
# in_community true
|
42
|
+
# in_service true
|
43
|
+
# in_welcome true
|
44
|
+
# in_campus true
|
45
|
+
# in_neighborhood true
|
46
|
+
# end
|
47
|
+
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
|
51
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# require File.join(Dir.pwd, 'spec', 'spec_helper')
|
2
|
+
|
3
|
+
# describe 'AdminApi' do
|
4
|
+
|
5
|
+
# before do
|
6
|
+
# simulate_connection_to_server
|
7
|
+
# end
|
8
|
+
|
9
|
+
# after do
|
10
|
+
|
11
|
+
# end
|
12
|
+
|
13
|
+
# it 'should get a list of users without any paramters'
|
14
|
+
# it 'should get a list of users with parameters'
|
15
|
+
# it 'should get a user count without any paramters'
|
16
|
+
# it 'should get a user count with the specified paramters'
|
17
|
+
|
18
|
+
# end
|
19
|
+
|
20
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# # Project:: TheCity Admin API
|
2
|
+
# # File:: the_city.rb
|
3
|
+
# #
|
4
|
+
# # Author:: Wes Hays <weshays@gbdev.com>
|
5
|
+
# # Link:: https://github.com/weshays/admin-api-ruby
|
6
|
+
# # Package:: TheCity::Admin
|
7
|
+
|
8
|
+
|
9
|
+
# require File.expand_path( File.dirname(__FILE__) + '/../lib/the_city_admin.rb')
|
10
|
+
|
11
|
+
# require 'rubygems'
|
12
|
+
# require 'rspec'
|
13
|
+
# require 'ruby-debug'
|
14
|
+
# require 'date'
|
15
|
+
|
16
|
+
# require 'factory_girl'
|
17
|
+
# Dir.glob(File.dirname(__FILE__) + "/factories/*").each { |f| require f }
|
18
|
+
|
19
|
+
|
20
|
+
# TheCityResponse = Struct.new(:code, :body, :headers)
|
21
|
+
|
22
|
+
# RSpec.configure do |config|
|
23
|
+
# config.tty = true
|
24
|
+
# config.mock_with :rspec
|
25
|
+
# config.include FactoryGirl::Syntax::Methods
|
26
|
+
# end
|
27
|
+
|
28
|
+
|
29
|
+
# def simulate_connection_to_server
|
30
|
+
# @the_city = TheCity::AdminApi.new('cf2903151e3213e66fd8080c7d8b65b1d6ccdd31', '5c88b32edda7653c')
|
31
|
+
# end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# require 'spec_helper'
|
2
|
+
|
3
|
+
# describe 'UserWriter' do
|
4
|
+
|
5
|
+
# it 'should show connection error messages if server cannot be reached' do
|
6
|
+
# TheCity.stub(:admin_request) { raise TheCityExceptions::UnableToConnectToTheCity.new('Unable to connect to server') }
|
7
|
+
# user_data = FactoryGirl.attributes_for(:user, {:id => nil})
|
8
|
+
# user = TheCity::User.new(user_data)
|
9
|
+
# user.id.should be_nil
|
10
|
+
# user.save.should === false
|
11
|
+
# user.id.should be_nil
|
12
|
+
# user.error_messages.should == ['Unable to connect to server']
|
13
|
+
# end
|
14
|
+
|
15
|
+
|
16
|
+
# it 'should save if all data is valid for new user' do
|
17
|
+
# request_data = FactoryGirl.attributes_for(:user, {:id => 12345}).to_json
|
18
|
+
# TheCity.stub(:admin_request).and_return( TheCityResponse.new(200, request_data) )
|
19
|
+
# user_data = FactoryGirl.attributes_for(:user, {:id => nil})
|
20
|
+
# user = TheCity::User.new(user_data)
|
21
|
+
# user.id.should be_nil
|
22
|
+
# user.save.should === true
|
23
|
+
# user.id.should == 12345
|
24
|
+
# end
|
25
|
+
|
26
|
+
|
27
|
+
# it 'should show error messages if save fails for new user' do
|
28
|
+
# TheCity.stub(:admin_request) { raise TheCityExceptions::TheCityResponseError.new('email address already exists') }
|
29
|
+
# user_data = FactoryGirl.attributes_for(:user, {:id => nil})
|
30
|
+
# user = TheCity::User.new(user_data)
|
31
|
+
# user.id.should be_nil
|
32
|
+
# user.save.should === false
|
33
|
+
# user.id.should be_nil
|
34
|
+
# user.error_messages.should == ['email address already exists']
|
35
|
+
# end
|
36
|
+
|
37
|
+
|
38
|
+
# it 'should save if all data is valid for existing user' do
|
39
|
+
# request_data = FactoryGirl.attributes_for(:user, {:id => 12345}).to_json
|
40
|
+
# TheCity.stub(:admin_request).and_return( TheCityResponse.new(200, request_data) )
|
41
|
+
# user_data = FactoryGirl.attributes_for(:user, {:id => 12345})
|
42
|
+
# user = TheCity::User.new(user_data)
|
43
|
+
# user.id.should == 12345
|
44
|
+
# user.save.should === true
|
45
|
+
# user.id.should == 12345
|
46
|
+
# end
|
47
|
+
|
48
|
+
|
49
|
+
# it 'should show error messages if save fails for existing user' do
|
50
|
+
# TheCity.stub(:admin_request) { raise TheCityExceptions::TheCityResponseError.new('something bad happened') }
|
51
|
+
# user_data = FactoryGirl.attributes_for(:user, {:id => 12345})
|
52
|
+
# user = TheCity::User.new(user_data)
|
53
|
+
# user.id.should == 12345
|
54
|
+
# user.save.should === false
|
55
|
+
# user.id.should == 12345
|
56
|
+
# user.error_messages.should == ['something bad happened']
|
57
|
+
# end
|
58
|
+
|
59
|
+
|
60
|
+
# it 'should delete if data ID exists for the existing user' do
|
61
|
+
# TheCity.stub(:admin_request).and_return( TheCityResponse.new(204, '{}') )
|
62
|
+
# user_data = FactoryGirl.attributes_for(:user, {:id => 12345})
|
63
|
+
# user = TheCity::User.new(user_data)
|
64
|
+
# user.is_deleted?.should === false
|
65
|
+
# user.delete
|
66
|
+
# user.is_deleted?.should === true
|
67
|
+
# end
|
68
|
+
|
69
|
+
|
70
|
+
# it 'should show error messages if data fails to be deleted' do
|
71
|
+
# TheCity.stub(:admin_request) { raise TheCityExceptions::TheCityResponseError.new('something bad happened') }
|
72
|
+
# user_data = FactoryGirl.attributes_for(:user, {:id => 12345})
|
73
|
+
# user = TheCity::User.new(user_data)
|
74
|
+
# user.is_deleted?.should === false
|
75
|
+
# user.delete
|
76
|
+
# user.is_deleted?.should === false
|
77
|
+
# user.error_messages.should == ['something bad happened']
|
78
|
+
# end
|
79
|
+
|
80
|
+
# end
|