JmanxC JmanxC - 5 months ago 9
Ruby Question

Modules and Accessing Variables from Modules (Ruby Language)

I have the following Module which has 1 variable which contains a string for the first day of a hypothetical year, 1 method which outputs a string and another method which also outputs a string:


module Week
first_day = "Sunday"

def weeks_in_month
puts "There are 4 weeks in a month"
end

def weeks_in_year
puts "There are 52 weeks in a year"
end
end


I now have a class who's only purpose is to print out the variable located in the module.(this is just for testing purposes)

class Decade
include Week

def firstday
puts Week::first_day
end
end


I now instantiate Decade and access the methods located in the module using Decades object. My program runs into a problem when calling the firstday method


z = Decade.new
z.weeks_in_month
z.weeks_in_year

z.firstday #Errors here


The error I get is:

undefined method `first_day' for Week:Module (NoMethodError)


I am new to Ruby and am just getting used to Modules, so any help would be appreciated.

Answer

When writing a module the convention is to declare constants like this:

module Week
  FIRST_DAY = 'Sunday'
end

Note that they're in ALL_CAPS. Anything that begins with a capital letter is treated as a constant. Lower-case names of that sort are treated as local variables.

Generally it's bad form to access the constants of another module, it limits your ability to refactor how those are stored. Instead define a public accessor method:

module Week
  def first_day
    FIRST_DAY
  end
end

Now you can call that externally:

Week.first_day

Note you can also change how that's implemented:

module Week
  DAYS = %w[
    Sunday
    Monday
    Tuesday
    ...
    Saturday
  ]

  def first_day
    DAYS.first
  end

  extend self # Makes methods callable like Week.first_day
end

The nice thing about that is the first_day method does exactly the same thing, no other code has to change. This makes refactoring significantly easier. Imagine if you had to track down and replace all those instances to Week::FIRST_DAY.

There's some other things to note here. The first is that any time you call include on a module then you get the methods and constants loaded in locally. The second thing is when you define a mix-in module, be careful with your names to avoid potential conflict with the target class.

Since you've mixed it in, you don't need the namespace prefix, just calling first_day should do it.