Properties and Methods in Ruby from a .NET POV
C# developers are used to working with fields, properties, and methods. But there’s certain confusion when it comes to understanding how those work in Ruby. So let’s have a quick C# refresher.
Fields, Properties, and Methods in C#
Fields are variables scoped either to an object (instance fields) or to a class (static fields). As a best practice, developers don’t expose fields publicly, as doing so exposes too much of the internals of a class. An instance field is declared like so:
public class Customer
{
private int _age;
}
Notice that the underscore before “age” is just a common convention that several C# developers follow. In order to expose such a field, one creates a “property”, which is defined as a pair of a getter and a setter method:
public class Customer
{
private int _age;
public int Age
{
get { return _age; }
set { _age = value; }
}
}
A property is defined only with a get method in case it’s only meant to be “accessed”, but not “assigned”. In cases where getters and setters simply get or set the value of a field, one may use a feature of C# called auto properties:
public class Customer
{
public int Age { get; set; }
}
When a property is defined like that, the C# compiler produces code similar to the one I had shown before (it creates the backing field, and the implementation of getters/setters).
Now, What Does That Look Like in Ruby…?
The following post briefly covered instance and class variables: NET to Ruby: Methods and Variables. In order to declare an instance variable, we define it in a class’ initializer (its constructor), preceeding it with an @:
class Customer
def initialize
@age = 18
end
end
At this point, @age is very similar to a private field in C#, in that it can be accessed anywhere within the class where it is defined, but not outside of it. In order to access or assign it from the outside, we must define accessor methods to it:
class Customer
def initialize
@age = 18
end
def age
@age
end
def age=(value)
@age = value
end
end
Different than in C#, where we defined an Age property containing set and get methods, here we created an age method that returns the value of the @age instance variable, and also an age= method, which takes in a value and assigns it to the instance variable.
Similarly to C#’s auto properties, Ruby also offers a construct that allow for easy creation of accessors. The code above could be rewritten like so:
class Customer
def initialize
@age = 18
end
attr_accessor :age
end
attr_accessor is not a keyword in Ruby; it is something called a “class macro”, which is a meta programming technique to add dynamic behavior to a class. In this case, what gets added are the reader and writer accessors. We can also only add either the reader or write accessors by using attr_reader or attr_writer, respectively.
Accessors That Do a Little More…
It’s very common to create a property in C# where the accessors may do a little more than just get and/or set the value of an instance variable. For example, maybe the getter needs to concatanate values before returning it:
public class Customer
{
private string _firstName;
private string _lastName;
public string FullName
{
get { return string.Format("{0} {1}", _firstName, _lastName); }
}
}
In the example above, the FullName property has a getter that concatenates the _firstName and _lastName fields. Such practice is common in Ruby as well:
class Customer
def initialize
@first_name = "Claudio"
@last_name = "Lassala"
end
def full_name
"#{@first_name} #{@last_name}"
end
end
Keep in mind that parenthesis are optional in Ruby, so a method such as full_name above can be called like so:
cust = Customer.new
puts cust.first_name
Cleaning Up the Code With Metaprogramming
Quite often we add boolean properties to a class so we can ask an object about certain things. Take this example:
public class User
{
private string _profile;
public User(string profile)
{
_profile = profile;
}
public bool IsDoctor
{
get { return _profile == "doctor"; }
}
public bool IsPatient
{
get { return _profile == "patient"; }
}
}
Now assume that there are more types of profiles then just “doctor” and “patient”, so the User class would end up having one property for each type of profile in this case. Similar class could be created in Ruby like so:
class User
def initialize(profile)
@profile = profile
end
def doctor?
@profile == 'doctor'
end
def patient?
@profile == 'patient'
end
end
What’s with the “?” suffix for the doctor? and patient? methods? Often, when a method in Ruby is meant to return a boolean, the method is called a predicate method, and it is common practice to have it with a “?” suffix. So instead of writing code such as “if user.is_doctor”, we write “if user.doctor?”.
If there’s really potential for the User class to have several methods such as doctor? and patient? (one for each type of profile), the class could be rewritten to leverage some metaprogramming. Here’s an example of what that could look like:
class User
def initialize(profile)
@profile = profile
end
def method_missing(method_name, *args, &block)
method_name = method_name.to_s
if method_name.ends_with?("?")
return @profile == method_name.chop
end
super if self.responds_to?(method_name)
end
end
Notice that both the doctor? and patient? methods are gone, and a method_missing method has been added. What happens now is that whenever some code queries the user object like “if user.doctor?”, such method is missing in the class, and the method_missing method gets called. In the example above, the method_missing checks whether the method being called on the object (“method_name”) ends with a “?”, and if it does, it compares @profile to it. If new profiles are supported, there is no need to add properties to the User class, as it is ready to handle it.
Summary
It’s important to understand the fact that there aren’t really “properties” in Ruby (there are methods, instead), and those can either be created automatically (using attr_accessor, attr_reader, attr_writer class macros) or manually (by simply defining the methods), since these things are used quite a bit in every application.
I hope you now have a better grip on how C# and Ruby compare when it comes to properties. Leave a comment if you have any questions or insight.