I am struggling with Rubocop's Style/ClassVars rule. It wants me to Replace class var ##mutex with a class instance var. Here is the code, it performs some lazy-initialization which is quite slow:
FastGettext.class_eval do
attr_writer :mutex
attr_accessor :locales
def mutex
# Style/ClassVars: Replace class var ##mutex with a class instance var.
##mutex ||= Mutex.new
end
def human_available_locales
original_locale = FastGettext.locale
mutex.synchronize do
return locales if locales
# perform loading and translating of locale names
# (removed)
locales
end
ensure
FastGettext.locale = original_locale
end
end
Rails had a nice helper attr_accessor_with_default which allowed to define an accessor with a default value, but it has been deprecated. Use Ruby instead, says the deprecation message. I am stuck, I really don't know how this code should looks like to satisfy Rubocop. Normally, attributes are initialized in the constructor but this is a class context. I need to initialize the mutex, ideally during class loading.
My initial implementation simply had ##mutex and ##locales and I have no idea why Rubocop pushes so hard on this. I know that accessors are convinient for overloading, but I am aware of this. Either I am missing something here or this is a really bad cop to be enabled by default.
Thanks for help
Edit: Here is one solution that still looks weird, but it works:
FastGettext.class_eval do
attr_accessor :mutex, :locales
self.mutex = Mutex.new
def human_available_locales
original_locale = FastGettext.locale
mutex.synchronize do
return locales if locales
# ...
locales
end
ensure
FastGettext.locale = original_locale
end
end
RuboCop is right. Suppose you have an inherited class like this:
class VeryFastGettext < FastGettext
end
Now VeryFastGettext shares the same mutex lock as the base class. This is rarely a desired behavior.
Here's the code that can appease RuboCop while being reasonable:
FastGettext.class_eval do
def self.mutex
#mutex ||= Mutex.new
end
def mutex
self.class.mutex
end
end
Related
Given that I have an abstract class which provides inherited functionality to subclasses:
class Superclass
class_attribute :_configuration_parameter
def self.configuration_parameter config
self._configuration_parameter = config
end
def results
unless #queried
execute
#queried = true
end
#results
end
private
# Execute uses the class instance config
def execute
#rows = DataSource.fetch self.class._configuration_parameter
#results = Results.new #rows, count
post_process
end
def post_process
#results.each do |row|
# mutate results
end
end
end
Which might be used by a subclass like this:
class Subclass < Superclass
configuration_parameter :foo
def subclass_method
end
end
I'm having a hard time writing RSpec to test the inherited and configured functionality without abusing the global namespace:
RSpec.describe Superclass do
let(:config_parameter) { :bar }
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
let(:test_instance) do
test_subclass.new
end
describe 'config parameter' do
it 'sets the class attribute' do
expect(test_subclass._configuration_parameter).to be(config_parameter)
end
end
describe 'execute' do
it 'fetches the data from the right place' do
expect(DataSource).to receive(:fetch).with(config_parameter)
instance.results
end
end
end
The real world superclass I'm mocking here has a few more configuration parameters and several other pieces of functionality which test reasonably well with this pattern.
Am I missing something obviously bad about the class or test design?
Thanks
I'm just going to jump to the most concrete part of your question, about how to avoid using a global variable to pass a local parameter to the dummy class instantiated in your spec.
Here's your spec code:
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
If you take the value returned from Class.new you can call configuration_parameter on that with the local value and avoid the global. Using tap does this with only a minor change to your existing code:
let(:test_subclass) do
Class.new(SuperClass).tap do |klass|
klass.configuration_parameter config_parameter
end
end
As to the more general question of how to test functionality inherited from a superclass, I think the general approach of creating a stub subclass and writing specs for that subclass is fine. I personally would make your _configuration_parameter class attribute private, and rather than testing that the configuration_parameter method actually sets the value, I'd instead focus on checking that the value is different from the superclass value. But I'm not sure that's in the scope of this question.
I want to initialize my_attr_reader. Changing attr_reader to cattr_reader doesn't help because there is such a method cattr_reader for some reason.
How can I do that?
module Mod1
def method1
puts "method1 from Mod1"
end
end
MyClass = Object.new
class << MyClass
include Mod1
attr_reader :my_attr_reader
my_attr_reader = "111" # doesn't get initialized
def initialize
self.my_attr_reader = "123" # doesn't get initialized
end
def my_class1_method1
puts "MyClass method1"
end
end
MyClass.my_class1_method1
MyClass.method1
p MyClass.my_attr_reader # nil
P.S. Why does include work here, whereas extend doesn't, even so it should be exactly the opposite?
You have a few problems here, so I'll split this up into sections.
A note about cattr_reader
cattr_reader would probably be useful for you, but it is a part of Rails, not Ruby. You will not be able to use this in Ruby code without first including the right parts of Rails.
How to use attr_reader
Your main problem here is that attr_reader and cattr_reader create instance and class variables respectively, but you are using local variables instead. Instance variables start with #, and class variables start with ##. Class variables have odd and confusing behaviors, and cattr_reader isn't built into Ruby as I mentioned above, so I would recommend using attr_reader on the class level.
Why does include work here, whereas extend doesn't, even so it should be exactly the opposite?
extend adds in class-level methods, while include includes instance methods. You are defining method as an instance method of Mod1, so you should be using include.
Is there any clean way to initialize instance variables in a Module intended to be used as Mixin? For example, I have the following:
module Example
def on(...)
#handlers ||= {}
# do something with #handlers
end
def all(...)
#all_handlers ||= []
# do something with #all_handlers
end
def unhandled(...)
#unhandled ||= []
# do something with unhandled
end
def do_something(..)
#handlers ||= {}
#unhandled ||= []
#all_handlers ||= []
# potentially do something with any of the 3 above
end
end
Notice that I have to check again and again if each #member has been properly initialized in each function -- this is mildly irritating. I would much rather write:
module Example
def initialize
#handlers = {}
#unhandled = []
#all_handlers = []
end
# or
#handlers = {}
#unhandled = []
# ...
end
And not have to repeatedly make sure things are initialized correctly. However, from what I can tell this is not possible. Is there any way around this, besides adding a initialize_me method to Example and calling initialize_me from the extended Class? I did see this example, but there's no way I'm monkey-patching things into Class just to accomplish this.
module Example
def self.included(base)
base.instance_variable_set :#example_ivar, :foo
end
end
Edit: Note that this is setting a class instance variable. Instance variables on the instance can't be created when the module is mixed into the class, since those instances haven't been created yet. You can, though, create an initialize method in the mixin, e.g.:
module Example
def self.included(base)
base.class_exec do
def initialize
#example_ivar = :foo
end
end
end
end
There may be a way to do this while calling the including class's initialize method (anybody?). Not sure. But here's an alternative:
class Foo
include Example
def initialize
#foo = :bar
after_initialize
end
end
module Example
def after_initialize
#example_ivar = :foo
end
end
Perhaps this is a little hacky, but you can use prepend to get the desired behavior:
module Foo
def initialize(*args)
#instance_var = []
super
end
end
class A
prepend Foo
end
Here is the output from the console:
2.1.1 :011 > A.new
=> #<A:0x00000101131788 #instance_var=[]>
modules provides hooks, as Module#included. I suggest you check out ruby doc on the topic, or use ActiveSupport::Concern, which provides some helpers on modules.
I think there may be a simpler answer to this. The module should have an initializer that initialises the variables as you normally would do. In the initializer for the class that includes the module, invoke super() to invoke the initializer in the included module. This is simply following the method dispatch rules in Ruby.
On reflection, this will not work so well if the class including the module also has a superclass that needs to be initialised. The initializer in the module would need to accept a variable parameter list and pass this up to the superclass. It looks like a good avenue to explore though.
I'm writing a Ruby library which has a module with a bunch of classes inside it. Many of these classes need to be usable and modifiable by calling scripts, but I don't want (some of) the initializers to be visible/callable:
module MyLib
class Control
def initialize
# They can use this
end
def do_stuff
Helper.new('things')
end
end
class Helper
# Shouldn't be visible
def initialize(what)
#what = what
end
def shout
#what
end
end
end
c = MyLib::Control.new
h = c.do_stuff
p h.shout
# => "things"
# ^ All of this is desired
# v This is undesirable
p MyLib::Helper.new('!')
# => <MyLib::Helper #what='!'>
If it's a simple thing, then I'd also appreciate the generated RDoc not even include the .new method for the Helper class either. Any ideas?
Thanks for reading!
My original answer was completely wrong, as #Matthew pointed out. But there are other workarounds. For instance, you can assign an anonymous class to a class variable on Control, and still define methods as normal by using class_eval:
module MyLib
class Control
def initialize
end
def do_stuff
##helper.new('things')
end
##helper = Class.new
##helper.class_eval do
def initialize(what)
#what = what
end
def shout
#what
end
end
end
end
The snippet
c = MyLib::Control.new
h = c.do_stuff
p h.shout
still writes "things", but now there's no way to access ##helper except through the class variable. If someone really wants to access it my reopening the Control class or using class_eval, there's nothing to stop them, but that's just something you have to deal with in a dynamic language.
I chose to assign the anonymous class to a class variable so that it would only be created once; but if you don't care about redefining the anonymous class many times, there's no reason it couldn't be an instance variable.
Ruby has access control.
I have a class that contains some private attributes. What I would like to do is to dynamically add some setters for these only for the execution of a specific block.
Example of what I would like to be able to:
class Content
attr_reader :a, :b
def initialize
#a = 1
#b = "plop"
end
def set(&block)
extend(Setter)
instance_eval(&block)
unextend(Setter) ????
end
module Setter
def a(value)
#a = value
end
def b(value)
#b = value
end
end
end
content = Content.new
content.set do
a 2
b "yeah!"
end
content.a # should return 2
EDIT: Thanks for the great answers so far. I clarified the question because I actually need to define attribute readers in the class itself that may conflict with the setters defined in the module. I forgot about this part when posting the question. (It was late ^^)
CLARIFICATION: This class is intended for a DSL to write a configuration file. It is targeted at non-developer so the less operators, the better.
I currently implement this using a proxy class that instance_eval the block but I have to mess with instance_variable_set in order to set the values and I don't like it. I am just trying another way to see if I can make my code more readable.
There's no native way to "unextend" modules in Ruby. The mixology gem implements this pattern as a C (and Java, for JRuby) extension, creating mixin and unmix methods. It appears you may need to apply a patch if you need Ruby 1.9 support, however.
If you'd prefer to avoid using third-party libraries, another approach might simply be to make the setters private:
class Content
def initialize
#a = 1
#b = "plop"
end
def set(&block)
instance_eval(&block)
end
private
def a(val)
#a = val
end
def b(val)
#b = val
end
end
content = Content.new
#This will succeed
content.set do
a 2
b "yeah!"
end
# This will raise a NoMethodError, as it attempts to call a private method
content.a 3
def set(&block)
extend(Setter)
instance_eval(&block)
Setter.instance_methods.each do |m|
instance_eval "undef #{m}"
end
end
I don't know of any method that would do that for you although there might be something.. This should do the job though, by finding all the instance methods of Setter and undefining them in Content.
You could use _why's mixico library (available on github)
It would let you do this:
require 'mixology'
#...
def set(&block)
Setter.mix_eval(Setter, &block)
end
The mixology gem does much the same thing, just slightly differently.
if you're feeling in an experimental mood also check out: dup_eval
It's similar in some ways to mixico but with some interesting extras (object2module)