Accessing class and module variables from a new class!

Hello,

I continue my training on classes and modules.

I understood that it is possible to make a class Inheritance with the method “Class1 < Class 2
For modules I must use the “Include Module” or “Extend Module” method.

I would now like to have access to the methods of a module and a class at the same time in a new class to display the results included in two variables:

module JulienDufren
  class Class1
    def self.variable1
	  a = 7
	  if a > 2
        @value1 = 10
      else
	    @value1 = 20
	  end
    end
  end
  module Module1
    def self.variable2
	  a = 0
	  if a > 2
        @value2 = 10
      else
	    @value2 = 20
	  end
    end 	
  end
  class Class2
    # How to retrieve @value1 and @value2?
  end  
end

How to retrieve @value1 and @value2 as simply as possible?

I found a solution but it seems to me too complex to be the best:

module JulienDufren
  class Class1
    def self.variable1(val1)
	  a = 8
	  if a > 2
        @value1 = 10
      else
	    @value1 = 20
	  end
	  val1 << @value1
    end
  end
  module Module1
    def self.variable2(val2)
	  a = 0
	  if a > 2
        @value2 = 10
      else
	    @value2 = 20
	  end
	  val2 << @value2
    end 	
  end
  class Class2
    def self.gets
	  val1 = []
	  Class1.variable1(val1)
	  p val1[0]
	  val2 = []
      Module1.variable2(val2) 
	  p val2[0]
    end	 
	gets
  end  
end

Thank you in advance for your help.

You were using a class variable in Class1, not an instance variable. Classes exist for being instantiated. If you never instantiate them, you could use a module instead.

Note also that you should now rename variable1 (and 2) to method1 or function1 because it is not at all a variable, and misnaming things makes it confusing for everyone. A variable stores data (and Ruby calls them “reference” which points to the data) but does not have any statements that do an action.

In Ruby, attributes (@value1, @value2) are always private. The only way to access them from outside is not to make them public but to add a public accessor method that sets or gets the value.

In your example, that would mean adding a module function to Module1 (analog a class method to Class1). Naively, that would look like:

  module Module1
    def self.value2
      return @value2
    end
    # If you want other code to modify the value:
    # def self.value2=(new_value)
    #   @value2 = new_value
    # end
  end

Since this is a lot of boilerplate, you instead want to let Ruby generate these methods automatically (metaprogramming). Better use this:

  module Module1
    attr_reader :value2
    # or:
    # attr_writer :value2
    # or for both:
    # attr_accessor :value2
  end

For classes with class variables, it is:

  class Class1
    class << self
      attr_reader :value1
    end
  end

Assuming your class had an instance variable @value3, it is:

  class Class1
    attr_reader :value3
  end

The advantage of getter methods is:

  • Flexibility: You can preserve the method’s interface unchanged (that other code already uses) but update its implementation for example if the attribute has been renamed, its type has been changed etc.
  • Encapsulation: Other code can by mistake or maliciously modify the object that it gets from the getter and then your class may behave differently or throw errors. Having a getter method, you could safe-guard against this by returning a copy of the attribute’s object.
    If you have a setter method, other code can by mistake or maliciously set an invalid or incompatible object, which may break your class. By having a setter method, you can add code in it that validates the assigned value.

Continuing …

With attributes in a module, it can be used as a mixin module, and with attributes in a superclass the subclass can inherit them.

module JulienDufren

  module Module1
    attr_accessor :value1
  end

  class Class1
    class << self
      attr_accessor :value2 # class variable
    end
    attr_accessor :value3 # instance variable
    def initialize
      self.class.value2= 2.0
      @value3 = 3.0
    end
  end

  class Class2 < Class1
    include Module1
    def initialize
      super # calls the superclass' initializer
      @value1 = 1.0
    end
    def get
      puts "@value1 : #{value1()}"
      puts "@value2 : #{self.class.value2()}"
      puts "@value3 : #{value3()}"
    end
  end

end

Notice how (in the above example,) Module1 is not mixed into Class1.
This means that instances of it will not have access to the @value1 attribute (instance method,) and it does not have getter or setter methods inherited by proxy.

But instances of Class2 will have access to the @value1 attribute and be able to use the accessor methods value1() and value=() because Module1 gets inserted into Class2's ancestry chain as a pseudo superclass when using include().
They will also have access to Class1's class attribute @value2 via the class accessor methods and instance attribute @value3 via the instance accessor methods that it inherits by being a subclass.

NOTE ALL instance and class variables should be initialized before they are accessed. This is usually done in the class constructor, ie, the initialize() method. If you do not initialize them, they point at nil, and can also cause a warning to clutter the console if the value is attempted to be read.

Also, keep in mind that class variables (@value2 above) will be shared by all instances of Class1, Class2 and any subsequent subclasses created later. If one instance of Class2 changes the value, it changes for all as this is a “family attribute”.

Run:

c2 = JulienDufren::Class2.new
c2.get
#=> @value1 : 1.0
#=> @value2 : 2.0
#=> @value3 : 3.0

Thank you for this information.

Despite a short period of study on the Class and the Modules, I admit that I have a little trouble understanding all your information.

For the Class I will use “attr_accessor” as Aérilius advised me.

For the modules I will use a hash to allow me to register several local variables.

Currently my example code looks like this:

module JulienDufren
  class Class1
    attr_accessor :value1
    def methode1
	  a = 8
	  if a > 2
        @value1 = 10
      else
	    @value1 = 20
	  end
    end
  end
  module Module1
    def self.methode2(hash)
	  a = 0
	  if a > 2
        hash[:value2] = 10
      else
	    hash[:value2] = 20
	  end
    end 	
  end
  class Class2
    class1 = Class1.new
	class1.methode1
	p "VALUE 1 = #{class1.value1}"
	hash = {}
	Module1.methode2(hash)
	p "VALUE 2 = #{hash[:value2]}"
  end  
end

Your use of Classes is weird.

If you want access to Class1's methods from Class2, just make the latter a subclass.

Again, instance variables now need to be initialized. This is nest done in the initialize method of the class, which executes in the new instance’s context.

Keep things simple. Do not mix in Modules. Do not inherit. These do speed up writing code but will hurt you when maintaining it.

When programming, in any object oriented language, try to stick to SOLID.

1 Like

I’m sorry Dan I’m doing the best I can!

Believe me, I really want to have good practices from the start of my apprenticeship.
I often post my codes on this forum to receive constructive feedback.
If I don’t apply everything I’ve been told the first time, it’s because I haven’t been able to understand what has been said to me.

Here is an example of module and class organization that apply what I understood on various tutorials and on this forum:

module JulienDufren

  module UserObserver
  
    class LanguageObserver
	
      attr_accessor :language
	  
      def initialize(lang)
        @language = lang
      end	
	  
	  def user_language_methode
	    @language = Sketchup.os_language
	  end
	  
    end
    class UnityObserver
	
      attr_accessor :unitie
	  
      def initialize(uni)
        @unitie = uni
      end
	  
      def user_unity_methode
        @unitie = "INCHES"
	  end
	  
    end 
  end
  module MyPlugin
  
    def self.user_language
      langObserver = UserObserver::LanguageObserver.new("")
      langObserver.user_language_methode
	  @language = langObserver.language
    end
	
    def self.user_unity
      unityObserver = UserObserver::UnityObserver.new("")
      unityObserver.user_unity_methode
      @unity = unityObserver.unitie
    end
	
    def self.user_information
      user_language
      user_unity
      p "USER LANGUAGE = #{@language}"
      p "USER UNITY = #{@unity}"
    end
    user_information
	
  end
end  

I believe it would be helpful if you would define a specific usecase.
What would you like to achieve? Once you have that defined it is easier for us to guide you through the path on how to get there.

An observer is an object (class or module) that watches something else and reacts to changes.

The modules and classes in this example are not observers.


This is true rule. For example …

    class LanguageObserver
	
      attr_accessor :language
	  
      def initialize(lang)
        @language = lang
      end	
	  
	  def user_language_methode
	    @language = Sketchup.os_language
	  end
	  
    end

… is a perfect example of what NOT to do in object oriented programming.

Meaning, you do not need an entire class to encapsulate the setting of a language reference.
A simple @langauge variable can be used, or your plugin submodule can hold a hash of settings.

module JulienDufren

  module SomePlugin

    @settings ||= {
      :language => Sketchup.os_language,
      # other settings ...
    }

But the idea of saving a reference to the user language is unnecessary as it cannot be changed by the API and is already set by the time any extension loads. Your code can get the value at any time by calling the Sketchup.os_language module method.

Anyway, back to main point about object oriented programming. Do not waste time coding classes that hold state if a simple data structure like an Hash, OpenStruct, or a Struct will do. Otherwise your just wasting your own time and complicating your code.

I understand what you mean Dan.
But the purpose of this class is not only to determine the language of the user!
Many methods will be added to modify the language of the Plugin as well as the units of measurement.
My goal is to know how to properly organize my classes and my modules!