Object Oriented Ruby

May 2nd, 2016 No comments

I have been seeing a lot influence in ruby from Functional programming (short: FP), and not as such in ruby itself but in code written in ruby. Developers try new things, they are fascinated by other languages, how they solve problems. They try to change ruby into functional language, they loose sight of how problems can be solved in the Object Oriented Programming (short: OOP). Let’s do a refresher of some most important aspects of OOP.

Encapsulation

Technical details aside encapsulation is the most important concept in OOP. At the roots OOP was meant to be a simplified representation of real life things/objects/people, think a car, an animal, a plant. calculator For an example we will take an calculator, it’s a fairly complex thing. An calculator has a display, some buttons and some magic insights. The buttons are the input for calculator, the display is it’s output. We do not know what happens inside. Well some of us do, the point is only those interested in creating calculators know how to build one. For anyone else calculator has an interface with input and output allowing anyone knowing it’s language interaction with the calculator. This is encapsulation, most calculator users will use it’s interface, they do not need to know what happens inside. Is it an old kind of machine with gears, is it a computer, or maybe a new invention protein based? As long as it works as expected – nobody cares.

Mixins

When looking around we see a lot of repeating patterns. As software developers we do not want to repeat code, we write reusable methods, we put them in modules to group them. Mixins is where writing functional style code is important, we do not mutate any state, we take inputs and give outputs. I will illustrate on few examples what to avoid and how to write mixins code.

Example 1.

module ConfigReader
  def config
    @config ||= File.readlines(@config_file_name)
  end
end

This code breaks encapsulation, it assumes there is @config_file_name, it puts requirements on people that will use it, it does not define interface, it’s not functional. How would that be bad? Any mixin author could use @config, maybe there is Options module that also defines @config – you see how that could be bad? Authors of both modules would need to know of each other and would need to make sure they variables do not overlap.

Example 2.

module ConfigReader
  def read_config(file_name)
    File.readlines(file_name)
  end
end

In this example we do not assume existence of any variables, we do not define any variables. What we have here is a pure functional interface, no side effects (for the object).

If you notice the need to reference/write variables in mixins you should reconsider, you might need composition or inheritance.

Example 3.

module Age
  attr_accessor :birth_date
  def age
    (Date.today - @birth_date)/365
  end
end

It looks pretty contained, right? The thing is – it touches again on instance variables. This is definitively where composition would come in handy.

Composition

When we think of the world we know it consists of multiple objects, those objects can be often decomposed further into smaller objects down to atoms. In OOP composition helps us move between high level overview and implementation details, but as always it can be understood wrong, it’s very important to not forget about encapsulation. Similarly to mixins I will show some examples here too:

Example 4.

class Person
  class Barber
    attr_reader :person
    def initialize(person)
      @person = person
    end
    def dye_hair(color)
      person.name = color
    end
  end

  attr_accessor :name, :hair_color
  def initialize(name, hair_color)
    @name, @hair_color = name, hair_color
  end
  def barber
    Barber.new(self)
  end
end
michal = Person.new("Michal", "brown")
michal.name
=> "Michal"
michal.barber.dye_hair("blue")
michal.name
=> "blue"

In this example we show the importance of encapsulation in composition, we can not allow anybody to change everything inside of a class. The biggest mistake in this example is passing the instance to the other object with assumption it will operate on the passed instance. If we pass the parent it should only be for reading. How this could have been done differently?

Example 5.

class Hair < Struct.new(:color)
end
class Barber
  attr_reader :hair
  def initialize(hair)
    @hair = hair
  end
  def dye_hair(color, &block)
    hair.color = "#{hair.color} #{color}"
  end
end
class Person
  attr_reader :name, :hair
  private :hair
  def initialize(name, hair)
    @name, @hair = name, Hair.new(hair)
  end
  def dye_hair(color)
    Barber.new(hair).dye_hair(color)
  end
  def hair_color
    hair.color
  end
end
michal = Person.new("Michal", "brown")
michal.hair_color
=> "brown"
michal.dye_hair("blue")
michal.hair_color
=> "brown blue"

With this new example Barber can not change anything else but Hair. In composition it’s important to not break the encapsulation, using other objects, blocks or return values we can be much safer, we will not manipulate objects that are not our concern.

Inheritance

In OOP inheritance is where we extend the functionality in each inherited(child) class. Let a example show what inheritance gives us:

Example 6.

class Person
  attr_reader :strength
  def initialize(name, strength)
    @name, @strength  = name, strength
  end
  # @returns NUMBER overly simplified distance how far it's thrown
  def throw(weight)
    strength / weight
  end
end

class SuperHuman < Person
  def initialize(name, strength)
    super(name, strength * 1_000_000)
  end
  # @returns NUMBER overly simplified distance flown in given time
  def fly(time)
    strength * time
  end
end

In above example we see two traits of inheritance:

  1. We have access to the variables / methods of the class we are inheriting.
  2. We can add new functionality extending capabilities of the inherited class.

So inheritance crosses the encapsulation boundary … does it? Not really, inheritance happens before we instantiate the object. Think of inheritance like of onions or ogres ;) – it has layers, each child class builds on top of the parent class, it adds extra functions, it improves previous implementation, it does not reach between objects to change variables.

When thinking of using inheritance you need to distinguish between “is a kind of” and “has the same methods”. Inheritance only works when it’s “kind of” relation, in other cases consider mixins or composition depending on the use case.

Summary

It’s hard to pick proper way of structuring our code. With the above examples I tried to show where each of the described methods fits in. For me the biggest differentiatior is encapsulation, is the object maintaining it’s own state or let’s others do that?

Writing OOP code does not mean that you need to forget about FP, actually it’s reverse, it all connects together. OOP does not require you to change state, you can actually write most of your methods in functional way, making sure the code has strictly defined interface and uses only it’s input and produces output. It’s more mater of your codding culture, the language can not restrict or force you to write good code, it falls on the developer to structure code. This is why it’s good to go out of your comfort zone and learn other languages, why you need to experiment. But before you go out on an adventure with other concepts make sure you know your own backyard.

Conference talk / follow ups

I’m working on extending this topic in further posts and also started creating conference talk from it, if you are interested in hosting the talk contact me directly.

I’m also searching for new Ruby job, I had a great time with StackBuilders but the company is focusing on Haskell and I want to continue my work in Ruby world. So if you heard of somebody in need of good Ruby developer let me know.

Categories: Development Tags:

Zeroconf on OpenSUSE

April 16th, 2016 No comments

I have been struggling with my home network for long time, and I do not mean setting up the router, that’s easy. What I was struggling with is using names for machines. Of course you can set names in the router, but is it really what we want? In times of dynamic networks, where devices come and go only small subset of them is there to stay. So I have been doing binding of IP addresses to MAC addresses on the router level to ensure all computers can access the printer or backup… How antiquated is that?

Welcome to the age of zero-configuration networking(zeroconf). Zeroconf provides set of protocols that allows configuration less discovery of network. Well almost configuration less, we still need to setup each computer. Although zeroconf also specifies automatic IP addresses assignment I think it’s almost pointless when we get access to internet everywhere – with dynamic IP address provided by DHCP.

What exactly does us give this zeroconf? For me it is no need for DNS locally, all computers with properly configured zeroconf will be visible to each other. Additionally this also implies no more need to set up static addresses as the names are automatically recognized in the system. So we can skip hard codding IP for the printer – yes many networked devices are already zeroconf enabled and should be visible by default.

All the commands should be executed from konsole you can start it fast with pressing ALT+F2 and typing konsole followed by <ENTER>. Before we start anything we need to become root user, depending on your system configuration you might use sudo -s or su - to enter administrative mode.

The most important step in zeroconf is configuring our computer name, this is one time setup and from this point on whenever we go, out computer will have the same easy to use name. Using console we need to set the system name and update one file:

hostname mpapis-linux
hostname > /etc/hostname

With the system named properly we need to restart the zeroconfig service to use the new name, on OpenSUSE it is avahi-daemon and can be restarted with:

systemctl restart avahi-daemon

That should be it! Let’s test our configuration, we need to install/use extra tool:

zypper in avahi-utils
avahi-browse -at

Unfortunately the output in most cases will be empty.

Let’s do quick course in security. By default OpenSUSE comes with SuSEFirewall2 which by default opens all traffic on internal interfaces and closes all traffic on external interfaces. Did anyone configure your interfaces to be internal? Let’s drop it here, the default firewall has no integration with NetworkManager(NM) – which is enabled by default on laptop setups. We will have to switch to another firewall that has integration for NM – firewalld. This requires us to do few extra steps:

zypper    addrepo http://download.opensuse.org/repositories/security:/netfilter/openSUSE_Factory        security:netfilter
zypper    addrepo http://download.opensuse.org/repositories/devel:/languages:/python/openSUSE_Leap_42.1 devel:languages:python
zypper    refresh
zypper    remove  SuSEfirewall2
zypper    addlock SuSEfirewall2
zypper    install firewalld firewall-applet firewall-config
systemctl enable  firewalld
systemctl start   firewalld

The bad news is now it’s best to restart the system for all the changes to take effect… relogin might be enough but I haven’t tested that.

After the system is up and running we can now configure our firewall with firewall-config, open it, disable all services in public zone, enable mdns in internal zone. The public zone will be used as default when not set for network connections, we need to set the internal zone for connections we trust.

Let’s now configure connections, first we get the list:

nmcli connection

for each of the connections we trust we execute this line (replace "connection name" with the name of the connection):

nmcli connection modify "connection name" connection.zone internal

From now on when we connect to one of the trusted networks our firewall will switch to the internal zone and we should now get access to our zeroconf autodetection:

avahi-browse -at

The output will now show all the devices in the network that support zeroconf, some example output from my network:

+   eth0 IPv4 Brother DCP-J4110DW          Internet Printer     local
+   eth0 IPv4 mpapis-linux                 SSH Remote Terminal  local

That’s it, your computer will now use zeroconf when configured to (internal connections). Some extra commands that might be helpful:

avahi-browse -art

It will show the list of available services with all the available details – including hostname for use in our configurations or communication, for example I can type now:

ssh mpapis-linux.local

from any computer in my home and it just works!

Most of the presented steps are for console but they can be as well executed from graphical interface via Yast.

Categories: Development, Linux Tags:

Changes in my open source life

February 15th, 2015 Comments off

TL;DR

I’m struggling with burnout, to fix it I decided to change my life, got part time job at Try Catch, decided to stop spending all my time on supporting RVM 1 and instead in freed time finish developing RVM 2. RVM 1 maintainers needed.

Open source

Before open source I was a bit unhappy, spending big part of my week in work. I was not depressed, just still not where I wanted to be. This unhappiness was pushing me to find things that made me happy – and open source was it. I was spending a lot of my after work time to be happy – helping people was the solution. So when I got chance to ditch work and do full time open source I was very happy. This continued for long time, I was solving problems, making people happy by helping them. I have noticed larger problems and thought of solving them, this is when I planned RVM 2 a rewrite that was supposed to fix many problems developers struggled in the setup phase of projects and development/production environments. I got money from community and started working, the feature looked bright.

Burnout

But then I was caught by trap of my own creation, I still wanted RVM 1 to work great, to be useful before RVM 2 is finished. Every time I started working on the second version I was distracted by problems of first version. With time this broke me and in the end I was unable to focus on version two. As the only developer on multiple projects I was continuing to help to solve existing problems. I even thought on changing projects I’m involved, I got into working with veewee. This helped for short time and I was able to help both veewee and to work on RVM 2, but soon the disruption wear off and I wasn’t unable to do anything else but support RVM 1. Part of the problem was I was to good in support, new people were not coming to help me, I was doing it all alone. Doing everything alone took a toll on me, I was struggling with burnout. Some weeks were good, some less. I was considering different paths I can take and how to get back in the game of development.

Solutions

In the end I have decided to cut down my support time, really cut it down to a few hours a week. Assuming the projects I’m involved are worth for others someone will pick it up and help with them. There are also alternatives to most of the code I am maintaining, people can migrate to the other projects, now it’s up to the community to step up and help me – help them self to manage the projects. I also decided to get back to working with people by getting a part time job. It happened I got contacted by an amazing startup – Try Catch – which is aligned with my personality, they want to help people. I see it as a great opportunity to get back into the development game. For start I’m working in 80% capacity – so I still can have time to focus on open source. Especially I want to focus in the remaining time on finishing RVM 2, I still have almost half of the money from the fundraiser. There is already a lot of code and putting it all together is only question of spending some quality time with it.

Excitement

I’m very excited to work with Try Catch. As I was looking to get a Ruby job it was very hard to find something really interesting and company I would like to work with. I was getting contacted by multiple headhunters, they were trying to fit me into the job openings they had, but they treated me only as meat, they did not consider searching opening for me. When I got contacted by Try Catch I was very skeptical, my thoughts were “another headhunter agency”, but after few minutes I realized they reverse the process, they search for proper position for developer, not try to push him to current openings. I have spent last week working with Try Catch and it only cemented my good feelings about the company and our future. I’m also very excited to get back into developing RVM 2, I have still the same strong feelings about solving problems with RVM 2.

The feature looks bright again!

For information on helping with open source projects I’m involved with check RVM 1 maintainers needed.

Categories: Development, News Tags: , , , ,

Testing rubys initialize

October 6th, 2014 7 comments

In /ruby you have a lot of flexibility in writing code, this includes possibility to not initialize an object.
I use this for testing my initializers, here are some examples how to do it.

Lets start with ruby class:

class TestMe
  attr_reader :value
  def initialize(value = nil)
    @value = value
  end
end

It’s a simple example where only initialize can set a value, usually you use more advanced code, but this is just a minimal example.

Now lets test it:

describe TestMe do
  subject do
    TestMe.allocate
  end

  it "has default value" do
    subject.send :initialize
    subject.value.must_equal nil
  end

  it "sets value" do
    subject.send :initialize, 3
    subject.value.must_equal 3
  end

end

For more real live examples of allocate check

RVM 2.0 Fundraiser halfway

October 30th, 2013 Comments off

Hello Ruby Lovers,

First off, I want to thank everyone who has donated to the RVM 2.0 fundraiser so far. Every contribution counts and I appreciate them all, no matter how small! I would also like to thank EngineYard for their sponsorship of RVM over the last two years, which has allowed me to add:

  • tests,
  • stable releases,
  • ruby binaries, and
  • automatic dependency installation (autolibs)

…among many others.

Working solely on RVM 1.x for the last few years has given me a unique view into the Ruby community. I discovered some of the most common issues users run into and tried to address them in RVM by automating installation and configuration to improve everyone’s experience with Ruby (both newcomers and experienced developers).

It was also during this time that I realized that maintaining a huge project in shell (20,000 lines) isn’t possible — it’s hard to separate modules, it’s hard to test, and advanced algorithms either slow down or need to be simplified to shell coding limitations. All of these lead to the idea of rewriting RVM in Ruby.

Initially, I resisted the idea of rewriting RVM in Ruby since there were some ideas touching static ZSH. However, thanks to DRUG, I was convinced. It seemed all the elements were in place, most importantly, binary rubies which are required for easy bootstrapping. As such, I came up with the RVM 2.0 slogan: “with RVM 2.0, you get one Ruby for free”.

I believe in Ruby. I believe it can be used for more than just web applications. We have a vast ecosystem of gems and Ruby implementations and we are constantly discovering new ways to use Ruby, like Rubinius X, JRuby 9k and Shoes 4. I want RVM 2.0 to become one of those projects that shapes the future of Ruby, and with your help, it can.

If you use RVM personally, please show your support. If you use RVM at work, please encourage your employer to show their support. Everything counts!

https://www.bountysource.com/fundraisers/489-rvm-2-0

Cheers,
Michal

Categories: Development, News Tags: , ,