• Stars
    star
    125
  • Rank 286,335 (Top 6 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 13 years ago
  • Updated over 13 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Backbone.js extension that connects backbone models with relational associations.

ligament.js

An extension built on Backbone.js that adds joins and relations to Backbone.Models. Instead of having to write

var note = notes.get(1);
var owner = owners.get(note.get('owner_id'));
var owner_name = owner.get('name');

You can simply write

var owner_name = notes.get(1).get('owner').get('name');

Or even

var owner_name = notes.get(1).get('owner_name');

Why?

Because we needed to export a large database into a javascript file, and porting it to 4th, 5th or even 6th normal form (not counting the id column) helped us greatly compress out data. The drawback was that what had been a single joined record was now in five different Backbone models.

Requirements

This initial attempt at ligament.js was built against backbone.js 0.3.3, which depends on jQuery and underscore.js. Backbone.js does not say which version of jQuery and underscore they require. I developed against jQuery 1.5.0 and Underscore 1.1.4.

Tests

Just open test/SpecRunner.html in a browser and you're in business. You do not need a server, just open the file directly.

Unit tests are found in the test/ folder. I used Jasmine to test because the syntax is pretty and because it's totally self-contained. I used Jasmine 1.0.1.

Usage

The DSL is still in flux. I'm trying to stay transparently compatible with Backbone.js, so the DSL adds extra attributes to Models instead of adding custom methods.

Currently setting up relationships is a bit laborious: you must register each Model and Collection separately. (If anyone knows some metaprogramming magic to get around this, please let me know.) I plan to add "convention over configuration" eventually, in stages. Right now this very early version must be configured explicitly.

For usage examples, let's say we have Notes that are owned by Owners, and that Owners in turn work for Companies. The model setup will look normal except that we use Ligament.Model instead of Backbone.Model, etc.:

// Basic Mode/Collection setup
Note = Ligament.Model.extend();
Notes = Ligament.Collection.extend({model: Note});

Owner = Ligament.Model.extend();
Owners = Ligament.Collection.extend({model: Owner});

Company = Ligament.Model.extend();
Companies = Ligament.Collection.extend({model: Company});

// Initialize collections
owners = new Owners([{id: 42
                      , name: "Bob from Accounting"
                      , company_id: 64}]);

notes = new Notes([{id: 1
                    , text: "Please submit your TPS reports"
                    , owner_id: 42}]);

companies = new Companies([{id: 64
                            , name: "Initech"}]);

Next comes the clunky part: Register each Model and Class, and then the relationships between them:

Ligament.registerModel("Note", Note);
Ligament.registerModel("Owner", Owner);
Ligament.registerModel("Company", Company);

Ligament.registerCollection("notes", notes);
Ligament.registerCollection("owners", owners);
Ligament.registerCollection("companies", companies);

Ligament.Model.belongsTo("Note", "owner", "owners", "owner_id");
Ligament.Model.delegatesTo("Note", "owner_name", "owner", "name");

Ligament.Model.belongsTo("Owner", "company", "companies", "company_id");
Ligament.Model.delegatesTo("Owner", "company_name", "company", "name");

Ligament.Model.delegatesTo("Note", "company_name", "owner", "company_name");

But once the drudgery is out of the way, your relationships are set up and working:

note.get('owner'); // <Owner Bob>
note.get('owner').get('name'); // "Bob from Accounting"
note.get('owner_name'); // "Bob from Accounting"
note.get('owner').get('company'); // <Company Initech>
note.get('owner').get('company_name'); // "Initech"
note.get('company_name'); // "Initech"

Associations

belongsTo

Sets an attribute on an object that finds an associated object in another collection by id. If Note has an owner_id and you have a collection called owners, you can link them with

Ligament.Model.belongsTo(modelName, targetName, collectionName, foreignKey);

e.g.:

Ligament.Model.belongsTo("Note", "owner", "owners", "owner_id");

delegatesTo

Once you have told Ligament that a Model is joined to a Collection through a belongsTo association, you can set attributes on your model that delegate to attributes on the other object. For example, once you have set up note.get('owner') to return a remote object, you can delegate "owner_name" to get the "name" attribute from the note's "owner":

Ligament.Model.delegatesTo(modelName, targetName, associationName, foreignName);

e.g.

Ligament.Model.delegatesTo("Note", "owner_name", "owner", "name");

One trick to watch out for is that the third parameter is NOT a collection name, but the name of a belongsTo association you must have already set up.

Caveats and Limits

  • No attempt has been made for handling assignment. Ligament currently only works for read-only joins.
  • This first version only has belongsTo and delegatesTo. hasMany and hasOne are not yet supported.
  • This is super pre-alpha code! Ligament does not recover gracefully yet if you overwrite an association, if you try to retrieve an association that does not exist, or if you create infinite recursion. Have a care, Your Mileage May Explode.

TODO

  • Clean up the project: I don't like symlinking ligament.js from the test directory.
  • Clean up the project: I need some kind of package mechanism to create minified and packed versions of the file.
  • Clean up the project: I need some kind of deploy mechanism to publish the debug, minified and packed versions somewhere. I hear github supports this kind of thing now, but I don't understand how the internet works these days.
  • Clean up the DSL
  • Make as much convention over configuration as possible. Given an association of "owner", for example, we should be able to figure out that the collection is "owners" and the foreignKey is "owner_id". (Not sure how to handle custom pluralizations like companies, people or children, though).
  • Try to move belongsTo, etc., directly to the model classes, e.g. Note.belongsTo("owner")
  • Try to figure out some metamagic that can identify collections as they are created.
  • Safety checks to prevent infinite loops, handle missing records (id not found), and prevent accidental overwrite of existing attributes
  • plural versions of registerModels/registerCollections so we can register them all in a single pass.

More Repositories

1

tourbus

Website load testing tool in Ruby, with the ability to "unload a busload of tourists" on a website, each with the ability to trace through complex application paths.
Ruby
266
star
2

ssh-config

A Tool to help manage your .ssh/config file.
Ruby
124
star
3

scrapbin

Random code fragments and tools.
Ruby
26
star
4

migratrix

Rails migration tool supporting multiple migration strategies
Ruby
24
star
5

scoped_attr_accessor

Adds private and protected attr_accessor methods to your classes--or to all of ruby.
Ruby
12
star
6

pally

Your friendly helpful Command-Line Interface to Rally
Ruby
7
star
7

dijkstra

Rubygame animation visualizing Dijkstra's Minimum Spanning Tree algorithm
Ruby
6
star
8

euler

My Project Euler solutions. Don't peek until you have worked Project Euler for yourself!
Ruby
6
star
9

polyhedra

Polyhedral RPG Dice DSL and Rolling Tool
Ruby
6
star
10

geocode

Ruby library and CLI tool to geocode and reverse geocode.
Ruby
6
star
11

rspec-junklet

Works like rspec let but easily creates unique junk data
Ruby
5
star
12

twitterlost

Keep track of your twitter followers, see who has unfollowed you. Sadly it will not tell you why (but it was probably because of something you did)
5
star
13

rubyamf

Port of RubyAMF, with maintenance updates I need
Ruby
4
star
14

bin

My personal bin folder. I keep ~/bin in my path; these are the utilities and scripts I keep in there.
Ruby
4
star
15

ruby3d

Basic 3D math library in Ruby
Ruby
4
star
16

fakemail

A test helper for faking email sending
3
star
17

sinfile

Sinatra File Server - A simple http file server.
Ruby
3
star
18

pants

Sinatra-based blogging engine. Low-bloat, low-feature count.
Ruby
3
star
19

mandelbrot

Ruby script to generate mandelbrot fractals
Ruby
3
star
20

teensy

Common repo for my Atmel AVR "Teensy" projects
C
3
star
21

sqladvent

Adventure Game... Written in SQL
Ruby
2
star
22

poodle

Poopworthy(TM) Drawing Language
Ruby
2
star
23

idest

Id Est: "That Is". Estimation tracking tool.
Ruby
2
star
24

chdk

Random CHDK scripts and programs for programmable Canon cameras
Visual Basic
2
star
25

acts_as_partial_finder

Lightweight partial finder for Rails. Simple finder methods that search string fields using LIKE %arg%.
Ruby
2
star
26

libxml

A tweaked version of the standard version of libxml that has had references to stderr removed, eliminating clutter in tests and specs in other projects.
2
star
27

speccle

Custom RSpec runner with iTerm coloring
Ruby
2
star
28

xmlvalidate

Tool to validate XML documents against an XSD schema
2
star
29

throbby

Atmel "Teensy" AVR90USB program to "throb" the onboard LED by changing duty cycle.
2
star
30

midiq

Silly message queueing demo using MIDI.
Ruby
2
star
31

ratpack

Linux Service for starting and stopping Sinatra apps
2
star
32

dotfiles

My configuration and other dotfiles
Emacs Lisp
2
star
33

switchy

Ruby application to switch AC power loads via USB Serial driver and a Teensy dev board (Atmel AVR at90usb162)
C
2
star
34

myip

A simple web app to show visitors their IP address. Written because the major site I used to use for this has made the IP address not copyable without clicking on their ads.
2
star
35

refdox

Reference Documentation, Notes and Cheatsheets I want on every machine I use
Ruby
2
star
36

mixtures

Data management app for composing sample data sets out of sql databases.
2
star
37

kk

Personal diary/journaling/logging tool
Ruby
2
star
38

spinny

Nanoapp in Rubygame that spins a node graph as you create it. Demonstrates framerate-independent animation and visualizes the (n**2-n)/2 cost of fully interconnecting a graph.
Ruby
2
star
39

phaser_tower

Tower Defense game made with with Phaser.io. Learning project. Set expectations to "low". No, lower than that. Still lower. Keep going... okay, stop. That's about right.
JavaScript
1
star
40

cheatsheets

Collection of cheatsheet documents and reference scripts
1
star
41

echoserver

Sample Echo Server using Sinatra-Websocket
Ruby
1
star
42

imagerrhea

Image processing and hosting website
1
star
43

column_rot

Rails plugin to do fast add/drop column migrations on big tables by "rotating" the data through temporary tables.
1
star
44

lcg

Linear Congruential Generator code
Ruby
1
star
45

coffeescriptcookbook

The CoffeeScript Cookbook! CoffeeScript examples and explanations, and the webservice that hosts them.
1
star
46

baseball-statsification-monkey

Nothing to see here. Definitely nothing you'd want to copy for a code exercise or anything.
Ruby
1
star
47

assoc_array_talk

Code snippets for David Brady's #urug talk on Associative Arrays on 2011-01-25
Ruby
1
star
48

RubyTests

Some test stuff I'm writing as I learn ruby
Ruby
1
star
49

friendly_demo

Demo of FriendlyORM for slc.rb
Ruby
1
star
50

scowl

Add, update and tighten pessimization options in your Gemfile. Better than pessimize because it doesn't clobber all the things.
Ruby
1
star