I like Rails, I really do, but you like me may need to migrate a data-model that doesn't use locally generated integers as the primary keys (PKs) of entities.
Rails, by default, seeds model (entity) data with unique identifiers it uses as PKs from database specific mechanisms (say serial columns in PostgreSQL).
Personally, I think locally generated integers used as identifiers for "things", are most poor for two main reasons:
- They don't scale. Your local postgreSQL instance may just be part of a wider data architecture. If you use locally generated PKs as Rails will get you to, you'll clash with other locally (but possibly the same value) PKs from other local DBs when synchronization with the "master" takes place. If you don't know what I'm talking about then go look at scaling databases, its not just a postgreSQL issue. If you really really have/want/need to use an identifier, then there is a good article of using Universal Unique Identifiers (UUID) for IDs at GUID-as-Primary-in-Rails although the article doesn't follow through on how in precise detail, so we'll cover that in a later blog.
- They don't mean anything. Say for example you have an entity type HillType, whose PK should really be a unique and meaningful name. If you really wanted to find out about the details of a HillType of name gnarly you'd want to enter a RESTful-like URL of http://localhost:3000/hilltypes/gnarly. You really wouldn't want to know the mapping between "gnarly" and some internal ID that Rails generated for you via a DB sequence. Yes, I know there are ways of RESTfulizing the internal ID to some other attribute on the model/table, but avoid the complexity in the first place, and under-populate your database with data it actually needs to be readable by people not frameworks.
So .......... After those contentious points, how do we do it?
Three steps to RAILS CRUD with meaningful PKS
1) Unfortunately, I've not found a way round of avoiding an integer PK in Rails, unless either you specify your migration without a PK, or you avoid the Rails meta-DDL completely and go native in your migration as in here:-
class CreateHillTypes < ActiveRecord::Migration def self.up # ------------------------------- # This is postgreSQL specific DDL # ------------------------------- execute <<-EOF create table public.hill_types ( typename varchar(255) not null unique, description varchar(2000), primary key (typename) ); EOF end def self.down drop_table "hill_types" end end
2) We also need to ensure our Rails "knows" we've provided a non-standard PK, so we need to amend our model:-
class HillType < ActiveRecord::Base set_primary_key "typename" end
3) Next we need to amend the templated controller that script/generate scaffold HillType generated for us to avoid attempting to mass-assign what is now a protected attribute (typename) on our model. So, on the create method Rails makes for you by default in your model controller, you'll need something like:
def create @hill_type = HillType.new got_details = params[:hill_type] @hill_type.typename = got_details["typename"] @hill_type.description = got_details["description"] respond_to do |format| if @hill_type.save flash[:notice] = 'HillType was successfully created.' format.html { redirect_to(@hill_type) } format.xml { render :xml => @hill_type, :status => :created, :location => @hill_type } else format.html { render :action => "new" } format.xml { render :xml => @hill_type.errors, :status => :unprocessable_entity } end end end
That is it. Go gambol in the fields of meaningful URLs, and leave out meaningless framework convenience identifiers from your models, they are embarrassing.
No comments:
Post a Comment