2
class Event

    @event_list = {}

    attr_reader :name, :value

    def initialize(name, value)
      @name  = name
      @value = value
    end

    def to_s
      "#{@value}"
    end

    class << self

      def event_list
          @event_list
      end

      def event_list=(value); end

      def register_event(name, value)
          @event_list[name] = Event.new(name, value)
      end

      def registered_events
          event_list
      end
    end
end

In the above code snippet I can access @event_list using Event.event_list, interesting thing is I am able to modify this variable from outside

Event.event_list[:name] = "hello"
Event.event_list  # => { :name => 'hello' }

How can I avoid this ?, I don't want to modify @event_list from outside.

Deepak
  • 341
  • 1
  • 10

4 Answers4

1

As far as I know, you can't stop outside code from modifying your instance variables in Ruby. Even if you don't use attr_reader and attr_writer it can still be accessed using Object#instance_variable_set. Ruby doesn't have private variables (or constants), only variables that you are politely asked not to modify.

If you don't define event_list=, that is seen as an indication that @event_list is a private variable. This is the solution to your problem.

Then there is the problem with mutable objects. Since almost all objects in Ruby sadly are mutable, usually if you can just get a reference to an object then you can change it.

This can be solved with Object#freeze which stops an object from being modified. Unfortunately this means that not even you can modify it.

Ruby is simply not very good for locking things down. This openness is a core part of the language that you probably need to learn to work with.

jforberg
  • 6,537
  • 3
  • 29
  • 47
  • event if I just add *attr_reader :event_list* instead of writing reader and writer methods I can modify the variable. In a class I can make an instance variable unwritable by just using attr_reader, If a class is an object then I should be able to make its instance variable unwriteable. – Deepak Apr 22 '15 at 18:08
1

As others have said, just make the methods private:

class Event
    @event_list = {a: 'dog'}
    class << self
      def pub_event_list
        @event_list
      end  
      def pub_event_list=(other)
        @event_list=other
      end  
      private
      def event_list
        @event_list
      end
      def event_list=(value)
        @event_list = value
      end
    end
end

Event.event_list
  #=> NoMethodError: private method `event_list' called for Event:Class

Event.pub_event_list
  #=> {:a=>"dog"} 

Event.event_list= {b: 'cat'}
  #=> #NoMethodError: private method `event_list=' called for Event:Class

Event.pub_event_list= {b: 'cat'}
  #=> {:b=>"cat"} 
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

You are defining def event_list on your singleton which is providing access to the variable. Make that method private

EDIT:

When you are declaring @event_list it has scope in your singleton methods.

class Event
  @event_list = {}

  class << self
    def event_list
      # scoped from above
      @event_list
    end
  end
end

Event.event_list #=> {}

To remove access simply make the method private:

class Event
  @event_list = {}

  class << self
    private

    def event_list
      # scoped from above
      @event_list
    end
  end
end

Event.event_list #=> NoMethodError: private method `event_list' called for Event:Class
Josh Bodah
  • 1,223
  • 6
  • 17
  • I didn't understand could you please explain a bit? – Deepak Apr 22 '15 at 18:02
  • @Deepak `event_list` returns the reference. Once you have the reference you can modify it. – Dave Newton Apr 22 '15 at 18:08
  • ok, but if I just use attr_reader :event_list then also I can access it and modify it. – Deepak Apr 22 '15 at 18:11
  • Ah I misread the question. You probably want to make a copy of the data with `deep_clone` or something and return that so the original variable can't be modified – Josh Bodah Apr 22 '15 at 18:17
  • Even if the reader is made private I need to access the value ourside, the method **registered_events** must return the events outside, freezing seems ok but that means I have to freeze and unfreeze frequently. – Deepak Apr 22 '15 at 18:18
0

Here is what I done for now, I don't know if it is a good solution

class Event

  @event_list = {}

  attr_reader :name, :value

  def initialize(name, value)
    @name  = name
    @value = value
  end

  def to_s
    "#{@value}"
  end

  class << self
    def register_event(name, value)
      @event_list = @event_list.merge(name => Event.new(name, value))
    end

    def registered_events
      @event_list.freeze
    end
  end
end

Now I can access @event_list without letting others to modify.

Deepak
  • 341
  • 1
  • 10