Blog Archives

Sharing code in FactoryGirl

Posted in Code, Inside TFG, Ruby on Rails, Tips and Tricks

Sharing Code

Sharing code between factories in FactoryGirl takes a little bit of effort. Here’s one way of accomplishing it, which I patched together from reading this GitHub Issue:

Sharing code in FactoryGirl

First, we create a module with the shared code. Then we include it into the FactoryGirl::SyntaxRunner. To keep your code organized I’d recommend you put all your shared modules for FactoryGirl in a single folder.

You can put it under something like spec/factories/support/ or spec/support/factory_girl_support/. In the following examples I’m going to use the latter.

So you might have a file like below:

# spec/support/factory_girl_support/user_support.rb

# I use a namespace here so there's no chance of overriding any other class or module.
module FactoryGirlSupport
  module UserSupport
    def some_shared_method
      "whatever you want to return!"
    end
  end
end

# This will make the methods in the FactoryGirlSupport::UserSupport available in your factories
FactoryGirl::SyntaxRunner.send(:include, FactoryGirlSupport::UserSupport)

Now you just need to require these files in your rails_helper.rb. You can do that with:

Dir[Rails.root.join("spec/support/factory_girl_support/**/*.rb")].each { |file| require file }

Now you can use the method in the module above in your factories like this:

FactoryGirl.define do
  factory :user do
    email { some_shared_method }
    name  { some_shared_method }
  end
end

That’s all there is to it.

Refactoring rails controllers the right way

Posted in Code, Inside TFG, Ruby on Rails, Tips and Tricks

Rails developers will often live by the mantra “Skinny Controller, Fat Model” when writing Rails controller code.

At first it can seem like a fairly reasonable practice. The examples in Darcy Laycock’s SitePoint article “10 Ruby on Rails Best Practices” are a great example of this. Another good example is Jamis Buck’s 2006 article “Skinny Controller, Fat Model”.

However, the name of the pattern is misleading and reinforces the noxious idea that any class should be “fat”.

In this article I will discuss ways that we can refactor Rails controllers that will not cause bloat in any models.

Skinny Controller, Fat Model

The “Skinny Controller, Fat Model” practice often results in code in controllers being moved into a model. This reduces the complexity of the controller and moves the responsibility of managing instances of the model back into the model itself. For example, if we have the following controller:

class EventsController < ApplicationController
  before_action :authenticate_user!
  after_action :verify_authorized

  def approve
    @event = Event.find(params[:id])
    @event.state = :approved
    @event.approved_at = Time.now
    @event.approved_by = current_user
    @event.save!

    redirect_to(root_path)
  end
end

The specs for the controller might look a bit like:

require 'rails_helper'

describe EventsController do
  describe 'PATCH approve' do
    subject { patch :approve, id: event.id }

    let(:event) { FactoryGirl.create(:event, approved: false) }

    context "when authenticated && authorized to approve the Event" do
      sign_in_as(:administrator)

      describe "approving the Event" do
        let(:current_time) { Time.now }

        before { Timecop.freeze(Time.local(1990)) }
        after  { Timecop.return }

        specify { expect { subject }.to change { event.reload.approved? }.to(true) }
        specify { expect { subject }.to change { event.reload.approved_by }.to(current_user) }
        specify { expect { subject }.to change { event.reload.approved_at }.to(current_time) }
      end

      it { should redirect_to(root_path) }
    end

    context "when not authenticated to approve the Event" do
      it_behaves_like "a controller action that has failed an authentication check"
    end

    context "when not authorized to approve the Event" do
      sign_in_as(:public_user)

      it_behaves_like "a controller action that has failed an authorization check"
    end
  end
end

The “Skinny Controller, Fat Model” practice might be followed by moving the approval code into the model, leaving the controller looking something like:

class EventsController < ApplicationController
  before_action :authenticate_user!
  after_action :verify_authorized

  def approve
    @event = Event.find(params[:id])
    @event.approve!(current_user)

    redirect_to(root_path)
  end
end

The process of approving an Event has been abstracted and defined in the Event model. Not only is our controller easier to understand, but the approval process is now re-usable and our tests for the controller are easier to write. For example, we could stub out the approve! method rather than having to check all the side effects of approving an Event. Here’s what our controller specs might look like now:

require 'rails_helper'

describe EventsController do
  describe 'PATCH approve' do
    subject(:approve_event) { patch :approve, id: event.id }

    let(:event) { FactoryGirl.create(:event, approved: false) }

    context "when authenticated && authorized to approve the Event" do
      sign_in_as(:administrator)

      it "approves the Event" do
        expect(event).to receive(:approve!).with(current_user)
        approve_event
      end

      it { should redirect_to(root_path) }
    end

    context "when not authenticated to approve the Event" do
      it_behaves_like "a controller action that has failed an authentication check"
    end

    context "when not authorized to approve the Event" do
      sign_in_as(:public_user)

      it_behaves_like "a controller action that has failed an authorization check"
    end
  end
end

However, there is a cost. The Event model has grown larger and the number of responsibilities in the Event model has increased. This makes the class harder to understand, which in turn means that:

  • The risk of defects being introduced is increased
  • Refactoring the class is more difficult
  • Tests are harder to read and write. When tests are hard to write, lazy developers sometimes “forget” to write them.
  • Most importantly: The code becomes a pain in the ass to work on

To prevent bloating our model, this workflow needs to be encapsulated in another class.

Encapsulating the workflow in a model

My preference for encapsulating such a workflow is to create another class. I just think of this class as another model, but some folk prefer to call them domain objects. Here’s a decent explanation from StackOverflow.

In the example above, I’d create a model called Event::Approver to encapsulate approving an Event.

I nest Event::Approver under Event because it is a subset of the workflows in the application that are related to the Event model.

Due to the way that Rails loads files, the file will need to be in the folder app/models/event. This is convenient since I can now look in this folder to find any workflows related to the Event model.

The Event::Approver model will look like:

class Event::Approver

  attr_reader :approver, :event

  def initialize(event, approver)
    @approver = approver
    @event = event
  end

  def approve!
    event.status = :approved
    event.approved_at = Time.now
    event.approved_by = approver
    event.save!
  end

end

The specs for this model will be equally concise, looking something like:

describe Event::Approver do
  describe "#approve!" do
    subject { Event::Approver.new(event, user).approve! }

    let(:event) { FactoryGirl.create(:event, approved: false) }
    let(:user)  { FactoryGirl.create(:user) }
    let(:current_time) { Time.now }

    before { Timecop.freeze(Time.local(1990)) }
    after  { Timecop.return }

    specify { expect { subject }.to change { event.reload.approved? }.to(true) }
    specify { expect { subject }.to change { event.reload.approved_by }.to(user) }
    specify { expect { subject }.to change { event.reload.approved_at }.to(current_time) }
  end
end

The implementation of this class is not especially important. For example, some developers will prefer not to use an initializer and will pass the event and approver directly into the approve! method.

This class is easy to understand as it has a single responsibility. The class is small enough that I can read it and understand it in only a few seconds. I don’t need to search in some 800 line of code Event model for this single method.

Similarly, the tests for the class are straight forward and easy to read and understand. The test file will be quite small, as opposed to the colossal test file that exists for the Event model.

One of the less thought about features of composing classes this way is that if I need to look at the git history for this class, 100% of the commits will be related to this single piece of functionality. I recently found out that is invaluable for when you need to work out why a process has changed. Imagine sifting through all the commits on the Event model trying to work out where function X changed (yeah, I know you can use grep – but what if the method name changed? A pain in the ass, for sure).

With our new model the controller would now look slightly different than before, but is just as skinny:

class EventsController < ApplicationController
  before_action :authenticate_user!
  after_action :verify_authorized

  def approve
    @event = Event.find(params[:id])
    Event::Approver.new(@event, current_user).approve!

    redirect_to(root_path)
  end
end

In defence of “Skinny Controller, Fat Model”

To be perfectly honest, I doubt the intention of “Skinny Controller, Fat Model” is to just blindly jam all your controller code into ActiveRecord models. I suspect it’s more likely that it means “Skinny Controllers, Fat Model Layer”.

That being said – I think it is all too easy for inexperienced developers to take the phrase literally and do just that. In fact, I have six years of experience working with developers who have zealously bloated ActiveRecord models to prevent controllers from having more responsibilities. Including myself for a time.

In summary…

When it comes to refactoring, cleaning up a controller doesn’t really benefit the health of your application if it causes a model to get dirtier.

Rather than adding more and more responsibilities to ActiveRecord models, we can create new single-purpose classes (domain objects, if you like) to encapsulate these workflows.

By doing this we make smaller classes that are easier to understand, easier to maintain, and easier to test.

Further Reading:

  1. “”Fat model, skinny controller” is a load of rubbish” by Jon Cairns
  2. “Skinny Controllers, Skinny Models” by Joe Ferris

Handling race conditions with Rails’ pessimistic locking

Posted in Code, Ruby on Rails, Tips and Tricks

I recently worked on a Rails application where race conditions were causing issues in the background tasks operating on my ActiveRecord objects. In this article I will explain how I used pessimistic locking to get around these issues.

Defining an example

Firstly, consider a sample Rails application with the following features/rules:

  1. Administrator can see a list of clients;
  2. Administrator can visit the client’s profile page and send an SMS to the client;
  3. Administrator cannot send more than one SMS to each client per day;

Let’s keep the implementation as simple as possible. Consider a Client model with an attribute last_sms_sent_on and the following class (which can be used by a controller or a Sidekiq task):

class ClientSMSSender
  def self.perform(client, message)
    client.transaction do
      if client.last_sms_sent_on.blank? || !client.last_sms_sent_on.today?
        client.last_sms_sent_on = Date.today
        client.save
        SMSGateway.send(client.phone_number, message)
      end
    end
  end
end

Analysing the race condition issue

Imagine that there are some external issues and SMSGateway.send hangs for an average of 30 seconds. In this meantime another administrator makes a new request to send an SMS to the client. Due to race conditions we’ll end up sending more than one message to the client on the same day.

This is what will happen:

  1. Administrator A makes a request (Request A) to send an SMS to the client
  2. Client has not received any messages today
  3. Request A is hanging due to external issues
  4. Administrator B make a new request (Request B) to send the same SMS to the client
  5. Client has not received any messages today (the Request A still hanging)
  6. Request B is hanging due to external issues as well
  7. Request A finishes and client.last_sms_sent_on is updated
  8. Request B still holding the previous state of the client object
  9. Request B finishes, send the SMS again and re-update client.last_sms_sent_on

Work around

In order to work around that issue, you can make use of the method with_lock from ActiveRecord::Locking::Pessimistic.

Have a look at the code below:

class ClientSMSSender
  def self.perform(client, message)
    client.with_lock do
      if client.last_sms_sent_on.blank? || !client.last_sms_sent_on.today?
        client.last_sms_sent_on = Date.today
        client.save
        SMSGateway.send(client.phone_number, message)
      end
    end
  end
end

Under the hood, with_lock:

  1. opens up a database transaction
  2. reloads the record (in order to obtain the last state of the record)
  3. requests exclusive access to the record from the database

When using with_lock: 

  1. Administrator A makes a request (Request A) to send an SMS to the client
  2. Request A locks the client record in database
  3. Client has not received any messages today
  4. Request A is hanging due to external issues
  5. Administrator B make a new request (Request B) to send the same SMS to the client
  6. As the client is currently locked by Request A, the Request B hangs until the database releases the record
  7. Request A finishes and client.last_sms_sent_on is updated
  8. Database releases the client record
  9. Request B (was hanging and waiting for database) now starts the execution
  10. Request B locks the client record
  11. Request B reloads the client record in order to obtain the latest state of the object
  12. Client has already received a message today
  13. Request B finishes without sending a new SMS.

If you’re still confused about that, here’s an easy way to see how with_lock works from your Rails console:

  1. Grab any existing Rails project and open up two Rails consoles
  2. In the first console execute the following:
    u = User.first
    u.with_lock do
      u.email = "test@thefrontiergroup.com.au"
      u.save
      sleep 40 # emulates a very slow external process
    end
    
  3. In the second console execute:
    u = User.first
    u.email = "test2@thefrontiergroup.com.au"
    u.save
    

You’ll notice in the second console that the execution of u.save will hang until the first console finishes the whole process.  You should be careful not to lock your entire app unnecessarily, otherwise you’re likely to introduce a new bottleneck.

Conclusion

The method with_lock is handy, but use it sparingly. Sticking with_lock everywhere might bring you good business logic consistency, but it can come at the expense of performance.

Blockchain Analytics with Cayley DB

Posted in Code, Data Analytics, Inside TFG

Bitcoin (and consequently, the Blockchain) have been making waves in the media over the past few years.

In this blog post I will be covering the process of building relationships between blocks, transactions and addresses using Google’s Cayley DB. With this information I may be able to pinpoint important transfers of value and also build an ownership database on top of it to track high value individuals.

I’m going to call this project Bayley, an amalgamation of “Bitcoin” and “Cayley”. I never was that creative.

The Blockchain

So what are the advantages of the Blockchain? What’s so special about it? For starters, for the first time in history we have a database that:

  • Can’t easily be rewritten
  • Eliminates the need for trust
  • Resists censorship
  • Is widely distributed.

A lot of people like to call it a distributed ledger. This is true if you use Bitcoin as a currency. The Blockchain however, has the capability for much more. As a new and possibly disruptive technology I figured it would be a good idea to learn more about it. In the process we also might glean enough of its processes for building unique services on top of the Blockchain.

The Database

I originally tried to work with this project using MongoDB. I ended up shelving the idea as MongoDB is not suitable for this task. The schema is consistent across blocks and I need to be able to easily find relationships between datapoints.

I had a look at LevelGraph and Neo4j but in the end decided to go with Cayley. Cayley has been explored previously by The Frontier Group, and is a very new technology and I wanted to learn how to use it.

Setup Considerations

The first step will be to synchronise a copy of the blockchain locally for your use. I used the testnet instead of mainnet for testing purposes.

Originally I used BTCD as I wanted a server-side, headless daemon. Bitcoin Core can do this, but not in OSX. I constantly ran into bugs and inconsistencies such as:

  • RPC setup using a different variable names making existing libraries that hook into Bitcoin Core useless
  • JSON batch requests not supported

In the end I just opted to run an instance (with GUI and all) of Bitcoin Core on my machine. Get it here!

Before starting to synchronise the Blockchain it might be useful to note that transaction data is not saved in the local blockchain to conserve disk space. Transaction indexing can be turned on with the commandline switch -txindex or adding the line txindex=1 to your bitcoin.conf.

RPC needs to be enabled. Using RPC calls to the Bitcoin daemon will allow you to pull out the block data.

Spinup instructions

Overview of Process

From a high level, the process will look like this:

  • Get block hashes from height
    • Get blocks from block hashes
  • Send an HTTP POST request to Cayley DB of the above data

This does not take into account transaction data either. That will be a topic for a future blog post. So lets get started!

Setting up Bitcoin Core

The Bitcoin Core standard conf file has a lot of stuff in there, but in general you’ll need to make sure the following lines are as follows:

txindex=1
testnet=1
server=1
rpcuser=bitcoinrpc
rpcpassword=BHaVEDoMkVr1xKudcLpVbGi2ctNJsseYrsuDufZxwEXb
rpcport=8332

The rpcpassword is autogenerated by Bitcoin Core. You can use an environment variable if you’re concerned about security and such. Since this project is just for testing purposes and the password is randomised, I’m not too bothered that its sitting there in plaintext.

Block Extraction

We’ll be using Peter Todd’s python-bitcoinlib library. The pprint library is also used printing to console for quick and dirty debugging purposes. Install these using Pycharm, then add to the top of your bayley.py file:

import bitcoin
import bitcoin.rpc
from pprint import pprint as pp

The next step will be to write some simple code to extract some blocks.

def main():
    # This will create a batch of commands that requests the first 100 blocks of the blockchain
    commands = [ {"method": "getblockhash", "params": [height]} for height in range(0, 100 ]
    # Connect to the RPC server, send the commands and assign to the results variable
    conn = bitcoin.rpc.RawProxy()
    results = conn._batch(commands)</p>
    # Extract the hashes out of the result
    block_hashes = [res['result'] for res in results]</p>
    # Prepare to extract specific block data
    blocks = []
    for hash in block_hashes:
        blocks.append(conn.getblock(hash))</p>
    # Call the function to make the triples to prepare for importing to CayleyDB
    block_triples = make_triples_for_block(blocks)

Block Structure

Here is an example of a single block’s data:

{'bits': '1d00ffff',
 'chainwork': '0000000000000000000000000000000000000000000000041720ccb238ec2d24',
 'confirmations': 1,
 'difficulty': Decimal('1.00000000'),
 'hash': '0000000084ee00066214772c973896dcb65946d390f64e5d14a1d38dfa2e4d90',
 'height': 445610,
 'merkleroot': 'eaf042fa845ea92aba661632bc6b8e78e8e64c2917a92f1a7da0800ed793b819',
 'nonce': 1413010373,
 'previousblockhash': '0000000087a272f48c3785de422e232c0771e2120c8fdd741a19ea98d122132b',
 'size': 315,
 'time': 1432705094,
 'tx': ['eaf042fa845ea92aba661632bc6b8e78e8e64c2917a92f1a7da0800ed793b819'],
 'version': 3}

With this in mind we can begin working on pulling the data from the blockchain and parsing the specific blocks.

Making Triples

Cayley uses the subject, predicate, object system, known as a triplestore. We need to parse the block data from the previous section into this triplestore format.

One of the limitations of the triplestore is that you can not add much metadata to each node. Array indexing and similar are a problem in this regard. In this case we will use the blockhash as the subject for all block data, the key value for all predicates, and the block data (excluding the block hash) as the object variable.

Lets create a function that does this:
At the top of my bayley.py file I will create a global variable which specifies which key value pairs for which I want to create a triplestore.

DESIRED_BLOCK_KEYS = ("height", "nextblockhash", "previousblockhash", "size", "time", "difficulty")

Next I wish to declare the function:

def make_triples_for_block(blocks):
    triples = []

We will next need to iterate through the blocks and their respective keys to start pulling the relevant data. The first thing to do is to ignore the blockhash key:

def make_triples_for_block(blocks):
    triples = []
    for block in blocks:
        for key in block:
            # Ignore self reference
            if (key == "hash"):
                continue

The transactions value is an array so its best to iterate through these separately.

def make_triples_for_block(blocks):
    triples = []
    for block in blocks:
        for key in block:
            # Ignore self reference
            if (key == "hash"):
                continue
            # Iterate through transactions
            if (key == "tx"):
                for t in block[key]:
                    triples.append({
                        "subject": block['hash'],
                        "predicate": key,
                        "object": t
                    })

And finally we can now append our block data to the triples array we declared in the beginning. Note how I casted the values to strings, this was to prevent an issue later on when you want to import into CayleyDB. Cayley is happiest when you give her JSON files that are all strings.

def make_triples_for_block(blocks):
    triples = []
    for block in blocks:
        for key in block:
            # Ignore self reference
            if (key == "hash"):
                continue
            # Iterate through transactions
            if (key == "tx"):
                for t in block[key]:
                    triples.append({
                        "subject": block['hash'],
                        "predicate": key,
                        "object": t
                    })
            # Iterate through first level block data
            if (key in DESIRED_BLOCK_KEYS):
                triples.append({
                    "subject": str(block['hash']),
                    "predicate": key,
                    "object": str(block[key])
                })
    return triples

So now we have a triples variable returned which contains all of our triples ready for importing!

Here is an example of the triples for a single block for your reference:

[{'object': '1',
  'predicate': 'height',
  'subject': '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'},
 {'object': '190',
  'predicate': 'size',
  'subject': '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'},
 {'object': 'f0315ffc38709d70ad5647e22048358dd3745f3ce3874223c80a7c92fab0c8ba',
  'predicate': 'tx',
  'subject': '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'},
 {'object': '000000006c02c8ea6e4ff69651f7fcde348fb9d557a06e6957b65552002a7820',
  'predicate': 'nextblockhash',
  'subject': '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'},
 {'object': '1.00000000',
  'predicate': 'difficulty',
  'subject': '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'},
 {'object': '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943',
  'predicate': 'previousblockhash',
  'subject': '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'},
 {'object': '1296688928',
  'predicate': 'time',
  'subject': '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'}]

Setting up Cayley

Cayley uses golang. A packaged binary is available (so you shouldn’t need to setup golang separately) from here.

This is my Cayley config file:

{
"database": "bolt",
"db_path": "./blockchain",
"read_only": false,
"replication_options": {
  "ignore_duplicate": true,
  "ignore_missing": true
}
}

I’m using bolt db over leveldb because bolt is slightly better for high reads. You can read more here.

After making the cayley.cfg file, initialise the database by running the init command like so (from the Cayley folder):

./cayley init -config cayley.cfg

This will create a blockchain file and prep the backend database for Cayley goodness. The next step will be to run the HTTP server:

./cayley http -config cayley.cfg

Now we’re ready to send all the data in!

Sending to Cayley

Cayley’s HTTP documentation will help with this section. It receives JSON triples in the form of the following:

[{
    "subject": "Subject Node",
    "predicate": "Predicate Node",
    "object": "Object node",
    "label": "Label node"  // Optional
}]   // More than one quad allowed.

We’ll need to POST this data to our Cayley server’s write API via http://localhost:64210/api/v1/write.

Now we need to make use of the excellent requests python library. Install it in Pycharm then add the following to the top of the bayley.py file. Cayley is expecting a json file so we’ll also need to install and import that.

You’ll also want to put in a global variable there for Cayley’s URL and also tell Cayley that we’re sending a JSON file.

import requests
import json
DB_WRITE_URL = "http://127.0.0.1:64210/api/v1/write"
DB_WRITE_HEADERS = {'Content-type': 'application/json'}

We’re going to create a function to send the data over to Cayley. Note how the data is converted to json in the data= argument.

def send_data(data):
    r = requests.post(DB_WRITE_URL, data=json.dumps(data), headers=DB_WRITE_HEADERS)
    pp(r)
    pp(r.text)

If the pp(r) prints out a response of 200 then we’re good! If not then we’ll need to look at what went wrong which is usually explained well in the r.text variable. This is the result I got:

<Response [200]>
'{"result": "Successfully wrote 693 quads."}'

Go back to your main function and call the send_data function:

def main():
    ...
    send_data(block_triples)
    ...

And that should do it.

Graphing the result

By now we should have 100 blocks in Cayley! Head over to http://localhost:64210 and lets start graphing!

In the query page we can test out our queries. I wrote a simple one that loops through the first 5 blocks, gets all objects that are one edge away (Out()) and gets the result:

for(var i=0; i<5; i++){
    g.V().Has("height", String(i)).Tag("source").Out().Tag("target").All();
}

Here is the result of the first block:

{
 "result": [
  {
   "id": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
   "source": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
   "target": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
  },
  {
   "id": "1296688602",
   "source": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
   "target": "1296688602"
  },
  {
   "id": "0",
   "source": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
   "target": "0"
  },
  {
   "id": "285",
   "source": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
   "target": "285"
  },
  {
   "id": "1.00000000",
   "source": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
   "target": "1.00000000"
  },
  {
   "id": "00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206",
   "source": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
   "target": "00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206"
  }
 ]
}

The query shape looks like this:

queryshape

The visualisation itself looks like these following images.

Single block:
oneblock

Five blocks:
fiveblocks

As you can see there are shared nodes, this is because the nodes have the same predicate and objects, but different subject (blockhash). This is a good example of how cayley helps in visualising relationships.

One hundred blocks:

hundredblocks

The shared nodes here are due to the common block size and block difficulty (the latter changes every 2 weeks). You can see a close up below:

zoom

Conclusion

This is just the early stage. The next step will be to parse the transactions for Bitcoin addresses and start drawing all the relationships between them. Once a strong system is in place for parsing the block chain, you might want to begin parsing the other 400,000 blocks or so, and also switch to the mainnet. Web scraping usernames for addresses and also estimating relationships based on round number transferring of value also is in the pipeline.

Using CanCan to implement an “Agree to Terms” workflow

Posted in Code, Inside TFG, Ruby on Rails

Terms & Conditions
Recently in a Rails application I was tasked with adding in a basic “terms and conditions” page.

There was nothing special about the feature, but I was really happy with my solution so I decided to write about it briefly.

Thinking about a solution

So, my initial plan was:

  1. Add a agreed_to_term_and_conditions_at datetime field to the User model. Use a datetime here so that if we change the conditions later we can check against the time.
  2. Perform checks in the app to prevent users that haven’t agreed to the terms and conditions will be redirected to the T&C workflow

Step 1 was straight forward, but step 2 got me thinking.

Initially I had considered implementing a before_filter in ApplicationController that would check whether the User had agreed to the T&Cs and redirect them to the T&Cs page if they hadn’t.

After thinking for a moment I decided that it was really a question of authorization, and as a result should be managed by an Ability file.

The reasoning I used is that I would say a user should not be able to access the site until they had agreed to the terms. That sounds suspiciously like a cannot statement in CanCanCan.

Implementing a solution with CanCanCan

Once I had decided to use CanCanCan to implement the solution, it was just a matter of getting all the parts together.

Firstly, I had my abilities split into separate files in the way I have suggested in this post. I had an Ability::Factory that would take a User (or nil) and return the appropriate ability file. It looks something like:

class Ability::Factory

  def self.build_ability_for(user)
    return Ability::Anonymous.new if user.nil?

    case user.role
    when :admin
      Ability::Admin.new(user)
    when :supervisor
      Ability::Supervisor.new(user)
    when :doctor
      Ability::Doctor.new(user)
    when :patient
      Ability::Patient.new(user)
    else
      raise(Ability::UnknownRoleError, "Unknown role passed through: #{user.role}")
    end
  end

end

My initial idea was to do some checks for each role and basically say something like:

if user.has_agreed_to_terms_and_conditions?
  # Implement abilities as per usual
else
  cannot :manage, :all
end

But that would lead to a lot of duplication in both implementation and tests. Plus, just a lot of code in general, which I despise.

Thinking further, I decided that a User who hadn’t agreed to the terms and conditions had a set of abilities of their own, independently to their role. I created a new Ability for such a condition: Ability::PendingAgreementToTermsAndConditions. The class was implemented like:

class Ability::PendingAgreementToTermsAndConditions < Ability

  def initialize(user)
    cannot :manage, :all
    can :agree_to_terms_and_conditions, User, id: user.id
  end

end

I amended my Ability::Factory so that it would return the pending ability in the right conditions:

class Ability::Factory

  def self.build_ability_for(user)
    return Ability::Anonymous.new if user.nil?

    if user.has_agreed_to_terms_and_conditions?
      ability_class_for(user.role).new(user)
    else
      Ability::PendingAgreementToTermsAndConditions.new(user)
    end
  end

private

  def ability_class_for(role)
    case role
    when :admin
      Ability::Admin
    when :supervisor
      Ability::Supervisor
    when :doctor
      Ability::Doctor
    when :patient
      Ability::Patient
    else
      raise(Ability::UnknownRoleError, "Unknown role passed through: #{user.role}")
    end
  end

end

Great, so now I had all the abilities I needed. It was time to incorporate the logic into my controller so the application would handle users who hadn’t agreed to the T&Cs.

I had to ensure two things:

  1. Users can’t access other pages in the app that aren’t the T&Cs. When they do, they will be redirected to the T&Cs page.
  2. When a user who hasn’t agreed to the T&Cs signs in, they are redirected to the T&Cs page.

Handling access to pages when terms and conditions aren’t agreed to

Regarding the first objective: Anyone who has used CanCan or CanCanCan will know that since the ability file prohibits users from accessing other pages (cannot :manage, :all), a CanCan::AccessDenied exception will be raised if those pages are hit.

That means that I just had to handle that exception, and redirect the user to the T&Cs page. The CanCanCan README explains how to catch this exception in detail, but I’ll post the code I used anyway:

class ApplicationController < ActionController::Base

  rescue_from CanCan::AccessDenied do |exception|
    if current_user.present?
      # You could also do: current_ability.can?(:agree_to_terms_and_conditions, current_user)
      # but I think the following reads better
      if current_user.has_agreed_to_terms_and_conditions?
        # Redirect as usual
      else
        # Redirect to the terms page
      end
    else
      # Do whatever for unauthed users
    end
  end

end

Moving on, let’s ensure the user isn’t sent straight to another redirect when they sign in.

Redirecting users to the terms and conditions page when they sign in

This is a problem for your authentication system. I use devise, so I was able to override the after_sign_in_path_for method in my ApplicationController as outlined in the documentation. The code looks like:

class ApplicationController < ActionController::Base

  # Override: Devise method
  def after_sign_in_path_for(user)
    # You could also do: current_ability.can?(:agree_to_terms_and_conditions, current_user)
    # but I think the following reads better
    if user.agreed_to_terms_and_conditions_at.present?
      # Redirect as usual
    else
      # Redirect to the terms page
    end
  end

end

Now the user will get one redirect, instead of being redirected to a page they can’t access.

Conclusion

So that’s my solution. About 20 extra lines of code (plus tests) and now you’ve got all the logic for implementing a terms and conditions workflow.

I really enjoyed implementing that solution. It was easy to write and has had no maintenance cost.

Search Posts

Featured Posts

Categories

Archives

View more archives