amistad 0.7.5 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +4 -1
  2. data/Gemfile.lock +56 -39
  3. data/README.markdown +29 -126
  4. data/Rakefile +35 -1
  5. data/amistad.gemspec +8 -3
  6. data/lib/amistad.rb +14 -9
  7. data/lib/amistad/active_record_friend_model.rb +157 -0
  8. data/lib/amistad/active_record_friendship_model.rb +52 -0
  9. data/lib/amistad/config.rb +21 -0
  10. data/lib/amistad/friend_model.rb +10 -4
  11. data/lib/amistad/friendship_model.rb +2 -2
  12. data/lib/amistad/friendships.rb +19 -0
  13. data/lib/amistad/mongo_friend_model.rb +168 -0
  14. data/lib/amistad/mongo_mapper_friend_model.rb +58 -0
  15. data/lib/amistad/mongoid_friend_model.rb +58 -0
  16. data/lib/amistad/version.rb +1 -1
  17. data/lib/generators/amistad/install/install_generator.rb +0 -1
  18. data/lib/generators/amistad/install/templates/create_friendships.rb +2 -2
  19. data/spec/activerecord/activerecord_spec_helper.rb +11 -24
  20. data/spec/activerecord/friend_custom_model_spec.rb +17 -0
  21. data/spec/activerecord/friend_spec.rb +16 -0
  22. data/spec/activerecord/friendship_spec.rb +13 -0
  23. data/spec/activerecord/friendship_with_custom_friend_model_spec.rb +18 -0
  24. data/spec/mongo_mapper/friend_custom_model_spec.rb +27 -0
  25. data/spec/mongo_mapper/friend_spec.rb +17 -0
  26. data/spec/mongo_mapper/mongo_mapper_spec_helper.rb +9 -0
  27. data/spec/mongoid/friend_custom_model_spec.rb +27 -0
  28. data/spec/mongoid/friend_spec.rb +17 -0
  29. data/spec/mongoid/mongoid_spec_helper.rb +8 -7
  30. data/spec/spec_helper.rb +33 -4
  31. data/spec/{activerecord/activerecord_friendship_model_spec.rb → support/activerecord/friendship_examples.rb} +11 -23
  32. data/spec/support/activerecord/schema.rb +40 -0
  33. data/spec/support/friend_examples.rb +2 -12
  34. data/spec/support/parameterized_models.rb +19 -0
  35. metadata +177 -36
  36. data/lib/amistad/active_record/friend_model.rb +0 -146
  37. data/lib/amistad/active_record/friendship_model.rb +0 -50
  38. data/lib/amistad/mongoid/friend_model.rb +0 -195
  39. data/lib/generators/amistad/install/templates/friendship.rb +0 -3
  40. data/spec/activerecord/activerecord_friend_model_spec.rb +0 -13
  41. data/spec/mongoid/mongoid_friend_model_spec.rb +0 -47
data/.gitignore CHANGED
@@ -3,4 +3,7 @@ pkg/*
3
3
  .bundle
4
4
  spec/db/*.db
5
5
  nbproject
6
- .rvmrc
6
+ .rvmrc
7
+ *.sw[a-z]
8
+ .rspec
9
+ database.yml
data/Gemfile.lock CHANGED
@@ -1,54 +1,66 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- amistad (0.7.5)
4
+ amistad (0.9.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
8
8
  specs:
9
- activemodel (3.1.0)
10
- activesupport (= 3.1.0)
11
- bcrypt-ruby (~> 3.0.0)
9
+ activemodel (3.2.8)
10
+ activesupport (= 3.2.8)
12
11
  builder (~> 3.0.0)
13
- i18n (~> 0.6)
14
- activerecord (3.1.0)
15
- activemodel (= 3.1.0)
16
- activesupport (= 3.1.0)
17
- arel (~> 2.2.1)
12
+ activerecord (3.2.8)
13
+ activemodel (= 3.2.8)
14
+ activesupport (= 3.2.8)
15
+ arel (~> 3.0.2)
18
16
  tzinfo (~> 0.3.29)
19
- activesupport (3.1.0)
17
+ activesupport (3.2.8)
18
+ i18n (~> 0.6)
20
19
  multi_json (~> 1.0)
21
- arel (2.2.1)
22
- bcrypt-ruby (3.0.1)
23
- bson (1.4.0)
24
- bson_ext (1.4.0)
25
- builder (3.0.0)
20
+ arel (3.0.2)
21
+ awesome_print (1.1.0)
22
+ bson (1.7.0)
23
+ bson_ext (1.7.0)
24
+ bson (~> 1.7.0)
25
+ builder (3.0.4)
26
+ database_cleaner (0.9.1)
26
27
  diff-lcs (1.1.3)
27
- fuubar (0.0.6)
28
+ fuubar (1.1.0)
28
29
  rspec (~> 2.0)
29
- rspec-instafail (~> 0.1.8)
30
- ruby-progressbar (~> 0.0.10)
31
- i18n (0.6.0)
32
- mongo (1.4.0)
33
- bson (= 1.4.0)
34
- mongoid (2.0.2)
30
+ rspec-instafail (~> 0.2.0)
31
+ ruby-progressbar (~> 1.0.0)
32
+ i18n (0.6.1)
33
+ mongo (1.7.0)
34
+ bson (~> 1.7.0)
35
+ mongo_mapper (0.12.0)
35
36
  activemodel (~> 3.0)
36
- mongo (~> 1.3)
37
+ activesupport (~> 3.0)
38
+ plucky (~> 0.5.2)
39
+ mongoid (3.0.9)
40
+ activemodel (~> 3.1)
41
+ moped (~> 1.1)
42
+ origin (~> 1.0)
37
43
  tzinfo (~> 0.3.22)
38
- multi_json (1.0.3)
39
- rake (0.9.2)
40
- rspec (2.6.0)
41
- rspec-core (~> 2.6.0)
42
- rspec-expectations (~> 2.6.0)
43
- rspec-mocks (~> 2.6.0)
44
- rspec-core (2.6.4)
45
- rspec-expectations (2.6.0)
46
- diff-lcs (~> 1.1.2)
47
- rspec-instafail (0.1.8)
48
- rspec-mocks (2.6.0)
49
- ruby-progressbar (0.0.10)
50
- sqlite3 (1.3.4)
51
- tzinfo (0.3.30)
44
+ moped (1.2.7)
45
+ multi_json (1.3.6)
46
+ mysql2 (0.3.11)
47
+ origin (1.0.9)
48
+ pg (0.14.1)
49
+ plucky (0.5.2)
50
+ mongo (~> 1.5)
51
+ rake (0.9.2.2)
52
+ rspec (2.11.0)
53
+ rspec-core (~> 2.11.0)
54
+ rspec-expectations (~> 2.11.0)
55
+ rspec-mocks (~> 2.11.0)
56
+ rspec-core (2.11.1)
57
+ rspec-expectations (2.11.3)
58
+ diff-lcs (~> 1.1.3)
59
+ rspec-instafail (0.2.4)
60
+ rspec-mocks (2.11.3)
61
+ ruby-progressbar (1.0.2)
62
+ sqlite3 (1.3.6)
63
+ tzinfo (0.3.33)
52
64
 
53
65
  PLATFORMS
54
66
  ruby
@@ -56,10 +68,15 @@ PLATFORMS
56
68
  DEPENDENCIES
57
69
  activerecord
58
70
  amistad!
59
- bson_ext (~> 1.3)
71
+ awesome_print
72
+ bson_ext
60
73
  bundler
74
+ database_cleaner
61
75
  fuubar
62
- mongoid (~> 2.0.0)
76
+ mongo_mapper
77
+ mongoid
78
+ mysql2
79
+ pg
63
80
  rake
64
81
  rspec
65
82
  sqlite3
data/README.markdown CHANGED
@@ -1,6 +1,6 @@
1
1
  # amistad #
2
2
 
3
- Amistad adds friendships management into a rails 3.0 application. it supports ActiveRecord 3.0.x and Mongoid 2.0.x.
3
+ Amistad adds friendships management into a rails 3.0 application. it supports ActiveRecord 3.0.x, Mongoid 3.0.x and MongoMapper 0.12.0.
4
4
 
5
5
  ## Installation ##
6
6
 
@@ -14,141 +14,44 @@ Then run:
14
14
 
15
15
  ## Usage ##
16
16
 
17
- If you are using ActiveRecord, you need to generate a friendship model. Amistad has a generator for this task :
17
+ Refer to the wiki pages for usage and friendships management.
18
18
 
19
- rails generate amistad:install
20
-
21
- This command creates a new model called __friendship__ in *'app/models'* :
22
-
23
- class Friendship < ActiveRecord::Base
24
- include Amistad::FriendshipModel
25
- end
26
-
27
- It also creates a new migration for the friendship model so don't forget to migrate your database :
28
-
29
- rake db:migrate
30
-
31
- If you are using Mongoid, you don't need a friendship model. Finally, activate __amistad__ in your user model :
32
-
33
- class User < ActiveRecord::Base
34
- include Amistad::FriendModel
35
- end
36
-
37
- or :
38
-
39
- class User
40
- include Mongoid::Document
41
- include Amistad::FriendModel
42
- end
43
-
44
- ## Friendships management ##
45
-
46
- ### Creating friendships ###
47
- To create a new friendship with another user use the method called __invite()__ :
48
-
49
- @john.invite @jane
50
- @peter.invite @john
51
- @peter.invite @jane
52
- @victoria.invite @john
53
-
54
- The __invite()__ method return *true* if the friendship successfully created, otherwise it returns *false*. The friendships remain in pending state until they are approved by the user requested. To approve the friendship created above use the method called __approve()__ :
55
-
56
- @jane.approve @john
57
- @john.approve @peter
58
- @jane.approve @peter
59
-
60
- As __invite()__, __approve()__ return *true* if the friendship was successfuly approved or *false* if not.
61
-
62
- ### Listing friends ###
63
-
64
- There are two types of friends in __amistad__ :
65
-
66
- - the friends who were invited by the user
67
- - the friends who invited the user
68
-
69
- To get the friend who where invited by __@john__, use the __invited()__ method :
70
-
71
- @john.invited #=> [@jane]
72
-
73
- To get the friends who invited __@john__, use the __invited_by()__ method :
74
-
75
- @john.invited_by #=> [@peter]
76
-
77
- To get all the friends of __@john__ (those he invited and those who invited him) :
78
-
79
- @john.friends #=> [@jane, @peter]
80
-
81
- To get the pending friendships use :
82
-
83
- @victoria.pending_invited #=> [@john]
84
- @john.pending_invited_by #=> [@victoria]
85
-
86
- It is also possible to check if two users are friends :
87
-
88
- @john.friend_with? @jane #=> true
89
- @victoria.friend_with? @john #=> false
90
-
91
- You can also check if a user is somehow connected to another :
92
-
93
- @john.connected_with? @jane #=> true
94
- @victoria.connected_with? @john #=> true
95
-
96
- You can also check if a user was invited by anoter :
97
-
98
- @john.invited_by? @john #=> true
99
- @victoria.invited_by? @john #=> false
100
-
101
- You can also check if a user invited another :
102
-
103
- @john.invited? @jane #=> true
104
-
105
- You can also find the friends that two users have in common :
106
-
107
- @john.common_friends_with(@peter) #=> [@jane]
108
-
109
- ### Removing friendships ###
110
-
111
- The __remove_friendship()__ method allow a user to remove its friendships :
112
-
113
- @john.remove_friendship @jane
114
- @john.remove_friendship @peter
115
- @john.remove_friendship @victoria
116
-
117
- ### Blocking friendships ###
118
-
119
- The __block()__ method allow a user to block a friendship with another user :
120
-
121
- @john.invite @jane
122
- @jane.block @john
123
-
124
- To get the blocked users :
125
-
126
- @jane.blocked #=> [@john]
19
+ ## Testing ##
127
20
 
128
- You can also check if a user is blocked :
21
+ There are rake tasks available which allow you to run the activerecord tests for three rdbms:
129
22
 
130
- @jane.blocked? @john #=> true
23
+ rake spec:activerecord:sqlite
24
+ rake spec:activerecord:mysql
25
+ rake spec:activerecord:postgresql
131
26
 
132
- ### Unblocking friendship ###
27
+ In order to run these tasks you need to create a confiuration file for the databases connections:
133
28
 
134
- The __unblock()__ method allow a user to unblock previously blocked friendship with another user :
29
+ spec/support/activerecord/database.yml
135
30
 
136
- @jane.block @john
137
- @jane.blocked #=> [@john]
31
+ sqlite:
32
+ adapter: "sqlite3"
33
+ database: ":memory:"
138
34
 
139
- @jane.unblock @john
140
- @jane.blocked #=> []
35
+ mysql:
36
+ adapter: mysql2
37
+ encoding: utf8
38
+ database: <name of mysql database>
39
+ username: <username>
40
+ password: <password>
141
41
 
142
- ## Testing ##
42
+ postgresql:
43
+ adapter: postgresql
44
+ encoding: unicode
45
+ database: <name of postgresql database>
46
+ username: <username>
47
+ password: <password>
143
48
 
144
- It is possible to test amistad by running one of the following commands from the gem directory:
49
+ Of course there are some tasks for running mongodb orms based tests:
145
50
 
146
- rspec spec/activerecord/ # activerecord tests
147
- rspec spec/mongoid/ # mongoid tests
148
-
149
- Remember that amistad is only compatible with ActiveRecord 3.0.x and Mongoid 2.0.x.
51
+ rake spec:mongoid
52
+ rake spec:mongo_mapper
150
53
 
151
- You can also run `rake` by itself and it will run the ActiveRecord tests followed by the Mongoid tests.
54
+ The default rake tasks runs the ActiveRecord tests for the three rdbms followed by the Mongoid tests.
152
55
 
153
56
  ## Contributors ##
154
57
 
@@ -156,7 +59,7 @@ You can also run `rake` by itself and it will run the ActiveRecord tests followe
156
59
  * Adrian Dulić : unblock friendships (and many other improvements)
157
60
 
158
61
  ## Note on Patches/Pull Requests ##
159
-
62
+
160
63
  * Fork the project.
161
64
  * Make your feature addition or bug fix.
162
65
  * Add tests for it. This is important so I don't break it in a future version unintentionally.
data/Rakefile CHANGED
@@ -5,18 +5,52 @@ require 'rspec'
5
5
  require 'rspec/core/rake_task'
6
6
 
7
7
  namespace :spec do
8
+ desc "Run Rspec tests for ActiveRecord (with sqlite as RDBM)"
8
9
  RSpec::Core::RakeTask.new(:activerecord) do |t|
9
10
  t.pattern = "./spec/activerecord/**/*_spec.rb"
10
11
  t.rspec_opts = "--format Fuubar"
11
12
  end
12
13
 
14
+ desc "Run Rspec tests for Mongoid"
13
15
  RSpec::Core::RakeTask.new(:mongoid) do |t|
14
16
  t.pattern = "./spec/mongoid/**/*_spec.rb"
15
17
  t.rspec_opts = "--format Fuubar"
16
18
  end
19
+
20
+ desc "Run Rspec tests for MongoMapper"
21
+ RSpec::Core::RakeTask.new(:mongo_mapper) do |t|
22
+ t.pattern = "./spec/mongo_mapper/**/*_spec.rb"
23
+ t.rspec_opts = "--format Fuubar"
24
+ end
25
+
26
+ namespace :activerecord do
27
+ desc "run activerecord tests on a sqlite database"
28
+ task :sqlite do
29
+ ENV['RDBM'] = 'sqlite'
30
+ Rake::Task['spec:activerecord'].reenable
31
+ Rake::Task['spec:activerecord'].invoke
32
+ end
33
+
34
+ desc "run activerecord tests on a mysql database"
35
+ task :mysql do
36
+ ENV['RDBM'] = 'mysql'
37
+ Rake::Task['spec:activerecord'].reenable
38
+ Rake::Task['spec:activerecord'].invoke
39
+ end
40
+
41
+ desc "run activerecord tests on a postgresql database"
42
+ task :postgresql do
43
+ ENV['RDBM'] = 'postgresql'
44
+ Rake::Task['spec:activerecord'].reenable
45
+ Rake::Task['spec:activerecord'].invoke
46
+ end
47
+ end
17
48
  end
18
49
 
19
50
  task :default do
20
- Rake::Task['spec:activerecord'].invoke
51
+ Rake::Task['spec:activerecord:sqlite'].invoke
52
+ Rake::Task['spec:activerecord:mysql'].invoke
53
+ Rake::Task['spec:activerecord:postgresql'].invoke
21
54
  Rake::Task['spec:mongoid'].invoke
55
+ Rake::Task['spec:mongo_mapper'].invoke
22
56
  end
data/amistad.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Rawane ZOSSOU"]
10
10
  s.email = ["dev@raw1z.fr"]
11
- s.homepage = "http://github.com/raw1z/amistad"
11
+ s.homepage = "https://github.com/raw1z/amistad/wiki"
12
12
  s.summary = %q{Adds friendships management into a rails 3.0 application}
13
13
  s.description = %q{Extends your user model with friendships management methods}
14
14
 
@@ -18,10 +18,15 @@ Gem::Specification.new do |s|
18
18
  s.add_development_dependency "rake"
19
19
  s.add_development_dependency "rspec"
20
20
  s.add_development_dependency "activerecord"
21
+ s.add_development_dependency "mysql2"
22
+ s.add_development_dependency "pg"
23
+ s.add_development_dependency "database_cleaner"
21
24
  s.add_development_dependency "sqlite3"
22
- s.add_development_dependency "mongoid", "~> 2.0.0"
23
- s.add_development_dependency "bson_ext", "~> 1.3"
25
+ s.add_development_dependency "mongoid"
26
+ s.add_development_dependency "bson_ext"
24
27
  s.add_development_dependency "fuubar"
28
+ s.add_development_dependency "awesome_print"
29
+ s.add_development_dependency "mongo_mapper"
25
30
 
26
31
  s.files = `git ls-files`.split("\n")
27
32
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
data/lib/amistad.rb CHANGED
@@ -1,11 +1,16 @@
1
- if defined?(ActiveRecord)
2
- require 'amistad/active_record/friend_model'
3
- require 'amistad/active_record/friendship_model'
4
- end
1
+ require 'active_support/concern'
2
+ require 'active_support/dependencies/autoload'
3
+ require 'amistad/config'
5
4
 
6
- if defined?(Mongoid)
7
- require 'amistad/mongoid/friend_model'
8
- end
5
+ module Amistad
6
+ extend ActiveSupport::Autoload
9
7
 
10
- require 'amistad/friend_model'
11
- require 'amistad/friendship_model'
8
+ autoload :ActiveRecordFriendModel
9
+ autoload :ActiveRecordFriendshipModel
10
+ autoload :MongoFriendModel
11
+ autoload :MongoidFriendModel
12
+ autoload :MongoMapperFriendModel
13
+ autoload :FriendshipModel
14
+ autoload :FriendModel
15
+ autoload :Friendships
16
+ end
@@ -0,0 +1,157 @@
1
+ module Amistad
2
+ module ActiveRecordFriendModel
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ #####################################################################################
7
+ # friendships
8
+ #####################################################################################
9
+ has_many :friendships,
10
+ :class_name => "Amistad::Friendships::#{Amistad.friendship_model}",
11
+ :foreign_key => "friendable_id"
12
+
13
+ has_many :pending_invited,
14
+ :through => :friendships,
15
+ :source => :friend,
16
+ :conditions => { :'friendships.pending' => true, :'friendships.blocker_id' => nil }
17
+
18
+ has_many :invited,
19
+ :through => :friendships,
20
+ :source => :friend,
21
+ :conditions => { :'friendships.pending' => false, :'friendships.blocker_id' => nil }
22
+
23
+ #####################################################################################
24
+ # inverse friendships
25
+ #####################################################################################
26
+ has_many :inverse_friendships,
27
+ :class_name => "Amistad::Friendships::#{Amistad.friendship_model}",
28
+ :foreign_key => "friend_id"
29
+
30
+ has_many :pending_invited_by,
31
+ :through => :inverse_friendships,
32
+ :source => :friendable,
33
+ :conditions => { :'friendships.pending' => true, :'friendships.blocker_id' => nil }
34
+
35
+ has_many :invited_by,
36
+ :through => :inverse_friendships,
37
+ :source => :friendable,
38
+ :conditions => { :'friendships.pending' => false, :'friendships.blocker_id' => nil }
39
+
40
+ #####################################################################################
41
+ # blocked friendships
42
+ #####################################################################################
43
+ has_many :blocked_friendships,
44
+ :class_name => "Amistad::Friendships::#{Amistad.friendship_model}",
45
+ :foreign_key => "blocker_id"
46
+
47
+ has_many :blockades,
48
+ :through => :blocked_friendships,
49
+ :source => :friend,
50
+ :conditions => "friend_id <> blocker_id"
51
+
52
+ has_many :blockades_by,
53
+ :through => :blocked_friendships,
54
+ :source => :friendable,
55
+ :conditions => "friendable_id <> blocker_id"
56
+ end
57
+
58
+ # suggest a user to become a friend. If the operation succeeds, the method returns true, else false
59
+ def invite(user)
60
+ return false if user == self || find_any_friendship_with(user)
61
+ Amistad.friendship_class.new{ |f| f.friendable = self ; f.friend = user }.save
62
+ end
63
+
64
+ # approve a friendship invitation. If the operation succeeds, the method returns true, else false
65
+ def approve(user)
66
+ friendship = find_any_friendship_with(user)
67
+ return false if friendship.nil? || invited?(user)
68
+ friendship.update_attribute(:pending, false)
69
+ end
70
+
71
+ # deletes a friendship
72
+ def remove_friendship(user)
73
+ friendship = find_any_friendship_with(user)
74
+ return false if friendship.nil?
75
+ friendship.destroy && friendship.destroyed?
76
+ end
77
+
78
+ # returns the list of approved friends
79
+ def friends
80
+ self.reload
81
+ self.invited + self.invited_by
82
+ end
83
+
84
+ # total # of invited and invited_by without association loading
85
+ def total_friends
86
+ self.invited(false).count + self.invited_by(false).count
87
+ end
88
+
89
+ # blocks a friendship
90
+ def block(user)
91
+ friendship = find_any_friendship_with(user)
92
+ return false if friendship.nil? || !friendship.can_block?(self)
93
+ friendship.update_attribute(:blocker, self)
94
+ end
95
+
96
+ # unblocks a friendship
97
+ def unblock(user)
98
+ friendship = find_any_friendship_with(user)
99
+ return false if friendship.nil? || !friendship.can_unblock?(self)
100
+ friendship.update_attribute(:blocker, nil)
101
+ end
102
+
103
+ # returns the list of blocked friends
104
+ def blocked
105
+ self.reload
106
+ self.blockades + self.blockades_by
107
+ end
108
+
109
+ # total # of blockades and blockedes_by without association loading
110
+ def total_blocked
111
+ self.blockades(false).count + self.blockades_by(false).count
112
+ end
113
+
114
+ # checks if a user is blocked
115
+ def blocked?(user)
116
+ blocked.include?(user)
117
+ end
118
+
119
+ # checks if a user is a friend
120
+ def friend_with?(user)
121
+ friends.include?(user)
122
+ end
123
+
124
+ # checks if a current user is connected to given user
125
+ def connected_with?(user)
126
+ find_any_friendship_with(user).present?
127
+ end
128
+
129
+ # checks if a current user received invitation from given user
130
+ def invited_by?(user)
131
+ friendship = find_any_friendship_with(user)
132
+ return false if friendship.nil?
133
+ friendship.friendable_id == user.id
134
+ end
135
+
136
+ # checks if a current user invited given user
137
+ def invited?(user)
138
+ friendship = find_any_friendship_with(user)
139
+ return false if friendship.nil?
140
+ friendship.friend_id == user.id
141
+ end
142
+
143
+ # return the list of the ones among its friends which are also friend with the given use
144
+ def common_friends_with(user)
145
+ self.friends & user.friends
146
+ end
147
+
148
+ # returns friendship with given user or nil
149
+ def find_any_friendship_with(user)
150
+ friendship = Amistad.friendship_class.where(:friendable_id => self.id, :friend_id => user.id).first
151
+ if friendship.nil?
152
+ friendship = Amistad.friendship_class.where(:friendable_id => user.id, :friend_id => self.id).first
153
+ end
154
+ friendship
155
+ end
156
+ end
157
+ end