Last Updated:

Properties and Methods in Ruby from .NET POV

C# developers are used to working with fields, properties, and methods. But there's some confusion when it comes to understanding how they work in Ruby. So, let's quickly refresh C#.

Fields, Properties, and Methods in C #

Fields are variables that relate to either an object (instance fields) or a class (static fields). It's best that developers don't expose the fields publicly, as this exposes too many internal members of the class. The instance field is declared as follows:

public class Customer
{
  private int _age;
}

Note that underlining before "age" is just a general convention followed by multiple C# developers. To set such a field, a "property" is created, which is defined as a pair of get methods and a setup method:

public class Customer
{
  private int _age;

  public int Age 
  {
    get { return _age; }
    set { _age = value; }
  }
}

A property is defined only by the get method if it is intended only for "access" and not for "destination". In cases where the getters and setters simply get or set the value of the field, you can use a C# function called auto properties:

public class Customer
{
  public int Age { get; set; } 
}

When a property is defined in this way, the C# compiler generates code similar to the one I showed earlier (it creates a secondary field and an implementation of hatters/setters).

Now, what does it look like in Ruby...?

The following post briefly discusses instance and class variables: NET to Ruby: Methods and Variables. To declare an instance variable, we define it in the class initializer (its constructor), preceded by @:

class Customer

  def initialize
    @age = 18
  end
end

At this point, the @age is very similar to a private field in C# because it can be accessed anywhere in the class where it is defined, but not outside of it. To access it or assign it externally, we need to define methods to access it:

class Customer

  def initialize
    @age = 18
  end

  def age
    @age
  end

  def age=(value)
    @age = value
  end
end

Unlike C#, where we defined the Age property containing the set and get methods, here we created the age method, which returns the value of the @age instance variable, as well as the age= method, which takes the value and assigns it to the instance variable.

Like the automatic properties of C#, Ruby also offers a construct that makes it easy to create accessors. The code above can be rewritten as follows:

class Customer

  def initialize
    @age = 18
  end

  attr_accessor :age
end

attr_accessor is not a keyword in Ruby; it's something called a "class macro," which is a metaprogramming method for adding dynamic behavior to a class. In this case, readers and writers are added. We can also only add readers or writers using attr_reader or attr_writer, respectively.

Accessors that do a little more...

Very often, a property is created in C# in which accessors can do little more than just get and/or set the value of an instance variable. For example, maybe a recipient needs to concatanize 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 receive method that combines the _firstName and _lastName fields. This practice is common in Ruby:

class Customer

  def initialize
    @first_name = "Claudio"
    @last_name = "Lassala"
  end

  def full_name
    "#{@first_name} #{@last_name}"
  end
end

Keep in mind that parentheses are optional in Ruby, so a method like full_name above can be called like this:

cust = Customer.new
puts cust.first_name

Clean up code with metaprogramming

Quite often, we add Boolean properties to a class so that 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 suppose that there are more types of profiles than just "doctor" and "patient", so in this case the User class will have one property for each profile type. A similar class can be created in Ruby like this:

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 a doctor? and the patient? Methods? Often, when a method in Ruby is designed to return a Boolean value, this method is called the predicate method, and it is common practice to use it with the suffix "?". So instead of writing code like "if user.is_doctor," we write "if user.doctor?".

Does the User class really have multiple methods, such as Doctor? and the patient? (one for each profile type), the class can be rewritten to use metaprogramming. Here's an example of what that might 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 doctors? and the patient? the methods disappeared, and the method_missing method was added. What now happens is that whenever any code requests a user object, such as "if user.doctor?", such a method is absent from the class, and the method_missing method is called. In the example above, the method_missing checks whether the method called on the object ("method_name") ends with a "?" and, if so, compares the @profile to it. If new profiles are supported, you do not need to add properties to the User class because it is ready to process it.

Resume

It's important to understand the fact that Ruby doesn't really have "properties" (there are methods) and can be created automatically (using macros of the attr_accessor class, attr_reader, attr_writer) or manually (just by defining methods), since these things are used quite often in every application.

I hope you now have a better understanding of how C# and Ruby compare when it comes to properties. Leave a comment if you have any questions or insights.