Squares [*]
A lightweight ORM backed by any hash-like storage. Hand-crafted from a solid piece of pure
aircraft-grade Ruby and drawing distilled awesomeness from atmospheric pollutants, its only
dependency is you.
Installation Blah, Blah, Blah
I swear, this part of the README just rolled right out of bundle gem squares
.
Add this line to your application's Gemfile:
gem 'squares'
And then execute:
$ bundle
Or install it yourself as:
$ gem install squares
And yeah, I did enjoy typing bundle gem squares
. It sounds like something to
eat. Now I'm hungry.
Usage
Because you are going to use it.
require 'squares'
Write Models
How come they never write back?
class Person < Squares::Base
properties :real_name, :age
end
You can also provide a default value if you switch to the property
variant:
class Person < Squares::Base
property :irish?, default: false
property :eye_color, default: lambda { |person|
person.irish? ? 'green' : 'brown'
}
end
Note that defaults which use callbacks (anything that responds to #call) are
always applied after defaults which don't use callbacks. Reverse the order
of the above two properties, and their defaults would still work.
Bootstrapping
A funny word for "setup & configure". Bootstrapping. Bootstrapping. See? Funny.
Now you can bootstrap ;)
your model to a hash-like storage object like so:
people_storage = Redis::Namespace.new(
Person.underscore_name,
:redis => $redis_connection )
Person.store = people_storage
Or if you just want to use a plain ole in-memory hash:
tom_sellecks_mustache = {}
Soup.store = tom_sellecks_mustache
Squares actually defaults the store to an empty hash, which means if you're ok
with in-memory, transient storage (e.g. when writing tests, etc.) you don't have
to do any config-- er, bootstrapping ;)
at all!
You can setup a bunch of 'em like this:
[Person, Place, SwampThing].each do |model|
model.store = LevelDB::DB.new("./tmp/#{model.underscore_name}")
end
But it gets even better: the Squares module is an Enumerable
which enumerates all
the model classes (inheritors of Squares::Base
). So you can:
Squares.map &:underscore_name
Or better yet:
Squares.each do |model|
model.store = LevelDB::DB.new './tmp/#{model.underscore_name}'
end
Onward To The Fun
Squares does not auto-generate an :id
for each new object --you'll do that
and it will be used as the "key" in the hash storage. In the following example,
we're creating a new Person
and using 'spiderman' as the key:
pete = Person.new('spiderman', real_name: 'Peter Parker', age: 17)
pete.save
Person.has_key? 'spiderman'
pete.id
When we retrieve an object, it returns an instance of that model:
wallcrawler = Person['spiderman']
wallcrawler = Person.find 'spiderman'
wallcrawler.class
wallcrawler.id
And then, of course, you can
wallcrawler.real_name
wallcrawler[:real_name]
wallcrawler.real_name = 'Petah Pahkah'
wallcrawler[:real_name] = 'Peshmerga Pete'
wallcrawler.changed?
wallcrawler.save
wallcrawler.update_properties(
real_name: 'Peter Parker'
)
Of course, for some types of storage, the model object has to be serialized and
de-serialized when it's stored and retrieved. Squares uses Marshal.dump
and
Marshal.restore
to do that. This means that custom marshalling can be added
to your models (see documentation on ruby Marshal).
Where-able Computing
You can use the ActiveRecord-esque .where
method with a block to retrieve records
for which the block returns true:
Person.where { |p| p.left_handed == true }
In this mode, .where
is essentially just like .select
...which, oh yeah!
Squares are enumerable! Yay!
Person.map(&:name)
But you can also pass a hash to .where
and it will do the expected thing:
Person.where( country: 'Latvaria' )
And if you give .where
a series of symbol arguments, it will consider them as
properties, and check the truthyness of each:
Person.where( :flying?, :secret_lair )
Square Hooks
You can hang a callable (such as a Proc, a Lambda or an instance of class Batman
which implements #call
) on any of Squares' polished, hand-crafted hooks:
class Hero
after_initialize do
tweet "In a world..."
end
end
Squares supports the following hooks:
- after_initialize
- before/after_create
- after_find (e.g. after
.find
and also .[]
) - before/after_save
- before_destroy (
#delete
does not trigger this callback)
There are two important things to remember about Squares' hooks: 1) while a hooked
callback is in progress, no other hooks will be triggered (i.e. hooks can't fire
hooks), and 2) never feed Square Hooks after midnight.
What It Doesn't Do
Much like Wolverine, Squares doesn't do relationships. You'll have to
maintain those in your code. If you have an issue with that, leave me
an issue, and I'll think about what that might mean.
Squares neither knows nor cares about the type or contents of your model
instance's properties. This has consequences.
First, anything you stash had darn well better be marshal-able, or there
will be blood on the roller-rink. Or at least errors. Yeah, I've made
sure there won't be blood (you're welcome), but watch out for errors.
If you run into problems, refer to the documentation on ruby Marshal.
Second, there is no magic-fu for stuff like generating question methods
for boolean properties. For example, it doesn't make a #left_handed?
method
out of your property :left_handed
). But hey, you know what you can
do? Behold:
class Person
property :awesome?, default: true
end
Ok, don't interrupt me, I'm selling here...
you = Person.new
you.awesome?
Of course, Squares doesn't mind how you use #awesome?
and the corresponding #awesome=
methods:
you.awesome = 'yak hair'
you.awesome?
or
you.awesome = nil
you.awesome?
But hey, who cares, as long as yak hair is truthy?
What's New in 0.3.0
- property defaults can use a callback
- implemented
#[]
on instances (to access properties) #update_properties
(i.e. #update_attributes
)- square hooks
#changed?
.where
accepts a hash, and/or series of properties
You can read in more detail on the 0.3.0 milestone
Contributing
- Fork it ( https://github.com/joelhelbling/squares/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request