Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ def has_pass?
!!@pass_id
end

def issue_pass!(pass_id)
def issue_pass(pass_id)
@pass_id = pass_id
end

def revoke_pass!
def revoke_pass
@pass_id = nil
end

Expand Down
4 changes: 2 additions & 2 deletions exercises/concept/amusement-park-improvements/attendee.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ def initialize(height)
@height = height
end

def issue_pass!(pass_id)
def issue_pass(pass_id)
@pass_id = pass_id
end

def revoke_pass!
def revoke_pass
@pass_id = nil
end

Expand Down
10 changes: 5 additions & 5 deletions exercises/concept/amusement-park-improvements/attendee_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ def test_new_instance_doesnt_have_pass

def test_when_issued_pass
attendee = Attendee.new(100)
attendee.issue_pass!(1)
attendee.issue_pass(1)
assert attendee.has_pass?
end

def test_when_revoked_doesnt_have_pass
attendee = Attendee.new(100)
attendee.issue_pass!(1)
attendee.revoke_pass!
attendee.issue_pass(1)
attendee.revoke_pass
refute attendee.has_pass?
end

Expand All @@ -37,13 +37,13 @@ def test_fits_ride_but_no_pass

def test_fits_ride_and_pass
attendee = Attendee.new(100)
attendee.issue_pass!(1)
attendee.issue_pass(1)
assert attendee.allowed_to_ride?(100)
end

def test_does_not_fit_ride_and_pass
attendee = Attendee.new(100)
attendee.issue_pass!(1)
attendee.issue_pass(1)
refute attendee.allowed_to_ride?(120)
end
end
4 changes: 2 additions & 2 deletions exercises/concept/amusement-park/.meta/exemplar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ def pass_id
@pass_id
end

def issue_pass!(pass_id)
def issue_pass(pass_id)
@pass_id = pass_id
end

def revoke_pass!
def revoke_pass
@pass_id = nil
end
end
8 changes: 4 additions & 4 deletions exercises/concept/amusement-park/attendee.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ def pass_id
raise 'Implement the Attendee#pass_id method'
end

def issue_pass!(pass_id)
raise 'Implement the Attendee#issue_pass! method'
def issue_pass(pass_id)
raise 'Implement the Attendee#issue_pass method'
end

def revoke_pass!
raise 'Implement the Attendee#revoke_pass! method'
def revoke_pass
raise 'Implement the Attendee#revoke_pass method'
end
end
6 changes: 3 additions & 3 deletions exercises/concept/amusement-park/attendee_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_issue_pass
attendee = Attendee.new(height)

pass_id = 1
attendee.issue_pass!(pass_id)
attendee.issue_pass(pass_id)

assert_equal pass_id, attendee.pass_id
end
Expand All @@ -31,8 +31,8 @@ def test_has_pass_after_revoked
height = 100
attendee = Attendee.new(height)
pass_id = 1
attendee.issue_pass!(pass_id)
attendee.revoke_pass!
attendee.issue_pass(pass_id)
attendee.revoke_pass
refute attendee.pass_id
end
end
2 changes: 1 addition & 1 deletion exercises/concept/moviegoer/.meta/exemplar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def watch_scary_movie?
age >= 18
end

def claim_free_popcorn!
def claim_free_popcorn
raise NotMovieClubMemberError unless member

"🍿"
Expand Down
2 changes: 1 addition & 1 deletion exercises/concept/moviegoer/moviegoer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def watch_scary_movie?
end

# Popcorn is 🍿
def claim_free_popcorn!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bang indicates dangerous, as in may raise an exception.

Copy link
Author

@orien orien Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All methods may raise an exception. I don't believe the bang has a definitive connection with raising errors.

Many Rails methods take this differentiation in behaviour when compared to their non-bang counterpart. But even the non-bang methods raise errors under the right circumstances. E.g. ActiveRecord's save vs save! can both raise an exception if the Database goes down or the Ruby process runs out of memory.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, those are both exceptional behaviors, though. And exceptions are for exceptional behavior.

Contrary to some of our exercises, even. This exercise, even, as it is arguably using the exception as flow control. I am aware of this, yet the discussions happen in mentoring (at least I discuss this during mentoring).

def claim_free_popcorn
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be very surprising to have an assertion come up, but the tests require that an exception is raised, so the bang is appropriate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, all Ruby methods can raise errors. It is not surprising, but it is exceptional!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed.

raise 'Please implement the Moviegoer#claim_free_popcorn method'
end
end
4 changes: 2 additions & 2 deletions exercises/concept/moviegoer/moviegoer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ def test_kids_cant_see_the_scary_movie
end

def test_members_get_free_popcorn
assert_equal "🍿", Moviegoer.new(25, member: true).claim_free_popcorn!
assert_equal "🍿", Moviegoer.new(25, member: true).claim_free_popcorn
end

def test_regular_moviegoers_dont_get_free_popcorn
assert_raises NotMovieClubMemberError do
Moviegoer.new(25, member: false).claim_free_popcorn!
Moviegoer.new(25, member: false).claim_free_popcorn
end
end
end
24 changes: 12 additions & 12 deletions exercises/practice/gilded-rose/.meta/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ def initialize(name:, sell_in:, quality:)

def conjured? = @conjured

def update!
update_quality!
def update
update_quality
@quality = 0 if quality < 0
@quality = 0 if conjured? && sell_in <= 0
@quality = max_quality if quality > max_quality

update_sell_in!
update_sell_in
end

private

def max_quality = 50

def update_quality! = raise NotImplementedError
def update_quality = raise NotImplementedError

def update_sell_in! = @sell_in -= 1
def update_sell_in = @sell_in -= 1
end

class NormalItem < AbstractItem
def self.name_regex = /.+/ # Should be checked last, after other item types.

private

def update_quality!
def update_quality
quality_change = -1
quality_change -= 1 if sell_in <= 0
quality_change *= 2 if conjured?
Expand All @@ -45,7 +45,7 @@ def self.name_regex = /Aged Brie/

private

def update_quality!
def update_quality
quality_change = 1
quality_change += 1 if sell_in <= 0

Expand All @@ -60,10 +60,10 @@ def self.name_regex = /Sulfuras, Hand of Ragnaros/

def max_quality = 80

def update_quality!
def update_quality
end

def update_sell_in!
def update_sell_in
@sell_in -= 1 if conjured?
end
end
Expand All @@ -73,7 +73,7 @@ def self.name_regex = /Backstage passes to a TAFKAL80ETC concert/i

private

def update_quality!
def update_quality
@quality = 0 and return if sell_in <= 0

quality_change = 1
Expand Down Expand Up @@ -103,9 +103,9 @@ def initialize(items)
@items = items
end

def update!
def update
@items.each do |item|
item.update!
item.update
end
end
end
2 changes: 1 addition & 1 deletion exercises/practice/gilded-rose/gilded_rose.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def initialize(items)
@items = items
end

def update!
def update
@items.each do |item|
if item.name != "Aged Brie" && item.name != "Backstage passes to a TAFKAL80ETC concert"
if item.quality > 0
Expand Down
4 changes: 2 additions & 2 deletions exercises/practice/gilded-rose/gilded_rose_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
class GildedRoseTest < Minitest::Test
def update_with(sell_in:, quality:, name:, sell_in_change: -1, quality_change: -1)
item = Item.new(name:, sell_in:, quality:)
GildedRose.new([item]).update!
GildedRose.new([item]).update

assert_equal sell_in + sell_in_change, item.sell_in
assert_equal quality + quality_change, item.quality
Expand Down Expand Up @@ -239,7 +239,7 @@ def test_multiple_items
normal_item = Item.new(name: "some item", sell_in: 1, quality: 10)
aged_brie = Item.new(name: "Aged Brie", sell_in: -1, quality: 10)

GildedRose.new([normal_item, aged_brie]).update!
GildedRose.new([normal_item, aged_brie]).update

assert_equal 9, normal_item.quality
assert_equal 0, normal_item.sell_in
Expand Down
2 changes: 1 addition & 1 deletion exercises/practice/simple-linked-list/.meta/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def pop
element
end

def reverse!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there is no reverse without, this does mean "change in place". If the tests verify that it is a mutation, then the bang should stand.

Copy link
Author

@orien orien Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bang (!) does not mean “destructive” nor lack of it mean non destructive either.

There are many methods in the Ruby standard library that mutate the receiver and do not include an exclamation mark in the name. For example, String#concat, Array#pop, Hash#clear.

These are idiomatic because they have no counterpart method.

def reverse
previous = nil
pointer = @head
while pointer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,15 @@ def test_list_from_array_still_acts_as_lifo
assert_equal 3, element.datum
end

def test_list_in_place_reverse!
def test_list_in_place_reverse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the test name does not necessarily need the exclamation point. A not in place reverse method could also be tested. The bang here was only a literal reflection of the method name, without a way to fully indicate that.

This could be removed. The method itself will be indicated in the actual method use in the test.

skip
first = Element.new(1)
second = Element.new(2)
third = Element.new(3)
list = SimpleLinkedList.new
list.push(first).push(second).push(third)

assert_equal [1, 2, 3], list.reverse!.to_a
assert_equal [1, 2, 3], list.reverse.to_a
end

def test_list_in_place_reverse_are_the_same_elements
Expand All @@ -121,7 +121,7 @@ def test_list_in_place_reverse_are_the_same_elements
list = SimpleLinkedList.new
list.push(first).push(second)

list.reverse!
list.reverse

assert_equal first, list.pop
assert_equal second, list.pop
Expand All @@ -130,7 +130,7 @@ def test_list_in_place_reverse_are_the_same_elements
def test_list_reverse_empty_list
skip
list = SimpleLinkedList.new
assert_equal list, list.reverse!
assert_equal list, list.reverse
end

def test_works_for_1_through_10
Expand Down