Tools to track customers data

What are these tools and what do they do?

Recently I got a chance to work with different tools to track the customers data on your web app. I decided to use Intercom and Mixpanel.  Both these tools allow you to track customers activity in the form of an ‘event‘.

Events are basically activity a user does on your web app. It could be as simple as clicking the sign up button. Or filling the sign up form and clicking submit. This could be 2 different events.

But you would wonder why use 2 tools for tracking the same data? Well mix panel is purely an analytics tool. But intercom does more than that. It also provides the in-app messenger,  a chatbot if you will. Also one can trigger behaviour based on events. For ex: a ‘sign up’ event is triggered and we need to send account activation link upon every successful sign up. Now instead of configuring mailers in our rails app, we can just define behaviour in intercom to do some action whenever sign up occurs. And this behaviour could be sending emails or sending messages to the chat box. So I would say that primary purpose of intercom is to add messenger and add behaviour on events rather than doing analytics on those event data. Mix panel would be a better tool for this.

Why we need these tools?

But you would wonder how does this data help? Well, it gives you insight about how does the user uses your app. Considering the same example, by tracking those 2 different events, you would have knowledge that ‘n’ users visited the sign up page, but only ‘n/3’ signed up.

So now you can add events to more granular level i.e by adding events to every field. This will give you insight about which is that field which users hesitates to enter the information about. And so on and so forth and thus can look to change things with user behaviour in mind rather than guessing about it.

How to use these tools?

I wanted to integrate these tools with an rails application. Now the general way to use any 3rd party tool is to generate the api key/token , secret key by registering on the dashboard if these tools. And there are usually ruby gems / javascript existing for these tools to integrate with our application.

And it is exactly what you need to do. Lets look at both of these tools one at a time.

Intercom basically provides you 3 products. Intercom acquire, Intercom Engage and Intercom Resolve. The first 2 are of interest to me. Intercom acquire allows you to add messenger. Intercom engage allows you to add behaviour based on occurrence of events.

Now if you just want to add the chat box to your app, you can just use this gem intercom-rails.  The usage is pretty straightforward. Generate an app id through intercom dashboard. Use the gem to generate the config file for intercom. And use the generated app_id in the intercom config file. As I needed the messenger on just the production site, so I enabled it only for it

config.enabled_environments = ["production"]

and I wanted to record some custom data of the tracker users. Ex:

config.user.custom_data = {
  id: :id,
  user_name: :email,
  companies: Proc.new do |company|
    company.sites.map do |site|
    {
      id: site.id,
      name: site.name
    }
    end
end
}

Now what intercom does is whenever a user logs into your application, it sends this custom data about logged in user to the intercom just by using the custom_data defined in the initialisers file. If does not matter if the user logs in for the first time or 100th time.

Now stop here if you only wish to add the messenger, but if you are looking to track events and add behaviour based on those events, read on.

To add events, there is another ruby gem which you need to use and it is called intercom-ruby. This gem also lets you register users, add tags, send messenger apps. But then intercom-rails lets you register users to intercom. So we don’t really need to use intercom-ruby to send users data.

The documentation is pretty neat. So I will not repeat what is already mentioned there. You can read it here. But server side is not the only way to track data. There is also the javascript way of doing it. You can read about it here. I decided to do this with ruby as of now as it looked more neat.

Now follow these pictures to see how to setup behaviour based on events or filters:

Step 1: Open the Intercom engage dashboard

1

Now you can set up message name as an identifier for behaviour.

Step 2: Choose your audience

2

So there are various criteria available to choose from. You can setup any rule you want to filter your audience with. Also if you scroll down the list of user data, at the bottom of that list, you will find filtering based on events names.

3

As you can see there is an event name ‘Signed Up’. So whenever user signs up, this behaviour will happen, it could be send the user an welcome link in an email or send him an welcome message on his intercom messenger.

4

 

and you can create your message.

Now lets move onto mix panel.

The ruby gem which could be used for mix panel is mixpanel-ruby. Again you read the documentation about how to use the gem or read on the official website here.

Now if you have been following so far, you would have realised thats its a lot of code that you would need to write(ruby or javascript) to track events in multiple platform. So to avoid this hassle, there is another tool called Segment.

Segment allows you to send your customer data to just one platform i.e theirs and they take of the rest. They send the same data to intercom and mix panel and various other platforms they can be integrated with.

Now to use segment, there is a ruby library as well as javascript library as there are always. Looking at the documentation I realised the javascript library usage is also pretty neat and could be used easily. So we would be using that.

So to use it like any other js library, you need to include the segment script in web page head. And then there are couple of things you could do. You can either identify user which means let segment know which user is doing the event and send the event. Or you can directly send the event by calling track without any call to identify function.

Also for events which occur internally and does not really have a web page to show it for so that javascript can run, I ended up using the ruby library.

So, all you have to do is integrate just segment and it takes care of sending this data to all other platform of interest to you.

I hope it helps some of you.

Happy tracking.. !!!!

Rails command line options I hardly ever use.

This is the blog post about few things in Rails command line that I hardly ever use.

rake notes

Left a comment like #FIXME or #TODO on a method, but never actually end up changing anything or fixing anything about the method. If this sounds familiar, then rake notes helps you avoid just that. It will find all the files with comments beginning with OPTIMIZE, FIXME, OPTIMIZE.

Screen Shot 2016-05-29 at 12.49.47 pm

Also, we can add search for specific annotation, for ex: searching for just FIXME comments would be as simple as firing rake notes:fixme.

Also it lets us search for any custom annotation that we might be using in our code, rake notes:custom ANNOTATION=ADDTESTCASE

Read more about rake notes on the guides.

rails console –sandbox

If you wish to test some code, but you also want the data to be reverted back to original after you have tested the code, running rails console in sandbox mode lets you do just that.

Also one of the great use case is when I  run rails console on our production server.  I don’t have to worry about accidentally modifying or even worse deleting some data.

You can also run rails c -s to run the console in sandbox mode.

rake stats

Rails provide us with this rake task to calculate few statistics about our codebase. It tells us about line of code, code coverage, number of methods in controllers, models and average number of lines of code in a method among other things.

Screen Shot 2016-06-06 at 11.49.35 pm

If you are wondering how does it come up with this data, continue reading.

The code for all  the command line options in rails can be found under railties gem. So looking at the internals of railties , the code for calculating statistics in under code_statistics.rb & code_statistics_calculator.rb .

Interesting thing about this option is that when calculating statistics for test cases, it only considers unit tests. So, rspec is not considered by default. It is in the rspec-rails gem that it adds the spec directory in the look up path of code_statistics. Here is the method which does that in rspec-rails.

Also here are couple of things unrelated to the post, but I thought of sharing as I found out when I was reading some rails internals.

yuk

Did you know ?

a.) reload!

To reload the rails app on rails c , we all use the reload! command, so it will load the changes we have made to our code, except for any changes made in file under config folder. In that case, we have to exit and run rails c again.

Screen Shot 2016-05-30 at 2.07.40 am

If you notice, upon running reload! command, a message is printed on the console “Reloading…”. You can turn off this log by passing false to the reload! method. You can run reload! false.

Screen Shot 2016-05-30 at 2.10.10 am

Now lets look at the code, how this method reload! is actually defined. It is part of the railties gem. The method is defined in lib/rails/console/app.rb file. This is what it looks like:

Screen Shot 2016-05-30 at 2.14.10 am

The method reload! can accept a parameter whose default value is true. So now we know why passing false does not print the log.

b.) Commenting block of code in coffeescript

Like in ruby we use =begin &  =end to comment any block of code, also in javascript we use /* */. Similarly in coffeescript, we can put our block of code between  ###  some code here ###

Thats it for now. I will continue to share things via such posts or on twitter.

Mother’s day

Its 2 pm on a Sunday afternoon and I have already tried calling my mother twice on her phone. First her phone is switched off. Tried after half an hour, she is now out of coverage area.

I called up my father. I ask him if she is around. He says yes, but she can’t talk right now. And she will call me later. He asks me if all is ok? I said yes, I just called casually.

And all this has happened and it was not even 12 o’clock then.

Now as I am writing this, I re read it and it sounds to me like I am trying to describe how my mother does not have time to talk to her son on Mother’s day. I do have a penchant to make things unnecessarily dramatic.

But, a big NO, I am not trying do that.

Instead I am in complete awe of my mother. Actually of both my parents. But since it is Mother’s day, I will focus on her.

Ever since I have matured a bit i.e started paying attention to things she does in her day to day life. I have realised that she gets so much work done on a daily basis. While I change the bed sheets and think that a lot has been achieved for the weekend and I should take some rest. So I spend my weekend lying around on my bed on the newly put up bed sheet. While she works 7 days a week to run a family, I, on the other hand work 5 days a week in the comfort of AC when my work also allows to chill in the office i.e playing counter-strike, FIFA, chai-pe-charcha and what not.

And after all the hard work she does, she also has time to meet so many people. I mean I already feel down by the thought of some body coming for tea at home. Not that I don’t like meeting people (I really do ..!!!), I am just lazy. Lazy to take a bath, lazy to smile at people I don’t know or remember meeting when I was little, lazy to answer same questions over and over again. But not my mother. She actually make an effort to meet all these people. She goes to all family functions even if it involves travelling by train, car, bus or combinations of those. And everybody in the family loves meeting my mother too. And why wouldn’t they.

I can keep on writing about the stuff she does. And now that I have finally talked to her. She asks me if I had lunch, its almost 3. And obviously I haven’t. So I would stop writing here and go have lunch because she wants me to.

So, here it is, I am sorry mother for so many countless mistakes I have made. But you and only you could have still loved me the way you did and will do. (remember … I love being dramatic :P)

A very Happy Mother’s day to my mother and to all the mothers.

Time is an illusion but TimeZones are not.

Recently I have been working on handling timezones on a client project. Front end is AngularJs 1.3 and backend api is Rails api.

It is a very common scenario. User chooses what timezone the data should be displayed in. Api saves and returns all the dates in UTC format.

There are also a lot many client side validations comparing saved time with local time (in all permutation and combinations possible).

Tools used:

  1. Javascript Date object:
    new Date();

    While working with timezones, it is always good to know that Javascript Date object is always in local time zone. There is no direct option to set the timezone. You can change time by adding/subtracting timezone offset.

  2. moment.js: moment is a javascript library which provides pretty options to format the moment date object which is not possible with javascript Date object.
  3. moment-timezone.js: This library allows you to convert moment time object in different timezones.
    moment.tz("2015-12-23 20:30:00", "US/Central")

 

My approach:

  • Keep all the times in local timezone, whatever that may be .. the browser figures that out and by default all the times are in local timezone.
  • All the comparison of different time objects should be done when all the objects are in same time zone and preferably in local time zone.
  • When submitting the form, change the time zone of the time objects from local timezone to desired timezone. Will explain in more detail later.
  • When doing a get request of the resource(i.e the edit page of lets say business profile), change all the time objects to time in local timezone.

Reasons for the approach:

There are 2 main things that I am doing. First I am changing all time objects to time objects in desired timezone. And second, I am converting all the time objects in local timezone.

These are 2 different things.

In first case, I am doing this:

make_double_digit = function(num) {
  if(num < 10) {
    return ("0" + String(num));
  }else{
    return String(num);
  }
}

var start_time = new Date();
var year = String(start_time.getFullYear());
var month = make_double_digit(start_time.getMonth() + 1);
var day = make_double_digit(start_time.getDate());
var time_hour = make_double_digit(start_time.getHours());
var time_min = make_double_digit(start_time.getMinutes());
var time_sec = make_double_digit(start_time.getSeconds());

var new_date_format = [year, month, day].join('-') + " " + [time_hour, time_min, time_seconds].join(':');

return moment.tz(new_date_format, zone_name);

For ex: let start_time be 2015-dec-21 8:30:00 IST. So I am extracting year, month etc  from this date object and asking moment to return moment object for this string “2015-12-21 08:30:00” in US/Central timezone. It is not converting start_time to US/Central timezone but rather building time object in US/Central timezone.

In second case, I am doing this:

var timezoned_start_date = moment(vm.info.start_date).tz('US/Central');

var start_date = new Date();
start_date.setYear(timezoned_start_date.format('YYYY'));
start_date.setMonth(timezoned_start_date.format('MM') - 1);
start_date.setDate(timezoned_start_date.format('DD'));
start_date.setHours(timezoned_start_date.format('hh'));
start_date.setMinutes(timezoned_start_date.format('mm'));
start_date.setSeconds(timezoned_start_date.format('ss'));

I am converting the time object obtained in response which is in UTC format to US/Central time zone. and then building a javascript Date object from it.

Now start_date object is in local time zone and we can use this to compare it with Date objects as needed.

Conclusion:

Dealing with timezones is not very straight forward. And this is not the only way to do it. A lot of people(read StackOverflow) suggested that I convert timezones by adding/subtracting timezone offsets. I found this to be more easy to manage in future and for other team mates to understand.

Review of 2015

Now that we have entered into December, I think I can review 2015 which passed by at a lightning speed.

Let me start with some resolutions I made with myself for 2015. Here you can read about it.

My last years resolutions.

I said that I would:

1. Understand computer networks

Result: Failure.

Thought process: The plan was to read a book or two until I have a basic understanding of the topic.

Reason for failure/success: I think this went really low on my list of priorities.

2. Stick to one side project

Result: Mixed feelings.

Thought process: As history have suggested, I keep wandering from ideas to ideas and never actually end up doing one thing completely. So I had decided last year that I will be working on just one thing. And believe me I had said no to friends for side projects they wanted me to be involved with. I ended up starting ‘At a happy place‘  in January and it has been 11 months since I have been working on this. Although for the last few months I have not been able to give it time as much as I would have wished to. But I think the project still needs a lot more work and I have some really cool ideas for the project which I have no idea how I can implement them. 🙂

 

Reason for failure/success: I would not go ahead and call this a failure. This was definitely not a failure but I would also say I am not fully satisfied with the progress so far on it.

3. Do more open source contributions

Result: Mixed feelings.

Thought process: I started doing open source contribution somewhere around mid 2014 and I wrote this straight from the heart post about contributing to open source.

You can see my github open-source contribution chart for this year. Its not the prettiest thing you will see but it has improved a lot this year. But that being said I could have done a lot more.

Screen Shot 2015-11-30 at 10.04.33 pm

Reason for failure/success: I can`t call this an outright success or a failure either. I would rather say that there is a lot more I could have done.

4. Read more non-tech books(at least 4)

Result: Success.

Thought process: The plan was to read more books to get a broader sense of things and pick up style of writing of the people who are professionals as I believe I need to improve upon my style of writing.

I ended up reading 4 John Grasham`s novel. I really liked his style of writing. I also read a couple of other non-fiction books. In all I think I read 6 books.

Reason for failure/success: I would definitely call this a success as I have achieved the target I had set for myself. But now looking back at it, I think I will set a more difficult target for myself for the future as reading 4 books in an year is no big deal.

Now moving on to see how things went in my professional and personal life.

Professional Review:

  • Spoke at 2 international conferences in Belgium and Israel. So this makes it 5 conferences so far.
  • Became a collaborator on medium_editor open source project.
  • Started working on angular js.

Personal Life:

  • As I had mentioned in my last years post, we were looking forward to welcome a special new member to our family. God blessed my sister with a healthy baby and we call him “Manny”.IMG_4174
  • I did travel this year. I visited New York, Chicago, Ghent, Barcelona and Jerusalem this year. And also did 3 or 4 local trips with friends. So it was all round a fun filled year.
  • I did skydiving from 5000 meters in Barcelona which was pretty scary and fun at the same time. Here is the video.
  • I ran my first ever 10kms marathon.
  • A friend of mine and I had a horrific accident on a road trip. But all is good now.

 

What about 2016 ?

I have not actually thought of resolutions for this year. But I would like to improve upon a few things namely contributing to open source projects as it is a very satisfying experience. I would like to speak at more conferences as it gives me opportunity to make new friends and travel to different places. I would also like to improve ‘At a happy place’ as I know it could be a really good project. I would definitely like to learn a lot more about computer science in general and not just things related to my work. Also I would like to taste the ‘startup bug’ about which I have been thinking (thats all I have done) for past 2 years now.

Some moments from 2015:

DSC_0551

IMG_1279

 

I hope the world have a great 2016 which is full of peace and harmony and less of terrorism… and as Ellen says it “Be kind to one another everyone” …. !!

Using Null Object Pattern in Rails to improve code quality

If you have not read my previous post I wrote about Service classes, I would suggest to read that first as it is in continuation to the previous post. This is the link to previous post.

In this post we are going to look at another pattern that helps us in refactoring code, especially helps us in removing conditions. It is called Null Object Pattern.

This code is used to download the data dump based on some rules as always. But here is my 2 cents, do not even try to understand the code.

   def download_measurements
    specification = @inspection.specification
    serial_numbers = @inspection.get_serial_numbers
    inspection_results = @inspection.inspection_results.includes(:inspection_serial_number)
    row_list = []

    row_list << ['unit number', 'serial number', 'gage id', 
                 'inspected by', 'inspected on yyyy-mm-dd', 
                 'place', 'measurement', 'bonus tolerance', 
                 'actual measurement', 'text', 'remark', 
                 'non-conformance number']

    serial_numbers.each_with_index do |serial_number, index|
      specification.feature_repeats.times do |place_index|
        place_number = place_index + 1
        ir = inspection_results.collect{|i| 
             i if i.serial_number.number == serial_number}.
             uniq.compact.first

        specification.readings_required.times do |measurement_index|
          measurement_number = measurement_index + 1
          rm = ir.nil? ? nil : 
            ir.result_measurements.where(:place_number => place_number, 
            :measurement_number => measurement_number).first
          
          inspected_on_date = ir.nil? ? nil : ir.inspected_on.nil? ?
          nil : ir.inspected_on.strftime('%Y-%m-%d') 
          
          measurement_value = rm.try(:actual_measurement).nil? ? 
          nil : rm.try(:actual_measurement)
         
          measurement_value = rm.try(:measurement_result) == 'P' ? 'PASS' : rm.try(:measurement_result) == 'F' ? 'FAIL' : nil if specification.result_type == 'Attr'
          row_list << [index+1, serial_number, ir.try(:gage_serial_number), 
                       ir.try(:inspected_by), inspected_on_date, place_number, 
                       measurement_number, nil, measurement_value, rm.try(:information),
                       ir.try(:remarks), ir.try(:non_conformance_number)]
        end
      end
    end
    file_name = "Template" + "-" + @inspection.balloon + "_#{Time.now.to_i}.xlsx"
    file_path = "#{Rails.root}/tmp/#{file_name}"
    XlsxHelper.generate(row_list, file_path)
     send_file(File.open(file_path), :type => 'application/vnd.ms-excel', :filename => file_name)
   end

We clearly need to refactor because this code is difficult to understand even for the person who has written the code, forget the team who is working on the same project. And since this code is difficult to understand, it is difficult to debug and it is difficult to change as changes will come as they always do.

As I can see that this whole code first of all should not be a part of a controller method. Since the purpose of this code is to generate a sort of data dump in a csv file, I would make it a service class and call the methods of that class as explained in the previous post.

By just glancing at the code, I can understand that there is a header added somewhere in there and then there is some logic to add the data. So I will start by making a service class. And make these empty methods.

class ExportIdeData
  def initialize(inspection)
    @inspection = inspection
  end

  def run!
  end

  private

  def header
  end

  def add_data
  end
end

And my aim is to make controller code look like this:


  def download_measurements
    ide_data_runner = ExportIdeData.new(@inspection)
    ide_data_runner.run!
    generate_file(ide_data_runner)
  end

Now that we have defined a service class and has added methods to it, lets focus on this line of the old code:

  inspected_on_date = ir.nil? ? nil : ir.inspected_on.nil? 
    ? nil : ir.inspected_on.strftime('%Y-%m-%d') 

In this code, we see a complex logic of conditions which decide what value to return. If ir object does not exist, we want the value nil, if ir object exists but no value for attribute inspected_on exists in db, then we want nil. And if the value exists in db, we want to change the format of the date and then return that value.

If I have to apply such conditions to lets say only 1 attribute of the model, I wont mind adding all the conditions for once. But in our use case, when we have to apply such logic to multiple attributes of a model, it won’t just make sense to compromise on the code quality. And thus, Null Object Pattern helps us in cleaning our code.

So these are some of the changes that we make to our new service class.

  def header
    @rows = [['unit number', 'serial number', 'gage id',
              'inspected by', 'inspected on yyyy-mm-dd',
              'place',  'measurement', 'bonus tolerance',
              'actual measurement', 'text', 'remark', 
              'non-conformance number']]
  end

  def add_data(sheet)
    @serial_numbers.each_with_index do |sn, index|
      @place_measurements.each do |pc|
        ir = find_inspection_result(sn)
        rm = find_result_measurement(ir, pc[0], pc[1])
        data = [index+1, sn.number, 
                ir.serial_number, ir.inspected_by, 
                ir.inspected_on_rep, pc[0], pc[1], nil, 
                rm.measurement_value, ir.remarks, 
                ir.non_conformance_number]

        add_row_with(sheet, data, @header_values, 
                     height=27.571428571428573)
      end
    end
  end

Now do not get too much into the code. Just have a quick look at it, this code looks clean.The whole code is not important for you to understand for what we are trying to achieve here, pay attention to these lines:

  ir = find_inspection_result(sn)
  rm = find_result_measurement(ir, pc[0], pc[1])

These are some of the methods that that we need to define:

  def find_inspection_result(serial_number)
    ir = @inspection.results.where(:serial_number_id => serial_number.id).first
         || NilInspectionResult.new
    ir
  end

In the above code, I find the inspection result object and if it fails to find such object in database, then we initialize a new object of the class NilInspectionResult.
Keep in mind these are classes that do have a corresponding database table. It is just a plain ruby class which happen to be created under models folder.

This is how it looks:

class NilInspectionResult
    
  def gage_serial_number
    nil
  end
    
  def inspected_by
    nil
  end 
    
  def inspected_on_rep
    nil
  end
  
  def remarks
    nil
  end
              
  def non_conformance_number
    nil
  end 
end 

So, when we do not find an result object in database, a new object of our newly defined class NilInspectionResult has access to these methods defined above which a valid result object would have.
What I mean here is that remarks, non_conformance_number etc are attributes or methods defined in InspectionResult model and so we define the same methods in our new class also with values we expect them to return.

This also helps us in keeping our code clean but also easy to change. For ex: In future if client wants that instead of returning nil as the date for the case when result object does not exist, return the current date. So I just have to change the method in NilInspectionResult class to return current date and not nil.

Similarly look at this code:

measurement_value = rm.try(:actual_measurement).nil? 
  ? nil : rm.try(:actual_measurement)

‘try’ is just a fancy way to write if else conditions. And it is suggested to avoid such conditions whenever possible. So I would change the code to something like this:

def find_result_measurement(ir, pn, pm)
  rm = ir.result_measurements.where(:place_number => pn, :measurement_number => mn).first 
       rescue NilResultMeasurement.new
  rm
end

Here we find the result measurement object which also depends on the ir object. And in our previous method we have found the result object ir, and it could contain the actual inspection result object or a nil template of it(NilInspectionResult.new).

So in code to find the result measurement object, I use rescue to initialize a NilResultMeasurement object. And I add a class like this:

class NilResultMeasurement
  def measurement_value
    nil 
  end

  def pass_fail_result
    'N/A'
  end 
end

Now I think I do not need to explain why this above code is what it is. But one of the most important thing to remember when using Null Object Pattern is not to overuse it.

So I hope this helps you in avoiding conditional blocks whenever and wherever feasible to use this pattern. And I will say this again, we have not reduced the number of lines of code, we have infact increased the number of lines of code. But we have made our code looks much cleaner, easier to change and understand.

The last lecture by Randy Pausch

This book is part of my new years resolution . It is second in the series.

It is not a fiction novel. It is as true as it gets out there. It will change the way you look at life and if it will not bring out such radical changes in your life, it will at least make you re-think of what you have been doing with your life until now. And if this does not bring any of these things to your mind, I do not know what will.

It is the story of Randy Pausch who was a professor at Carnegie Mellon University. He was also many other things. In 2006, he was diagnosed with pancreatic cancer. And he died in 2008. But he did gave a ‘last lecture’ at Carnegie Mellon. And boy that lecture was something. And he said that this lecture was the last fake-head he gave. Go ahead and find out what the term fake-head means by reading the book. I am not going to spoil it for you. This lecture was not meant for the audience inside the auditorium but for his three kids who were so young at that time to really understand or remember any of this. So, he wanted to leave something for his kids to look upto to find out about their dad. And he also said if he was a painter, he would paint something for them. And since he was a professor, so he lectured for them.

Now I am not going to give out any more details about the book. But I am going to tell you my favourite lines from the book or stories from it as this book is full of amazing quotes and awesome stories. Here goes:

1.

Brick walls are there for a reason, it gives us a chance to show how badly we want something

Brick walls here are the obstacles you face when you want to achieve something. And if you really want to do it, you will find a way to do it.

2.

Time is all you have. And one day you may find that you have less than what you think.

This one he learnt the hard way when he was diagnosed with pancreatic cancer and was given a 3-6 months of healthy life. And I have read it time and time again, people who suffer from such diseases or somebody who have near death experience, they all have same thing to see. That life is precious, do not cry about petty things in life and do what you gotta do.

3.

Experience is what you get when you do not get what you wanted

This is a great advice. We all get disappointed when we fail at something after we have worked so hard to achieve it. But I will remember this one that I have just found out a way to fail at something and I am not going to repeat the same mistakes.
I read this in an article about entrepreneurs. It said that companies are more inclined to hire people who have failed at something, specially at the level of CEO’s as they are lot less likely to fail than someone who has never tasted failure.

4. On when people complain about government or an organisation spend billions of dollars to achieve something like trying to land people on the moon. When that money could be used to serve the hungry and try and solve the hunger problems in the country and the world.

When you use money to fight poverty, you are working at the margins. But when you’re spending billions to put people on the moon, you are inspiring all of us.

True indeed ..!!

5.

Complaining does not work as a strategy. We all have finite time and energy. Any time we spend whining is unlikely to help us achieve our goals. And it won’t make us happier.

6. And a story for the last.
Randy said this is a lesson for the folks at management.
When Randy and his sister( who is 2 years older than him ) were kids. They went to Disneyland. And they went to one of the gift shops. And they bought a salt and pepper bottle from the shop. And when they went out of the shop, it fell from their hands, hit the ground and broke.
A man who saw this happen told Randy to go back to the shop and ask them to replace the gift. And Randy said why would they do that. It is not their fault. I broke it. I should have been careful.
Then the man said, there is no harm in trying. So the kids went back to the shop and told the guys working at the shop about how exactly it broke. And the shopkeeper replaced the broken one with a new one and did not ask them to pay for it again. And he also said that it was probably our mistake that we did not package it good enough to not brake when it accidentally fells on the ground. So the kids were obviously happy. They went back home and told this story to their parents and they always remembered this little gesture from the shopkeeper. And over the years it was a tradition sort of to visit Disneyland and Randy’s parents spent more than 100,000$ in tickets and souvenirs. So all in all it was good investment of $10 by the Disney shopkeeper which evaluated to $100K over the years.

I think this is crucial to every one as it not only tells us how we should treat our customers, but also tells us how we should treat each other. How there should be humility, forgiveness and willingness to help each other and it is only then we can help to make this world a better place to live.

I hope you will read the book and get inspired. And this is the youtube link to the video of the last lecture by Randy Pausch.

Using Service Classes to improve code-quality

ServiceClasses are nothing but plain old ruby classes. But they go a long way to keep your controllers thin and models thin. To show what I mean by this, lets look at some code. This is the code in controller to download data following some set of rules.

class ImportExportsController < ApplicationController

  def multi_column_download_template
    rows = []
    header = ['Spec Number', 'place', 'measurement',
              'measurement number']

    file_name = "multi-column-template" + "_#{Time.now.to_i}.xlsx"
    file_path = "#{Rails.root}/tmp/#{file_name}"

    specs = @inspection_master.row_no_wise_specs
    specs.each do |spec|
      header << spec.id.to_s + "#" + spec.characteristic
    end
    rows << header

    feature_repeat = specs.collect(&:feature_repeats).max
    measurement    = specs.collect(&:minimum_readings_required).max

    inspection = Inspections::Inspection.new(
                   :feature_repeats => feature_repeat, 
                   :minimum_readings_required => measurement)
    place_cols = inspection.calculate_records

    @inspection_master.inspection_serial_numbers.each_with_index do |d, index|
      rows << [index+1, d.place, d.measurement, d.serial_number]      
    end
  
    generate_and_download_file(rows, file_path, file_name)
  end

  def generate_and_download_file(rows, file_path, file_name)
    #code which generate a xls file and send it as response
  end

end

This is bad. The controller should not look like this. It is not thin. It is difficult to understand. And the bigger problem which is difficult to see the moment we write code is that things will change. Believe me change is the only constant. And the cost of those changes will be much more higher. So comes in Service classes.

The ideology behind service classes is that it should have only one purpose to serve and nothing else.

And I can clearly see a purpose which our new service class should serve and that is to provide the data for our template download and nothing else. I keep the service classes under app/services folder. Also I do not think I need to iterate much on the importance of the name of the service class you choose. It goes without saying that the name should tell the purpose it is going to serve. I will name my service class MultiColumnDownload and I put it under app/services folder. This is how the service class looks like:


class MultiColumnDownload

  def initialize(inspection_master_id)
    @inspection_master = InspectionMaster.find inspection_master_id
    @file_name = "Multi-column-template-" + "_#{Time.now.to_i}.xlsx"
  end

  def file_name
    @file_name
  end

  def file_path
    "#{Rails.root}/tmp/#{@file_name}"
  end

  def run!
    initialize_header
    find_specs
    insert_data
    @rows
  end

  private

  def initialize_header
    header = ['Spec Number', 'place', 'measurement', 'measurement number']
    specs = @inspection_master.row_no_wise_specs
    specs.each do |spec|
      header << spec.id.to_s + "#" + spec.characteristic
    end
    @rows = [header]
  end

  def find_specs
    @specs = @inspection_master.specifications
  end

  def find_feature_repeats
    @specs.collect(&:feature_repeats).max
  end

  def find_minimum_readings_required
    @specs.collect(&:minimum_readings_required).max
  end

  def insert_data
    @inspection_master.inspection_serial_numbers.each_with_index do |d, index|
      @rows << [index+1, d.place, 
                d.measurement, d.serial_number]
    end
  end

end

And now our controller looks like this:


class ImportExportsController < ApplicationController

  def multi_column_download_template
    download_object = MultiColumnDownload.new(@inspection_master.id)
    rows = download_object.run!
    generate_and_download_file(rows, download_object.file_path, download_object.file_name)
  end

  def generate_and_download_file(rows, file_path, file_name)
    #code which generate a xls file and send it as response
  end
end

In the controller, I just have to instantiate an object for our newly created service class and we call this object service object. And call the run! method. Keeping our controller code very thin just the way it should be.

Now this is the code I like and proud of. Not the earlier one. Now in future if I want to change the formatting of this template or add or remove some data, I know where to look for the code and where to make changes. Also I do not have to change anything in my controller code as long as the service class returns data.

Have we reduced the number of lines of code? NO. In fact we have increased the lines of code by at least one and half times.

Have we reduced the complexity of code? YES. Now the code is easier to understand and maintain over long term.

I can only hope that you find this useful and make service objects without any fear of increasing code complexity or number of lines of code. I certainly do..!!!

(p.s. I have reduced the complexity of the controller to not deviate away from what we are trying to understand here.)

Whats new in Rails 5 ?

I watched the live stream of DHH’s Railsconf 2015 keynote yesterday and he was talking about Rails 5. Here is a quick look at the new things I found out thats coming in Rails 5.

1. Turbolinks 3

Consider you have a simple blogging application where you can add posts and comments. So there are basically 2 important sections of a blog page. One the blog post self and second are the comments. Section one is not going to change frequently. But section two which is comments will keep changing. So in turbolinks 3, you can tell the server to just update the comment section and keep the rest of the page as it is, as there is no need to refresh the whole page. This is an attempt to reduce the page load time even further and improve the experience for the user.

You can specify that this part of the page will not change by mentioning something like this “turbolink-permanent” in HTML.

2. ActionCable

This sounds interesting. This is an attempt to implement WebSockets the Rails way. Making it super easy to subscribe to a channel, sending and receiving messages over the channel.

Screen Shot 2015-04-21 at 8.06.09 pm

Screen Shot 2015-04-21 at 8.06.58 pm

The code looks like a controller code, so I think before_filters should also work with classes inheriting from ActionCable::Channel::Base.

P.S.
1. Code snippets are screenshots from DHH’s keynote live streaming.
2. This is just glimpse of what I took away from his keynote. I did not see the whole thing from the start, so I am not sure what else he talked about.

At a happy place

For the past 3 months I have been working on a side project. It is called ‘At a happy place‘. Here is the link to it, athappyplace.

The story behind this project goes on to my love for travelling and strangely more so of reading about travelling. I wanted to build a platform where folks who like writing can share their stories and who like reading can enjoy these stories.

But then there are already tons of blogs out there with this information. Yes, there are. And that is the problem. All those blogs and ’10 places to see in India’ kind-ish posts are not enjoyable to read.

I want people to write about the places they loved, the one they never wanted to leave. I want people to write about their ‘Happy Places’ which genuinely made them happy, and not because it was one of the must visit place. And such posts make for a good reading too.

I wanted to build a product which is obviously dead simple to use, but also which reflects all those things I have mentioned and I think I have achieved that with athappyplace.

The project is open-source. Here is the codebase.
Would love to hear your thoughts…!!!

Happiness quite unshared can scarcely be called happiness; it has no taste.
― Charlotte Brontë