Ruby Class vs Struct
From the Struct docs:
A Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.
The Struct class generates new subclasses that hold a set of members and their values. For each member a reader and writer method is created similar to Module#attr_accessor.
So, if I want a Person
class that I can access a name attribute (read and write), I either do it by declaring a class:
class Person
attr_accessor :name
def initalize(name)
@name = name
end
end
or using Struct:
Person = Struct.new(:name)
In both cases I can run the following code:
person = Person.new
person.name = "Name"
#or Person.new("Name")
puts person.name
When use it?
As the description states we use Structs when we need a group of accessible attributes without having to write an explicit class.
For example I want a point variable to hold X and Y values:
point = Struct.new(:x, :y).new(20,30)
point.x #=> 20
Some more examples:
- http://blog.steveklabnik.com/posts/2012-09-01-random-ruby-tricks--struct-new
- "When to use Struct instead of Hash in Ruby?" also has some very good points (comparing to the use of hash).
When should I use Struct vs. OpenStruct?
With an OpenStruct
, you can arbitrarily create attributes. A Struct
, on the other hand, must have its attributes defined when you create it. The choice of one over the other should be based primarily on whether you need to be able to add attributes later.
The way to think about them is as the middle ground of the spectrum between Hashes on one side and classes on the other. They imply a more concrete relationship amongst the data than does a Hash
, but they don't have the instance methods as would a class. A bunch of options for a function, for example, make sense in a hash; they're only loosely related. A name, email, and phone number needed by a function could be packaged together in a Struct
or OpenStruct
. If that name, email, and phone number needed methods to provide the name in both "First Last" and "Last, First" formats, then you should create a class to handle it.
Why equality check for instance of Struct/Class are different?
I believe the answer you're looking for is found in the Struct documentation
Equality—Returns true if other has the same struct subclass
and has equal member values (according to Object#==).
Your example has equal member values for str_a
and str_b
, and they have the same subclass (Customer
), so they are equal when compared with ==
Contrast this with the Object documentation
Equality — At the Object level, == returns true only if
obj and other are the same object. Typically, this method is
overridden in descendant classes to provide class-specific meaning.
In your example, foo_a
and foo_b
are not the same object (because they're not the same instance)
If you're seeking why these are different, I didn't really answer that question. Just that the behavior is as intended per the docs. They don't actually have the same ID:
pry >> Bar = Struct.new(:name) do; end
=> Bar < Struct
pry >> x = Bar.new
=> #<Struct:Bar:0x7f8ebca47610
name = nil
pry >> y = Bar.new
=> #<Struct:Bar:0x7f8ebca14058
name = nil
pry >> x.name = "foo"
=> "foo"
pry >> y.name = "foo"
=> "foo"
pry >> x
=> #<Struct:Bar:0x7f8ebca47610
name = "foo"
pry >> y
=> #<Struct:Bar:0x7f8ebca14058
name = "foo"
But, you'll note comparison is based on attributes, rather than the object ID:
pry >> x == y
=> true
Even though the object id's are different:
pry >> x.__id__
=> 70125513489160
pry >> y.__id__
=> 70125513383980
Ruby: Struct vs Initialize
The class (non-struct) has a simpler ancestry tree:
>> Fruit.ancestors
=> [Fruit, Object, Kernel, BasicObject]
As compared to the struct version:
>> Fruit.ancestors
=> [Fruit, #<Class:0x1101c9038>, Struct, Enumerable, Object, Kernel, BasicObject]
So, the Struct class could be mistaken for an array (rare, but absolutely could happen)
fruit = Fruit.new("yo")
# .. later
fruit.each do |k|
puts k
end
# outputs: yo
So... I use Structs as throw-away data objects. I use "real" classes in my domain and application.
When is it better to use a Struct rather than a Hash in Ruby?
Personally I use a struct in cases when I want to make a piece of data act like a collection of data instead of loosely coupled under a Hash
.
For instance I've made a script that downloads videos from Youtube and in there I've a struct to represent a Video and to test whether all data is in place:
Video = Struct.new(:title, :video_id, :id) do
def to_s
"http://youtube.com/get_video.php?t=#{id}&video_id=#{video_id}&fmt=18"
end
def empty?
@title.nil? and @video_id.nil? and @id.nil?
end
end
Later on in my code I've a loop that goes through all rows in the videos source HTML-page until empty?
doesn't return true.
Another example I've seen is James Edward Gray IIs configuration class which uses OpenStruct
to easily add configuration variables loaded from an external file:
#!/usr/bin/env ruby -wKU
require "ostruct"
module Config
module_function
def load_config_file(path)
eval <<-END_CONFIG
config = OpenStruct.new
#{File.read(path)}
config
END_CONFIG
end
end
# configuration_file.rb
config.db = File.join(ENV['HOME'], '.cool-program.db')
config.user = ENV['USER']
# Usage:
Config = Config.load_config('configuration_file.rb')
Config.db # => /home/ba/.cool-program.db
Config.user # => ba
Config.non_existant # => Nil
The difference between Struct
and OpenStruct
is that Struct
only responds to the attributes that you've set, OpenStruct
responds to any attribute set - but those with no value set will return Nil
When to use Struct instead of Hash in Ruby?
Structs differ from using hashmaps in the following ways (in addition to how the code looks):
- A struct has a fixed set of attributes, while you add new keys to a hash.
- Calling an attribute that does not exist on an instance of a struct will cause a NoMethodError, while getting the value for a non-existing key from a hash will just return nil.
- Two instances of different structs will never be equal even if the structs have the same attributes and the instances have the same values (i.e.
Struct.new(:x).new(42) == Struct.new(:x).new(42)
is false, whereasFoo = Struct.new(:x); Foo.new(42)==Foo.new(42)
is true). - The
to_a
method for structs returns an array of values, whileto_a
on a hash gets you an array of key-value-pairs (where "pair" means "two-element array") - If
Foo = Struct.new(:x, :y, :z)
you can doFoo.new(1,2,3)
to create an instance ofFoo
without having to spell out the attribute names.
So to answer the question: When you want to model objects with a known set of attributes, use structs. When you want to model arbitrary use hashmaps (e.g. counting how often each word occurs in a string or mapping nicknames to full names etc. are definitely not jobs for a struct, while modeling a person with a name, an age and an address would be a perfect fit for Person = Struct.new(name, age, address)
).
As a sidenote: C structs have little to nothing to do with ruby structs, so don't let yourself get confused by that.
Why does a class inherit from a struct
No. It is equal to
B = Struct.new(:att)
class A < B; end
which is pretty much equivalent, but not equal to
class B
attr_accesor :att
def initialize(att)
@att = att
end
end
class A < B
end
Not equal because the value is not actually stored in @att
:
B = Struct.new(:att)
class B
def real_att
@att
end
end
b = B.new(32)
b.att
# => 32
b.real_att
# => nil
Personally, I don't see why one wouldn't just do A = Struct.new(:att)
, without the inheritance business.
EDIT: As Jordan notes in comments, there is an even nicer way to add methods to a struct: the Struct
conStructor (teehee) takes a block, which executes in the context of the newly created class: whatever you define there, will be defined on the new struct.
B = Struct.new(:att) do
def process
#...
end
end
In Ruby, how does one verify the identity of a Struct?
Summary:
use is_a?(Struct)
explanation:
A struct is a constructor for an anonymous class:
struct_class = Struct.new(:foo)
# => #<Class:0x007fa7e006ea98>
You can check that an instance of the anonymous class is a struct like so:
inst = struct_class.new
inst.class.superclass
# => Struct
However Object#is_a? checks the parent class as well as superclasses:
inst.is_a?(Struct)
# => true
You can see the same behavior in the following arbitrary example:
# inherits from String
anon_class = Class.new(String)
inst = anon_class.new
# => ""
inst.class == String
# => false
inst.is_a?(String)
# => true
Related Topics
Rails 5.2 Active Storage Add Custom Attributes
How to Access a Ruby Module Method
Better Way to Turn a Ruby Class into a Module Than Using Refinements
Differencebetween Methods and Attributes in Ruby
How to Use Define_Method Inside Initialize()
Object.Valid? Returns False But Object.Errors.Full_Messages Is Empty
How Might I Pass Text Data from the Ruby Console into My Clipboard Without Saving to a File
Best Way to Combine Fragment and Object Caching for Memcached and Rails
How to Delete Specific Characters from a String in Ruby
How to Write Specs for Code That Depends on Environment Variables
How to Set a Blank Value for an F.Select Form Field
How to Check If a Gem Is Installed
Listing the Names of Associated Models
Ruby: What Does the Asterisk in "P *1..10" Mean
Rails Console - Find Where Created at = Certain Day