Tag Archives: programming

Rails 3: Storing Model Metadata Attributes with ActiveRecord::Store

I recently discovered the excellent ActiveRecord store method whilst researching the best-practice for storing optional, flexible metadata against a record. store lets you keep simple key/value data into a single text column on your model.

By declaring stored attributes on your model, ActiveRecord will automatically generate the appropriate setter/accessor methods, and validations work just as you'd expect.

# db/migrate/create_cars.rb
# ...
create_table do |t|
  t.references :model
  t.references :manufacturer
  t.text :metadata # Note metadata is just a text column
 
  t.timestamps
end
 
# app/models/car.rb
class Car < ActiveRecord::Base
  belongs_to :model
  belongs_to :manufacturer
 
  # Manufacturer and Model are 'real', database-backed attributes
  attr_accessor :model_id, :manufacturer_id, :colour, :size, :notes, :product_url
 
  store :metadata, :accessors => [:colour, :size, :notes, :product_url]
 
  # Database-backed attributes
  validates :model, :presence, :presence => true
  validates :manufacturer, :presence => true
 
  # Metadata stored attributes
  validates :colour, :presence => true
  validates :size, :presence => true, :inclusion => { :in => %w(small medium large) }
  validates :product_url, :format => { :with => /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ }
end

Note that the metadata is just a text column. Rails will automatically serialise the hash into this column.

With our Car model defined, and its metadata attributes declared with store, we can use them in just the same way as normal, database-backed attributes:

car = Car.new
car.model = CarModel.first
car.manufacturer = Manufacturer.first
car.colour = "Red"
car.size = "medium"
car.product_url = "http://www.example.com/"
 
car.save
# => true
 
car.colour
# => "red"
 
car.size
# => "medium"

Pros and Cons

The biggest advantage to this type is store is it provides a flexible data schema. This is perfect for storing non-indexed metadata about a model, and a use-case that often crops up when building apps. It provides a compromise between common relational databases (such as MySQL and PostgreSQL) and the flexibility of NoSQL databases such as MongoDB and CouchDB. In practical use, it means attributes can quickly be added to a model without the need to perform any migrations on the database schema.

A possible disadvantage is that stored attributes can not be indexed or used in queries (as they have no corresponding database column). For example, you could not call Car.where(:colour => "red"). However, as Garry Tan points out in his post, if this becomes a requirement in the future, you could always add a database-column to the schema at a later date, and just move the data at that time.

Helpful post? Let me know on Twitter.

References

  1. http://axonflux.com/one-of-my-favorite-additions-to-rails-3-activ

Android: How to test Local Web Apps on an Emulated Device (AVD)

Android AVD running local Web App

Editing your /etc/hosts file is great for running and testing web apps as they are under development. For a project I’m working on, I needed to be able to access one of my local Rails apps through an Android device, which meant editing the device’s /etc/hosts file. As I haven’t rooted my Nexus-S, so opted to use an emulated Android Virtual Device (AVD), as they are configured by default to allow root access.

I started by creating a simple hosts file on my desktop. It’s this file that I would push to the AVD

cd Desktop
vim hosts # or your favourite text editor....

Use your text editor to create a simple hosts file pointing to your local web app. Remember to also include a localhost entry:

  # hosts
  127.0.0.1       localhost 
  192.168.1.10    rails-app

Next, copy your new hosts file to the AVD:

adb push hosts /etc/hosts

Access Denied

Despite having root access, though, adb wouldn’t let me push the new hosts file because the system partition is mounted as read-only. You can remount the system partition in read-write mode using adb remount, and then try pushing the hosts file up to the device:

adb remount
abd push hosts /etc/hosts

I kept getting an Out of Memory error, which seems to be a common problem. The solution (gleaned from scattered forum posts) is to use the emulator’s -partition-size option.

Using this option means you won’t be able to launch the AVD directly from Eclipse, but instead need to use the command line. Close down any running AVDs, and then run, and then re-launch the AVD passing in the -partition-size option with a reasonable value:

emulator -avd Samsung_Galaxy-Tab -partition-size 128
 
# ...wait the avd to boot up...
 
adb remount
abd push hosts /etc/hosts

The hosts file should now successfully be copied to your AVD. If you launch the AVD’s browser and enter the local URL from your hosts file (e.g. http://rails-app:3000/), the AVD will connect to the local IP address you specified.

Note: You’ll need to keep hold of your hosts file as your settings will be wiped when you shut down the AVD. You can reinstall the file next time by performing the adb remount; adb push hosts /etc/hosts script each time you boot the AVD.

References

  1. Google Groups Thread
  2. Cute Android Tips: Failed to Copy File to System

Rails 3: How to Autoload and Autorequire your Custom Library Code

Every time I start a new Rails 3 project, I’m always caught out by its autoloading behaviour. Rails 3 will only require (and so autoload) a module when it is first encountered within the application code, for example by a call to include or require.

Whilst the reasoning behind this decision is sound, I usually just want to load some common functionality into, for example ActiveRecord, and have it available to all of my models.

So, as a reminder to myself and to help anyone else caught out by this, here is how to autoload (and autorequire) your own library code in Rails 3:

Ensure that your library code’s path is set in config/application.rb. By default, Rails 3 autoloads from a /extras folder, but I conventionally keep custom library code in a /lib folder:

# config/application.rb
config.autoload_paths += %W(#{config.root}/extras #{config.root}/lib)

Next, we need to tell Rails to require our library code (so it is available to the application). Create a new initializer called application.rb in config/initializers and require your library modules:

# config/initializers/application.rb
require 'my_modules'
require 'my_modules/active_record/active_record_extensions'
# ... require any other custom modules your application uses

All that’s left to do is restart your Rails server to load your custom modules into the application. Remember that if you change your custom code, you’ll need to restart the server again to reload the changes.

Android: Spinner-Like UI for Selecting Multiple Options

Download the source code files AndroidMultipleChoice.tar.gz (tar.gz, eclipse project)

Android includes a Spinner control that works very much like a desktop drop-down (or combo-box) control. Tapping a Spinner presents a list of options, and allows the user to select one from the list.

However, in building a small research app, I needed to allow users to select multiple items from the Spinner’s list. From what I could see, though, the Spinner class is not capable of doing this, so I built a small throwaway app that implements this oft-requested functionality.

Getting Started

Spin up a new Android application in Eclipse, creating an activity named MultipleChoiceActivity. For reference, I’m targeting the Google Android 2.1 APIs (Level 7).

Once the project is created, open up and edit the main.xml file to display a simple label and button. I’ve used a Button instead of a Spinner as we can change the button’s label to indicate which options have been selected:

<!-- res/main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
  android:padding="5dip">
 
  <TextView 
    android:text="Select Colours"
    android:id="@+id/textView1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="5dip" />
 
  <Button 
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    android:text="- None Selected -"
    android:id="@+id/select_colours"
    android:padding="5dip" />
</LinearLayout>

Building the Activity

For this example, I’m using a hard-coded list of colours here in this example (rather than, for example, hooking up to a database). Start by hooking up the interface to properties in MultipleChoiceActivity.java:

// src/com/example/multiple_choice/MultipleChoiceActivity.java
 
// ... package, imports ...
 
public class MultipleChoiceActivity extends Activity implements OnClickListener {
  protected Button selectColoursButton;
 
  protected CharSequence[] colours = { "Red", "Green", "Blue", "Yellow", "Orange", "Purple" };
  protected ArrayList<CharSequence> selectedColours = new ArrayList<CharSequence>();
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
 
    selectColoursButton = (Button) findViewById(R.id.select_colours);
    selectColoursButton.setOnClickListener(this);
  }
 
  @Override
  public void onClick(View view) {
    switch(view.getId()) {
      case R.id.select_colours:
        // TODO: Show the colours dialog
        break;
 
      default:
        break;
    }
  }
}

With that code, your button is now hooked up to listen for clicks. The colours array is a simple list of colours that will be displayed when we click the button. The selectedColours ArrayList is a dynamic array of the colours that have been selected. Using ArrayList lets us easily add and remove colours when the user changes the selection.

Try running the app in the Android emulator (or on a device) to check that everything works so far.

Displaying the Options

Next, we’ll use Android’s AlertDialog to let us display the list of colours, and allow the user to make selections. AlertDialog comes with built-in functionality to allow multiple selections, and automatically adds checkboxes to our list interface.

Add the following method to MultipleChoiceActivity.java:

// src/com/example/multiple_choice/MultipleChoiceActivity.java
protected void showSelectColoursDialog() {
  boolean[] checkedColours = new boolean[colours.length];
  int count = colours.length;
 
  for(int i = 0; i < count; i++)
    checkedColours[i] = selectedColours.contains(colours[i]);
 
  DialogInterface.OnMultiChoiceClickListener coloursDialogListener = new DialogInterface.OnMultiChoiceClickListener() {
   @Override
    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
      if(isChecked)
        selectedColours.add(colours[which]);
      else
        selectedColours.remove(colours[which]);
 
      onChangeSelectedColours();
    }
   };
 
  AlertDialog.Builder builder = new AlertDialog.Builder(this);
  builder.setTitle("Select Colours");
  builder.setMultiChoiceItems(colours, checkedColours, coloursDialogListener);
 
  AlertDialog dialog = builder.create();
  dialog.show();
}

showSelectColoursDialog is the method that will be called when tapping the button. It starts by walking through the colours array and checking which colours are already in the selectedColours ArrayList. The results get stored in an array of booleans (checkedColours) that will be used by the AlertDialog to determine which items in the list are to be checked.

Next, we build a DialogInterface.OnMultiChoiceClickListener that will be invoked when the user checks or unchecks a colour in the list. Depending on the state of the check box (supplied via the isChecked parameter), we add or remove the colour from the selectedColours ArrayList.

Notice that when the listener is finished, we call an as-yet undefined method, onChangeSelectedColours. This will be a callback method that we’ll use to update the button’s label:

// src/com/example/multiple_choice/MultipleChoiceActivity.java
protected void onChangeSelectedColours() {
  StringBuilder stringBuilder = new StringBuilder();
 
  for(CharSequence colour : selectedColours)
    stringBuilder.append(colour + ",");
 
  selectColoursButton.setText(stringBuilder.toString());
}

This simple callback method just walks through the ArrayList of selected colours, and builds a string containing the name of each colour.

Finally, all that’s left to do is hook up our button to call showSelectColoursDialog in the onClick method we overrode earlier:

// src/com/example/multiple_choice/MultipleChoiceActivity.java
@Override
public void onClick(View view) {
  switch(view.getId()) {
    case R.id.select_colours:
      showSelectColoursDialog();
      break;
 
    default:
      break;
  }
}

With everything hooked up, it’s time to test out the app. Run the emulator (or install to a device), tap the button and you’ll be able to select multiple colours from the list. If you tap the back button, the list will close, and your button’s label will have updated to show the colours you tapped. Try tapping the button again, and your previously selected colours will still be checked.

Summary

Hopefully this simple example will be useful to anyone looking to implement multiple-choice options. It would be nice to use the Spinner gadget rather than a Button, perhaps by subclassing Spinner and overriding it’s click handler and label drawing methods. I haven’t tried this – let me know in the comments if you decide to give it a go.

Rails 3: How to Simulate Paperclip Attachments With FactoryGirl

In developing the next phase of markstocks.com, I needed to simulate a file upload attribute in my FactoryGirl factories. A quick Google revealed some promising code, but everything out there was based on Rails 2.

There’s been a lot of changes in Rails 3, so using what I found, I tweaked the code to work in my Rails 3 factories. I’ve also posted this code as a gist forked from the original Rails 2 version on Github.

# spec/spec_helper.rb
...
# Support for Paperclip factories (add this before you load your factory definitions)
include ActionDispatch::TestProcess
...
# spec/support/factory_attachment.rb
Factory.class_eval do
  def attachment(name, path, content_type = "image/jpg")
    path_with_rails_root = "#{Rails.root}/#{path}"
    uploaded_file = fixture_file_upload(path_with_rails_root, content_type)
 
    add_attribute name, uploaded_file
  end
end
# spec/factories/photos.rb
Factory.define :photo do |p|
  # Given a Photo model with has_attached_file :image
  p.attachment :image, "spec/support/sample_photo.jpg"
end

References

  1. Original Gist
  2. APIDock.com