1

I'm making a concern, but I want to limit what the Class that is getting it can see. For example, I don't want anyone to use PRIVATE_FOO or PRIVATE_BAR separately, I just want them to use them together in PUBLIC_FOOBAR. Is this possible?

module MyConcern
  extend ActiveSupport::Concern

  ##This should not be visible in the class it is included in:
  PRIVATE_FOO = 'foo'
  PRIVATE_BAR = 'bar'

  
  ##This should be visible to the class it is included in:
  PUBLIC_FOOBAR = PRIVATE_FOO + PRIVATE_BAR

end

class MyClass < ApplicationRecord
  include MyConcern
  
  PRIVATE_FOO # NameError Exception
  PRIVATE_BAR # NameError Exception

  PUBLIC_FOOBAR # "foobar"

end
Ashbury
  • 2,160
  • 3
  • 27
  • 52
  • You can find your answer her https://stackoverflow.com/questions/2873903/how-to-i-make-private-class-constants-in-ruby. – Prathamesh Vichare Aug 06 '21 at 01:10
  • in `MyConcern` did you mean to write `PUBLIC_FOOBAR = PRIVATE_FOO + PRIVATE_BAR` ? b/c variables `foo` and `bar` are not defined. – Les Nightingill Aug 06 '21 at 01:31
  • indeed! updated – Ashbury Aug 06 '21 at 01:51
  • @PrathameshVichare - I can still see @@PRIVATE_FOO from within MyClass, same with using private_constant. – Ashbury Aug 06 '21 at 02:03
  • @Ashbury there is nothing that is truly private in ruby. Marking something as "private" is more of a warning that this is an internal concern but it does not make it truly private. It's a bit like putting up a fence, it suggests you should stop here and even makes it more difficult to proceed but it certainly does not completely prevent access. – engineersmnky Aug 06 '21 at 03:10
  • `@@PRIVATE_FOO` is not a constant. Its a class variable. Ruby will only treat an identifier as a constant if it starts with an uppercase letter which is not any of the sigils - `@`, `@@`, `$` . – max Aug 06 '21 at 11:07

2 Answers2

1

Constants in Ruby are always public so this is not possible.

If you want to limit the visibility you should just use local variables:

module MyConcern
  extend ActiveSupport::Concern

  foo = 'foo'
  bar = 'bar'
  
  FOOBAR = foo + bar
end

Or instance variables:

module MyConcern
  extend ActiveSupport::Concern

  # these instance variables belong to the module itself
  @foo = 'foo'
  @bar = 'bar'
  
  FOOBAR = @foo + @bar
end

Or methods:

module MyConcern

  private

  def self.foo
    'foo'
  end

  def self.bar
    'bar'
  end

  FOOBAR = foo + bar
end
Ashbury
  • 2,160
  • 3
  • 27
  • 52
max
  • 96,212
  • 14
  • 104
  • 165
0

The problem is that ActiveSupport::Concern is a bit too liberal for your scenario. The following achieves what I think you're looking for. But it's definitely not very elegant!

module MyConcern
  def self.included(base)
    base.class_eval do
      const_set(:PUBLIC_FOOBAR, ClassMethods::PRIVATE_FOO + ClassMethods::PRIVATE_BAR)
    end
  end

  module ClassMethods
    PRIVATE_FOO = "foo"
    PRIVATE_BAR = "bar"
  end

end

class MyClass
  include MyConcern

  def self.get
    PUBLIC_FOOBAR
  end

  def self.get_private
    PRIVATE_FOO
  end
end

MyClass.get #=> 'foobar'

MyClass.get_private #=> uninitialized constant MyClass::PRIVATE_FOO (NameError)

However there is still a way that MyClass can get the private constants, using ClassMethods::PRIVATE_FOO. Maybe you can live with that?

Les Nightingill
  • 5,662
  • 1
  • 29
  • 32