Tag Archives: Ruby

Whisper Ruby

When using callbacks in your Ruby objects, there are more than a few ways of doing it. Recently I stumbled into Wisper gem that abstracts details away in a nice manner. Best do some code comparison.

Previously, I might be writing something like this:

class Worker
  def do_some_hard_work
    status = :should_be_sleeping_like_a_log
    notify_listeners(:on_work_done, status)
  end

  def add_listener(listener)
    (@listeners ||= []) << listener
  end

  def notify_listeners(event_name, *args)
    @listeners && @listeners.each do |listener|
      if listener.respond_to?(event_name)
        listener.public_send(event_name, self, *args)
      end
    end
  end
end

class Owner
  def set_things_in_motion
    worker = Worker.new
    worker.add_listener(WorkerDisplay.new)
    worker.do_some_hard_work
  end
end

class WorkerDisplay
  def on_work_done(worker, status)
    display_worker_status(worker, status)
  end
end

Here you have a worker class that has the ability to register listeners and to trigger appropriate events on them, if they respond to the given event. Not too big of a footprint but it does sort of pollute the domain a bit. I guess things could be extracted to a module but let’s try it with Wisper now:

class Worker
  include Wisper

  def do_some_hard_work
   status = :should_be_sleeping_like_a_log
   publish(:on_work_done, self, status)
  end
end

class Owner
  def set_things_in_motion
    worker = Worker.new
    worker.subscribe(WorkerDisplay.new)
    worker.do_some_hard_work
  end
end

class WorkerDisplay
  def on_work_done(worker, status)
    display_worker_status(worker, status)
  end
end

So, most of the code didn’t change, but the Worker class did benefit from a more clearly revealed intent. A small one, but still a win. Best thing is, you can use the async Wisper gem extension and turn your listener into a Celluloid Actor.

class Owner
  def set_things_in_motion
    ...
    worker.subscribe(WorkerDisplay.new, async: true)
    ...
  end
end

There are other nice features like global listeners, mapping subscription to a different method or  subscription chaining, so if you’re interested go ahead and read the project’s readme.

Advertisements
Tagged ,

Nokogiri vs Crack & Hashie

Recently I wrote about using Vacuum gem for accessing Amazon Product Advertising API. As the result of API calls, it returns vanilla XML response from Amazon API. There are gems that returns PORO’s, but the “bare metal” access I get from Vacuum made it a great gem to use for several reasons (breakdown of those reasons and other existing gems is a topic for another post). Parsing the Amazon responses is a bit of a funny issue since information is located at various points. Here’s an example:

  root = Nokogiri::XML(response.body).remove_namespaces!
  items = parse_items(root)

  def parse_items(node)
    node.xpath('//Items/Item').map do |item_node|
      create_item_from(item_node)
    end
  end

  def create_item_from(node)
    attributes = {}
    attributes[:id] = parse_value(node, './ASIN')
    attributes[:title] = parse_value(node, './ItemAttributes/Title')
    attributes[:url] = parse_value(node, './DetailPageURL')
    attributes[:group] = parse_value(node, './ItemAttributes/ProductGroup')
    attributes[:images] = parse_item_images(node)
    Item.new(attributes)
  end

  def parse_item_images(node)
    image_sets = node.xpath('./ImageSets/ImageSet')
    return if image_sets.children.size == 0

    image_set = image_sets.find {|image_set| image_set.attribute('Category').value == 'primary'} || image_sets.first
    image_set.xpath('./*').inject(Hash.new) do |images, image_node|
      image = create_item_image_from(image_node)
      images[image.type] = image
      images
    end
  end

  def create_item_image_from(node)
    attributes = {}
    attributes[:url] = parse_value(node, './URL')
    attributes[:height] = parse_value(node, './Height', :to_i)
    attributes[:width] = parse_value(node, './Width', :to_i)
    attributes[:type] = node.name.gsub("Image", "").downcase
    ItemImage.new(attributes)
  end

  def parse_value(node, path, apply_method = nil)
    nodes = node.xpath(path)
    if nodes.first
      value = nodes.first.content
      value = value.respond_to?(:strip) ? value.strip : value
      apply_method ? value.send(apply_method) : value
    end
  end

As you can see, my domain objects don’t map exactly to Amazon structure. But, that’s what this parser is all about. It translates API responses to what I need in my domain. What bothered me was that I needed to use hard-coded paths to specific information, so I went looking for another solution. This is where Crack & Hashie gems come in. The first one converts received XML to a Hash, and the second one enables nicer Hash parsing. The code seems nicer:

  data = Hashie::Mash.new(Crack::XML.parse(response.body))
  items = parse_items(data)

  def parse_items(data)
    items_data = data.ItemSearchResponse.Items.Item
    if items_data.kind_of?(Array)
      items_data.map {|item_data| create_item_from(item_data)}
    else
      [create_item_from(items_data)]
    end
  end

  def create_item_from(data)
    attributes = {}
    attributes[:id] = data.ASIN
    attributes[:title] = data.ItemAttributes.Title
    attributes[:url] = data.DetailPageURL
    attributes[:group] = data.ItemAttributes.ProductGroup
    attributes[:images] = parse_item_images(data)
    Item.new(attributes)
  end

  def parse_item_images(data)
    return unless data.respond_to?(:ImageSets)

    image_set = data.ImageSets.ImageSet
    image_set = image_set.find { |image_set| image_set.Category == 'primary' } || image_set.first if image_set.kind_of?(Array)
    image_set.keys.select { |key| key =~ /.*Image/ }.inject(Hash.new) do |images, key|
      image = create_item_image_from(image_set.send(key), key)
      images[image.type] = image
      images
    end
  end

  def create_item_image_from(item_data, type)
    attributes = {}
    attributes[:url] = item_data.URL
    attributes[:height] = item_data.Height.to_i
    attributes[:width] = item_data.Width.to_i
    attributes[:type] = type.gsub("Image", "").downcase
    ItemImage.new(attributes)
  end

So, the result although similar seems nicer to me. A bit more code like, and less strings, even a bit less code. Definitely a win so far. But, there are some issues.

First one is that with XML parsing I don’t think much about collections, they are always the same, regardless of the number of child nodes. With Crack/Hashie, suddenly I needed to think about it since the combination converts a collection of 1 into direct child. Hence the Array check in parse_items method. I don’t like making such checks but OK, it was a very limited and specific enough not to hurt me later.

The second issue was performance. Even while testing everything suddenly seemed slower. At first I attributed this to my fatigue, but just to be sure, I made a small performance test. It consisted of parsing a predefined XML document (with recorded Amazon API response) for 100 times. The XML response has 9 items, and you can see an example here. The test routine can be found here. The results were more than interesting:

Seconds: 1st 2nd 3rd 4th 5th Average
Nokogiri 1.18 1.26 1.27 1.33 1.23 1.25
Crack/Hashie 7.06 7.87 7.56 7.58 7.44 7.50

It seems extra baggage from Crack and Hashie makes that solution about 5 times slower.
This was more than enough reason to abandon the approach and just live with plain XML and Nokogiri.
But, at least now I know why 🙂

Tagged , ,

Groovy and Ruby nested Hashes with default value, a short comparison

A bit of Groovy / Ruby comparison today! Recently I needed to write a Groovy script that needed to convert an array of objects to a structured and nested report. And I stumbled on Groovy’s default values for Hashes, described here. I loved how you can create nested hashes with default on all levels:

nested_hash = [:].withDefault() { [:].withDefault() {0} }

It felt so nice to remove all those checks (if a key exists … otherwise …) and it cleaned up the code nicely, making it more intent revealing. A big plus!

So, I wanted to see how Ruby would do. And gues what? It didn’t feel as natural at first sight.

For example, I expected this to work out of the box:

nested_hash = Hash.new(Hash.new(0))

But, very fishy things started to happen, making feel lost quite a bit. A correct  description of issues with this approach is summarised in this Stackoverflow question.

I guess this comes from the fact that Groovy treats non-existent hash key access with default value differently:

  • Groovy creates non-existent keys by default
  • Ruby doesn’t, it just returns the default value

The effect is such that with Groovy the default value is never changed, and you pile up items in your hash by accessing non-existent items.

For Ruby, a bit different approach is needed:

nested_hash = Hash.new { |hash, key| hash[key] = Hash.new(0) }

In effect, this makes Ruby nested hash behave as Groovy one: creating non-existent keys when accessed. At the above link, you can find a solution to have endlessly deep hash if you’re interested.

Finally, I decided to solve a small scenario in both languages. The idea was that while having a family, one needs to create a report stating age group / decade distribution by gender for it. Nested hashes / dictionaries with default values seemed ideal for it. Here are implementations in both languages (in no particular order):

I like how Ruby is less verbose, especially regarding Person class / struct. But I must admit I like Groovy default nested hash values more, it somehow feels more natural. Then again, maybe for this situation the automatic creation of non-existent keys is good, but somewhere else it might not be such a good idea, so having the option to choose is worthwhile. I guess one just needs to get used to the flavor of the language used and that’s it. The rest of the code is pretty much the same, no surprises there. And in case you were wondering, yes, some of the names are from my own family 🙂

Tagged ,

SOA ROAR

I’ve been toying with the SOA idea in Ruby workspace lately. Part of it comes from reading Service-oriented design with Ruby & Rails by Paul Dix and part from watching that, by now famous, Uncle Bob’s talk Architecture the Lost Years. What really got me interested was the idea to package domain logic separately from the persistence and delivery mechanisms. At that time I also ran into roar and roar-rails gems from Nick Sutterer. All of this resulted in a small thought experiment and an accompanying Github repo.

I imagined to be a fruit shake maker 🙂 The entire ecosystem consists of the following components:

  • Book of orcharding – contains all the domain / business rules about fruit management
  • Orchard – serves us as the persistence layer for the orcharding rules
  • Tutti frutti – exposes orcharding rules as a json api
  • Smoothie mixer – rails project that consumes the api and delivers delishes fruit smoothies

At some later point in time, I was thinking about adding some other kind of client, e.g. command line or android/ios app. These clients would preferably use the same business gem for main logic and json client gem to communicate with the json api. It is a work in progress, hope you like it 🙂 And kudos to Nick for the beautiful gems!

Tagged , , ,

Gitlabhq +1

About a few months ago, I had the opportunity to tryout and decide on the Git ecosystem. Out of the few existing solutions, like Gitorius and others, I finally settled on Gitlab. The setup procedure was pretty straight, although not as automated or easy as I would like it to be. Some work is done to speed up the process but for those in need or the impatient ones, you can always follow the default instructions, and apply additional recipes where needed.

Since then, the Gitlab web interface and the entire ecosystem kept running like a charm, no glitches and everyone is pretty much happy with it.

I’ve personally chosen to set it up on CentOS, for which there is a good guide, and in the above recipes you can find specifics for the given OS. Ruby compiling is what actually took the most time, but with the mentioned resources you shouldn’t have a hard time.

The only issue left is the project <=> user relation. Using LDAP for the domain , it integrates nicely with Gitlab, authentication wise. Unfortunately, there is no way to automatically add projects to the new user based on some rule. A relation between LDAP groups and projects would be nice. I read that in the latest release there is support for project groups, so maybe this will solve the issue, have to try it out soon. For now, we settled on adding all projects to all user. A rake task is used for this:

desc "Add all users to all projects (admin users are added as masters)"
task :add_users_to_project_teams => :environment do |t, args|
  user_ids = User.where(:admin => false).pluck(:id)
  admin_ids = User.where(:admin => true).pluck(:id)

  Project.find_each do |project|
    puts "Importing #{user_ids.size} users into #{project.code}"
    UsersProject.bulk_import(project, user_ids, UsersProject::DEVELOPER)
    puts "Importing #{admin_ids.size} admins into #{project.code}"
    UsersProject.bulk_import(project, admin_ids, UsersProject::MASTER)
  end
end

desc "Add user to as a developer to all projects"
task :add_user_to_project_teams, [:email] => :environment do |t, args|
  user = User.find_by_email args.email
  project_ids = Project.pluck(:id)
  UsersProject.user_bulk_import(user, project_ids, UsersProject::DEVELOPER)
end
Tagged , ,