Modules in Crystal serve two purposes:
The first purpose is to create a namespace to avoid name collisions. However, it also groups code, making it easier to understand what the code is for.
The second purpose is to define mixins to share code to types.
Modules are similar to classes, but the main difference is that they cannot be instantiated and, thus, don't have instance variables.
To declare a module, you use the module
keyword followed by the module's name.
module Foo
end
A namespace is a way to group code.
This avoids name clashes and makes it easier to understand what the code is for.
When wanting to access a constant or a class that has been placed inside a namespace, you use the ::
operator.
module Foo
 class Bar
 end
end
Foo::Bar.new
This can be useful when, for example, multiple classes need the same "base" functionality or when code needs to be shared between unrelated classes. Or when wanting to share code between classes that are not related.
There are two different ways to use a module as a mixin: the first is to use the include
keyword, and the second is to use the extend
keyword.
Both methods will make constants available to the type that includes or extends the module.
Include will make all methods in the module available as instance methods on the type that includes the module.
The include
keyword should be at the top of the type, followed by the module's name.
module Foo
 def foo
  "foo"
 end
end
class Bar
 include Foo
end
Bar.new.foo # => "foo"
Extend works similarly to include, but instead of making the methods available as instance methods, it makes them available as class methods.
The extend
keyword should be written at the top of the type, followed by the module's name.
module Foo
 def foo
  "foo"
 end
end
class Bar
 extend Foo
end
Bar.foo # => "foo"
A typical pattern in Crystal uses the extend self
pattern in a module.
This will make all methods in the module available as class methods on the module itself.
This means you don't have to assign each method to the module using the def self.method_name
syntax.
module Foo
 extend self
 def foo
  "foo"
 end
end
Foo.foo # => "foo"
Bellebrook Basketball League is a league of multiple teams, each with a roster of players. A new season is about to start, and they need to create a new ticketing system to sell tickets for the games. Last season, they sold tickets at the stadium's entrance, but popularity has exploded, and they have decided to sell tickets online this season to handle the increased demand.
The league has contacted you and asked you to create a system to handle ticket sales.
First, you need to create a ticketing system to handle ticket sales. The ticketing system needs to tell how many tickets are available for a game.
An instance of a class called TicketSystem
should be created for each game.
The initialized state should hold the following information:
Construct an initialized state from arguments indicating the number of tickets available and which stadium the match is played at.
The initialized state should consist of the instance variables @tickets_available
and @stadium
.
These should contain the first and second arguments given, respectively.
TicketSystem.new(100, "Bellebrook")
# => #<TicketSystem:0x10f0b8 @tickets_available=100, @stadium="Bellebrook">
First, you need to create a method that returns the amount of tickets available for a given game.
Define a module named TicketingReservation
above the already defined class.
This module should be included in the TicketSystem
class.
Inside the module, define a method called tickets_available
.
This method should return the amount of tickets available.
ticket_system = TicketSystem.new(100)
ticket_system.tickets_available
#=> 100
You must create a method that checks if tickets are available for a given game. The game needs to have 100 tickets in reserve. This is to ensure that there are always tickets available for the people who buy tickets at the stadium entrance.
Inside the module TicketingReservation
, define a method called order_ticket?
.
The method should return true
if at least 100 tickets are available and false
otherwise.
If at least 100 tickets are available, the method should also decrease the number of tickets available by 1.
ticket_system = TicketSystem.new(100)
ticket_system.order_ticket?
#=> true
The ticketing system has to have messages that are easy for users to understand. And the messages should feel personal, so they should include the user's name.
If the ticket purchase was successful, the user should get a message telling them that the purchase was successful, their ticket number, and which stadium the game is played at. The ticket number should be the number of tickets available before the order.
If the purchase was successful, the message should look like this: {name}, your purchase was successful, your ticket number is #{ticket_number}, and the game is played at the {stadium_number} stadium.
.
If the purchase was unsuccessful, the message should look like this: {name}, your purchase was unsuccessful, and there are not enough tickets available.
.
Inside the module TicketingReservation,
define a method called order_message
that takes an argument name
containing the purchaser's name.
The method should return a string with the message.
ticket_system = TicketSystem.new(100, "Bellebrook")
ticket_system.order_message("John")
#=> "John, your purchase was successful, your ticket number is #100, and the game is played at the Bellebrook stadium."
Sign up to Exercism to learn and master Crystal with 26 concepts, 133 exercises, and real human mentoring, all for free.