As I've mentioned
before, I advocate using a Model-View-Controller pattern for certain types of JavaScript-heavy web-app clients. In spite of recent licensing issues, I still think ExtJS is among the better libraries supporting MVC. For example, (if you don't know what namespace/using are, please read
this)
namespace('dk.okooko.model');
using(dk.okooko.model).run(function(m){
m.OrderItem = Ext.data.Record.create([
{"type": "int", "name": "id"},
{"type": "int", "name": "product_id"},
{"type": "int", "name": "quantity"},
{"type": "int", "name": "order_id"},
{"type": "date", "format": "d/m/Y-H:i", "name": "created_at"},
{"type": "date", "format": "d/m/Y-H:i", "name": "updated_at"}
]);
});
The 'Record.create' function gives a way of succinctly creating constructor functions for model objects which are supported throughout the Ext functions; you can read more about it
here.
If you are using Ruby on Rails at server-side (something which I have been doing quite a bit recently), then you realize that you are manually duplicating all the rails model classes in JavaScript. For example, here is the corresponding migration:
class CreateOrderItems < ActiveRecord::Migration
def self.up
create_table :order_items do |t|
t.integer :product_id
t.integer :quantity
t.integer :order_id
t.timestamps
end
end
def self.down
drop_table :order_items
end
end
I was annoyed by this lack of dryness: whenever I changed the migration, I'd have to manually change the corresponding JS model. So I slapped together a really simple code generator which can create the model files from the rails models. It adds a method acts_as_jsmodel which is intended to be called by a rails model class, e.g.,
class OrderItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
has_one :order_item_status
acts_as_jsmodel
end
So you can annotate model classes that you'd like to generate js models for. The acts_as_jsmodel causes a file to be written to public/javascripts/dk/okooko/model (generally, it depends on a constant APP_NAMESPACE).
Here is the code for the generator:
module Trifork
module JSModel
JS_MODEL_DIR = "public/javascripts/#{APP_NAMESPACE}/model".gsub!('.','/')
MODEL_PACKAGE = APP_NAMESPACE + '.model'
def acts_as_jsmodel
export_record "#{JS_MODEL_DIR}/#{self}.js"
rescue
nil
end
def export_record(fn)
File.open(fn,'w') do |f|
s =<<END
namespace('#{MODEL_PACKAGE}');
using(#{MODEL_PACKAGE}).run(function(m){
m.#{self} = Ext.data.Record.create([
#{self.to_record}
]);
});
END
f.write s
end
end
def to_record(spec = {},datefm=nil)
spec[:exclude] ||= []
keys = spec[:only] ? [*spec[:only]] :
(columns.map &:name).reject {|a| spec[:exclude].include?a}
props = columns_hash
keys.map do |k|
p = props[k.to_s]
if p.nil?
raise "Property #{p} is not a column of #{self}"
end
{:name => p.name}.merge!(Trifork::JSModel::to_ext_type(p.type,datefm)).to_json
end.join(",\n ")
end
def self.to_ext_type(t,datefm)
type = case t
when :integer then 'int'
when :string, :text then 'string'
when :boolean then 'bool'
when :datetime then 'date'
else
'auto'
end
res = {:type => type}
res.merge!(:format => (datefm || 'd/m/Y-H:i')) if t==:datetime
res
end
end
end
You can download the files from this entry
here.