Both the player and the alien enemies shoot bullets. These are not, however, represented by separate classes; they're represented by a single class. The type of the bullet is determined by which direction it's traveling. In this case (our Space Invaders clone) this is particularly convenient because the player's bullets are always traveling up and the alien's bullets are always traveling down.
The bullet class isn't anything new to you. It's a lot like the alien and player classes. It moves on its own and, if it goes off the screen, it deletes itself from the game (or "kills" itself). What the bullet class does not do is collision detection against the player and aliens. A bullet doesn't know anything about players and aliens. It just knows where it's going.
The bullets themselves are created either randomly by a timer from the aliens or when the player presses the space bar. Two new sprite groups are created to handle these bullets: a sprite group as a member of the EnemyFormation sprite group and a sprite group as a member of the Player class. This keeps everything organized and, again, keeps complexity out of the main loop.
Here's a snippet from the EnemyFormation class' update method. Every so often, this will let a bullet be fired from a random alien.
if (Clock.runtime - @last_fired) * 0.001 > FIRE_RATE a = self[ rand(length) ] @bullets.push Bullet.new( [ a.rect.centerx, a.rect.centery + 16 ], :enemy ) @last_fired = Clock.runtime end