define local/private structs in ruby - ruby

I want to define a struct which is local to a file/class.
I.e., if I define struct foo in two different files, I want one to be applicable to file a, and one to file b.
Is that doable?

Just as idea: you can use modules or inner classes
module A
MyStruct = Struct.new(:x)
end
module B
MyStruct = Struct.new(:x)
end
or
class A
MyStruct = Struct.new(:x)
end
class B
MyStruct = Struct.new(:x)
end
This way you can use your structs independently as A::MyStruct and B::MyStruct

Just assign the struct class to a local variable (i.e. don't give it a class name).
my_struct = Struct.new(:x)
Caveat
You can't use the struct definition inside classes/modules/functions/methods in the same file.
my_struct = Struct.new(:x)
# This works
my_instance = my_struct.new(123)
class Foo
def foo
# This does NOT work
my_struct.new(123)
end
end
Workaround
You can use the metaprogramming trick called flat scope to mitigate the caveat, but if you decide to do that, you can no longer define methods and classes in the usual way in the whole file, and it's harder to reason about the code because the scope of a method is no longer a clean slate.
my_struct = Struct.new(:x)
Foo = Class.new do
define_method :foo do
# This works, too
my_struct.new(123)
end
end

Related

Struct undefined local vars. Where to define?

Twitchuserspoints = Struct.new(:name, :points)
xuser = ""
unless ($pointsstructarray.include? xuser.name)
xuser = Twitchuserspoints.new(##username, 100)
$pointsstructarray << xuser.name
$pointsstructarray << xuser.points
else
$pointsstructarray[$pointsstructarray.index(xuser.points)+1] += 1
end
Where to define method 'name'? Also method '+' in else is undefined?
Struct.new(:name, :points) declares a struct itself. To instantiate it, one might either assign the result of above to the variable (or a constant as you do,) and then call Struct#new method on it.
Twitchuserspoints = Struct.new(:name, :points)
x = Twitchuserspoints.new(nil, 0) # arguments are optional
# or, if you don’t want to reuse it
# x = Struct.new(:name, :points).new
x.name = "seasonalz"
x
#⇒ #<struct Twitchuserspoints name="seasonalz", points=0>
Sidenote: using global and class variables is considered a bad practice and anti-pattern in most modern languages, including ruby.
Preliminaries
Without a lot more context, it's hard to see what you're really trying to do or why you're trying to define your behavior in a Struct. That doesn't mean it's wrong to do so; it's just not really obvious why you want to do it this way.
Inherit from Struct
That said, the code for inheriting from a Struct is pretty simple. In your case, you might be able to do something as simple as:
# Define a class that inherits from the Struct class.
class TwitchUser < Struct.new(:name, :points)
end
# Assign the struct to a variable.
twitcher = TwitchUser.new 'foo', 100
# Access the struct members.
twitcher.name #=> "foo"
twitcher.points #=> 100
You can then edit the values by assigning directly to a Struct member:
twicher.points = 200
Re-Open Class to Add Utility Methods
Alternatively, you can re-open your TwitchUser class to extend it with a method to do something with the members. For example:
class TwitchUser
def inc
self.points += 1
end
end
twitcher.inc #=> 101
twitcher.points #=> 101
All-in-One Approach
A very Ruby way to do this would be to declare the class to inherit from Struct, and include any utility methods you might want to operate on the members. For example:
class TwitchUser < Struct.new(:name, :points)
def inc
self.points += 1
end
end
Build Your Array of Struct Objects
Once you have the right type of object to represent your problem domain (whether or not it's a Struct), you can build an array of them. For example:
twitch_users = [twitcher]
twitch_users.first.name
#=> "foo"
twitch_users << TwitchUser.new('bar', 200)
twitch_users.last.name
#=> "bar"
You can then operate on the Array of Struct objects any way you like to find or display the records you want. As just one possible example:
twitch_users.select { |s| s.name.eql?('foo') }.first.points
#=> 101

How to namespace constants inside anonymous class definitions?

When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants:
klass1 = Class.new do
FOO = "foo"
end
klass2 = Class.new do
FOO = "bar"
end
This gives warning: already initialized constant FOO and looks like it's right:
> klass1.const_get(:FOO)
"bar"
> klass2.const_get(:FOO)
"bar"
> FOO
"bar"
I was going to use this approach in a simple DSL for defining addons for an application, something like this:
class App
class AddonBase
attr_reader :session
def initialize(session)
#session = session
end
end
def self.addons
#addons ||= {}
end
def self.addon(name, &block)
addons[name] = Class.new(AddonBase, &block)
end
end
This works fine for simple add-ons but if defining constants, they will be under Object:: instead of becoming addons[name]::CONSTANT:
App.addon "addon1" do
PATH="/var/run/foo"
def execute
File.touch(PATH)
end
end
App.addon "addon2" do
PATH="/etc/app/config"
def execute
File.unlink(PATH)
end
end
# warning: already initialized constant PATH
The constants could be anything and the add-ons could even define their own utility subclasses, so it's not just about replacing PATH with a function.
Is there some way to work around this?
When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants
They do, you can use const_set to define constants in anonymous classes:
klass1 = Class.new do
const_set(:FOO, 'foo')
end
klass2 = Class.new do
const_set(:FOO, 'bar')
end
klass1::FOO #=> "foo"
klass2::FOO #=> "bar"
Or via self::
klass1 = Class.new do
self::FOO = 'foo'
end
klass2 = Class.new do
self::FOO = 'bar'
end
klass1::FOO #=> "foo"
klass2::FOO #=> "bar"
When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants
Sure, by the definition of the word “anonymous.” Compare two following snippets:
class C1; puts "|#{name}|"; end
#⇒ |C1|
C2 = Class.new { puts "|#{name}|" }
#⇒ ||
Unless assigned to the constant, the class has no name and hence all constants defined inside go to Object namespace. That said, the warning here is actually pointing out to error and Object::FOO = "bar" overrides Object::FOO = "foo" constant.
That said, one cannot use constants in this scenario. Use class-level instance variables instead, or construct unique constant names manually (I would advise avoiding polluting Object class with a bunch of unrelated constants, though.)
Actually the problem is how to define a class using a proc including constant definitions. As has already been said it is not possible the way you did it, since the proc gets class_eval'd and that doesn't allow to define constants.
I suggest another approach. Can you use modules instead of procs to define new addons mixing a module into a class?
Example:
module AddonModule
FOO = "foo"
end
klass = Class.new
klass.include AddonModule
klass::FOO # => "foo"
Usage in your DSL:
def self.addon(name, addon_module)
addon = Class.new(AddonBase)
addon.include addon_module
addons[name] = addon
end

What is difference between class variable and variable defined on class level

What is the difference between class variable and variable defined on the class level?
say, bar is defined with ## which means it's a class variable and will be accessible within all methods in class.
class Foo
##bar = 'bar'
end
so does bar without ##, so what is the difference..?
class Foo
bar = 'bar'
end
Well, with your second option, bar is a local variable which gets out of scope when reaching the end. As such, it won't be accessible to any methods (class methods nor instance methods) of the class.
With that being said, in Ruby, there are class variables (##bar which are shared between all child classes and their instances and instance variables (#bar) Since classes are also just objects in Ruby, you can also define an instance variable on class level (or more correctly: on the singleton class of your class). This can work like this:
class Foo
def self.bar
#bar
end
def self.bar=(value)
#bar = value
end
end
Compared to class variables, these instance variables on the singleton class are not accessible on Foo instances nor on child classes of Foo.
The ##bar will return the same variable to all instances of the class, not just the same variable to all methods in the class.
The way I think of class variables (##variables) is as namespaced global variables. Their usage is just about as frowned upon as using global variables, but not quite since you are limiting the scope to within the code defined in your class.
Generally they would be used to track some kind of state across your application.
Let's say you had an object that needed to be able to identify its most recently instantiated sibling object.
One way to do that would be via a global variable:
class MyThing
def initialize
$latest_thing = self
end
def latest
$latest_thing
end
end
thing1 = MyThing.new
thing1.latest # => thing1
thing2 = MyThing.new
thing1.latest # => thing2
thing2.latest # => thing2
Now, that uses a global variable which is generally perceived as bad practice, one of the reasons being for pollution of the global namespace and risk of naming collision and/or someone else changing it.
If you are dealing with a situation like this where you need this shared state between instances, but no one outside needs to know about it, then you can use the class variable exactly like a global:
class MyThing
def initialize
##latest_thing = self
end
def latest
##latest_thing
end
end
thing1 = MyThing.new
thing1.latest # => thing1
thing2 = MyThing.new
thing1.latest # => thing2
thing2.latest # => thing2
That's just generally cleaner/safer/better encapsulated, since any 3rd party code can not simply do ##latest_thing = :something_else the way that they could do had a global been used.
Hope that helps. I think class variables are rarely used or encouraged in the wild, but on the rare occasion I've needed to use one it was for things like this.

Get a reference to an object while instantiating it

I'd like to reference an object while instantiating it in order to pass it to another object I'm instantiating. What I mean:
A.new(B.new(self))
In this case, self would refer to the scope in which I'm actually calling A.new. What I want is for self (or whatever other keyword) to refer to the newly instantiated A object, so that B would have a reference to A. Is there a way to do this?
The way you have written it (A.new(B.new(self))) is impossible, due to a circular reference.
In order to create an instance of A, you need an instance of B; in order to create the instance of B, you need the instance of A.
There are a few ways you tweak the implementation to make this possible, but you must first resolve this chicken-and-egg problem between the A and B. For example:
class A
def initialize
#b = yield(self)
end
end
class B
def initialize(a)
#a = a
end
end
A.new { |a| B.new(a) }
Note that in the above code, a is being initialized first. It is only being yielded in the scope after the object has been created.
Or, here's another way:
class A
def initialize
#b = B.new(self)
end
end
class B
def initialize(a)
#a = a
end
end
A.new
Like above, the instance of A is being created first. But this time, I've done all the initialization in one go rather than building it within the new() methed call.
One final example:
class A
attr_writer :b
def initialize
end
end
class B
def initialize(a)
#a = a
end
end
A.new.tap { |a| a.b = B.new(a) }
In this example, I have fully initialized a before defining its attribute of b. This could just as easily have been written in two lines of code, with a regular variable instead of the closure:
a = A.new
a.b = B.new(a)

A more concise way of assigning instance variables from initialize?

I have a few classes I'm using to contain data (and a few methods on it) in Ruby. For instance:
class Foo
def initialize(bar, biz, baz)
#bar=bar
#biz=biz
#baz=baz
end
end
Is there a less repetitive way of propagating these initialization arguments into instance variables?
One liner, but I find this can obscure things:
#bar,#biz,#baz = bar,biz,baz
class Foo
def initialize(*args)
raise ArgumentError unless args.length == 3
#bar, #biz, #baz = args
end
end
One quick way is to use a Struct:
class Foo < Struct.new(:bar, :biz, :baz)
# custom methods go here
end
Struct.new will return a class with the initializer and accessors set up for you (other than that, it's just a normal class). If you don't need any custom methods, you can also define a struct inline (e.g. Foo = Struct.new(:bar, :biz, :baz)).

Resources