Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit 7b080ebd41b52c5e2e9ce4e16e0d140661b797a8 @fperrad committed Jul 9, 2010
Showing with 563 additions and 0 deletions.
  1. +11 −0 .gitignore
  2. +4 −0 CHANGES
  3. +30 −0 COPYRIGHT
  4. +105 −0 Makefile
  5. +27 −0 README
  6. +32 −0 rockspec.in
  7. +198 −0 src/Coat/Persistent.lua
  8. +51 −0 src/Coat/Persistent/Meta.lua
  9. +21 −0 test/000-require.t
  10. +84 −0 test/001-sqlite_binding.t
11 .gitignore
@@ -0,0 +1,11 @@
+
+MANIFEST
+*.tar
+*.tar.gz
+*.patch
+*.bak
+
+*.db
+*.png
+
+luacov.*
4 CHANGES
@@ -0,0 +1,4 @@
+Revision history for lua-CoatPersistent
+
+0.1.0
+ First release
30 COPYRIGHT
@@ -0,0 +1,30 @@
+lua-CoatPersistent License
+--------------------------
+
+lua-CoatPersistent is licensed under the terms of the MIT/X11 license reproduced below.
+
+===============================================================================
+
+Copyright (C) 2010 Francois Perrad.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+===============================================================================
+
+(end of COPYRIGHT)
105 Makefile
@@ -0,0 +1,105 @@
+
+LUA := lua
+VERSION := $(shell cd src && $(LUA) -e "require [[Coat.Persistent]]; print(Coat.Persistent._VERSION)")
+TARBALL := lua-coatpersistent-$(VERSION).tar.gz
+ifndef REV
+ REV := 1
+endif
+
+ifndef DESTDIR
+ DESTDIR := /usr/local
+endif
+LIBDIR := $(DESTDIR)/share/lua/5.1
+
+install:
+ mkdir -p $(LIBDIR)/Coat/Persistent
+ cp src/Coat/Persistent.lua $(LIBDIR)/Coat
+ cp src/Coat/Persistent/Meta.lua $(LIBDIR)/Coat/Persistent
+
+uninstall:
+ rm -f $(LIBDIR)/Coat/Persistent.lua
+ rm -f $(LIBDIR)/Coat/Persistent/Meta.lua
+
+manifest_pl := \
+use strict; \
+use warnings; \
+my @files = qw{MANIFEST}; \
+while (<>) { \
+ chomp; \
+ next if m{^\.}; \
+ next if m{^rockspec/}; \
+ push @files, $$_; \
+} \
+print join qq{\n}, sort @files;
+
+rockspec_pl := \
+use strict; \
+use warnings; \
+use Digest::MD5; \
+open my $$FH, q{<}, q{$(TARBALL)} \
+ or die qq{Cannot open $(TARBALL) ($$!)}; \
+binmode $$FH; \
+my %config = ( \
+ version => q{$(VERSION)}, \
+ rev => q{$(REV)}, \
+ md5 => Digest::MD5->new->addfile($$FH)->hexdigest(), \
+); \
+close $$FH; \
+while (<>) { \
+ s{@(\w+)@}{$$config{$$1}}g; \
+ print; \
+}
+
+version:
+ @echo $(VERSION)
+
+CHANGES:
+ perl -i.bak -pe "s{^$(VERSION).*}{q{$(VERSION) }.localtime()}e" CHANGES
+
+tag:
+ git tag -a -m 'tag release $(VERSION)' $(VERSION)
+
+MANIFEST:
+ git ls-files | perl -e '$(manifest_pl)' > MANIFEST
+
+$(TARBALL): MANIFEST
+ [ -d lua-CoatPersistent-$(VERSION) ] || ln -s . lua-CoatPersistent-$(VERSION)
+ perl -ne 'print qq{lua-CoatPersistent-$(VERSION)/$$_};' MANIFEST | \
+ tar -zc -T - -f $(TARBALL)
+ rm lua-CoatPersistent-$(VERSION)
+
+dist: $(TARBALL)
+
+rockspec: $(TARBALL)
+ perl -e '$(rockspec_pl)' rockspec.in > rockspec/lua-coatpersistent-$(VERSION)-$(REV).rockspec
+
+install-rock: clean dist rockspec
+ perl -pe 's{http://cloud.github.com/downloads/fperrad/lua-CoatPersistent/}{};' \
+ rockspec/lua-coatpersistent-$(VERSION)-$(REV).rockspec > lua-coatpersistent-$(VERSION)-$(REV).rockspec
+ luarocks install lua-coatpersistent-$(VERSION)-$(REV).rockspec
+
+ifdef LUA_PATH
+ export LUA_PATH:=$(LUA_PATH);../test/?.lua
+else
+ export LUA_PATH=;;../test/?.lua
+endif
+#export GEN_PNG=1
+
+check: test
+
+test:
+ cd src && prove --exec=$(LUA) ../test/*.t
+
+coverage:
+ rm -f src/luacov.stats.out src/luacov.report.out
+ cd src && prove --exec="$(LUA) -lluacov" ../test/*.t
+ cd src && luacov
+
+html:
+ xmllint --noout --valid doc/*.html
+
+clean:
+ rm -f MANIFEST *.bak *.db src/*.db src/*.png test/*.png *.rockspec
+
+.PHONY: test rockspec CHANGES
+
27 README
@@ -0,0 +1,27 @@
+
+lua-CoatPersistent : an ORM for lua-Coat
+========================================
+
+Introduction
+------------
+
+lua-CoatPersistent is a Lua 5.1 port of Coat::Persistent, a Perl module,
+which is inspired by Ruby Active Record.
+
+lua-CoatPersistent is an Object-Relational Mapping for lua-Coat.
+It is built over the modules LuaSQL and Dado.
+It could support all database engine which has a driver in LuaSQL.
+
+Links
+-----
+
+The homepage is at http://fperrad.github.com/lua-CoatPersistent/,
+and the sources are hosted at http://github.com/fperrad/lua-CoatPersistent/.
+
+Copyright and License
+---------------------
+
+Copyright (c) 2010 Francois Perrad
+
+This library is licensed under the terms of the MIT/X11 license, like Lua itself.
+
32 rockspec.in
@@ -0,0 +1,32 @@
+package = 'lua-CoatPersistent'
+version = '@version@-@rev@'
+source = {
+ url = 'http://cloud.github.com/downloads/fperrad/lua-CoatPersistent/lua-coatpersistent-@version@.tar.gz',
+ md5 = '@md5@',
+ dir = 'lua-CoatPersistent-@version@',
+}
+description = {
+ summary = "an ORM for lua-Coat",
+ detailed = [[
+ lua-CoatPersistent is an Object-Relational Mapping for lua-Coat.
+ It is built over the modules LuaSQL and Dado.
+ It could support all database engine which has a driver in LuaSQL.
+ ]],
+ homepage = 'http://fperrad.github.com/lua-CoatPersistent',
+ maintainer = 'Francois Perrad',
+ license = 'MIT/X11'
+}
+dependencies = {
+ 'lua >= 5.1',
+ 'dado >= 1.2.0',
+ 'lua-coat >= 0.8.4',
+ 'lua-testmore >= 0.2.2',
+}
+build = {
+ type = 'builtin',
+ modules = {
+ ['Coat.Persistent'] = 'src/Coat/Persistent.lua',
+ ['Coat.Persistent.Meta'] = 'src/Coat/Persistent/Meta.lua',
+ },
+ copy_directories = { 'doc', 'test' },
+}
198 src/Coat/Persistent.lua
@@ -0,0 +1,198 @@
+
+--
+-- lua-CoatPersistent : <http://fperrad.github.com/lua-CoatPersistent/>
+--
+
+local rawget = rawget
+local rawset = rawset
+local require = require
+local setmetatable = setmetatable
+local tostring = tostring
+local type = type
+local _G = _G
+local table = require 'table'
+local Coat = require 'Coat'
+local dado = require 'dado.sql'
+
+local error = Coat.error
+local checktype = Coat.checktype
+
+module 'Coat.Persistent'
+
+local Meta = require 'Coat.Persistent.Meta'
+
+local drv = {}
+local cnx = {}
+
+function establish_connection (class, driver, ...)
+ local function create_db_sequence_tables (conn)
+ if not conn:execute "select count(*) from db_sequence_state" then
+ local r, msg = conn:execute "create table db_sequence_state (dataset varchar(50), state_id int(11))"
+ if not r then
+ error(msg)
+ end
+ end
+ end
+
+ drv[class] = driver
+ require('luasql.' .. driver)
+ local env = _G.luasql[driver]()
+ if not env then
+ error("cannot create an environment for " .. driver)
+ end
+ local conn, msg = env:connect(...)
+ if not conn then
+ error(msg)
+ end
+ cnx[class] = conn
+ create_db_sequence_tables(conn)
+ return conn
+end
+
+function connection (class)
+ return cnx[class]
+end
+
+local function execute (class, sql)
+ _G.print('#', sql)
+ local conn = cnx[class]
+ if not conn then
+ error("No connection for class " .. class)
+ end
+ local r, msg = conn:execute(sql)
+ if not r then
+ error(msg)
+ end
+ return r
+end
+
+local function next_id (class)
+ local dataset = Meta.table_name(class)
+ local cond = dado.AND { dataset = dataset }
+ local cur = execute(class, dado.select('*', 'db_sequence_state', cond))
+ local row = cur:fetch({}, 'a')
+ if not row then
+ local id_1 = 1
+ execute(class, dado.insert('db_sequence_state', { dataset = dataset, state_id = id_1 }))
+ return id_1
+ else
+ local id = row.state_id
+ local id_1 = id + 1
+ local cond = dado.AND { dataset = dataset, state_id = id }
+ execute(class, dado.update('db_sequence_state', { state_id = id_1 }, cond))
+ return id_1
+ end
+end
+
+function save (class, obj)
+ local primary_key = Meta.primary_key(class)
+
+ local values = {}
+ for field in Meta.attributes(class) do
+ local val = obj[field]
+ if val ~= nil then
+ values[field] = tostring(obj[field])
+ end
+ end
+
+ if rawget(obj, '_db_exist') then
+ execute(class, dado.update(Meta.table_name(class), values))
+ else
+ obj[primary_key] = next_id(class)
+ values[primary_key] = obj[primary_key]
+ execute(class, dado.insert(Meta.table_name(class), values))
+ rawset(obj, '_db_exist', true)
+ end
+
+ if primary_key then
+ return obj[primary_key]
+ else
+ return 'saved'
+ end
+end
+
+function delete (class, obj)
+ local primary_key = Meta.primary_key(class)
+ local cond = dado.AND { [primary_key] = obj[primary_key] }
+ return execute(class, dado.delete(Meta.table_name(class), cond))
+end
+
+function create (class, val)
+ if type(val) == 'table' and #val > 0 then
+ for i = 1, #val do
+ create(class, val[i])
+ end
+ else
+ local obj = class.new(val)
+ obj:save()
+ return obj
+ end
+end
+
+local function find_by_sql (class, sql)
+ local cur = execute(class, sql)
+ return function ()
+ local row = cur:fetch({}, 'a')
+ if row then
+ local obj = class.new(row)
+ rawset(obj, '_db_exist', true)
+ return obj
+ else
+ cur:close()
+ return nil
+ end
+ end
+end
+
+function find (class, val)
+ if val == nil then
+ return find_by_sql(class, dado.select('*', Meta.table_name(class)))
+ else
+ local cond = dado.AND { [Meta.primary_key(class)] = val }
+ return find_by_sql(class, dado.select('*', Meta.table_name(class), cond))
+ end
+end
+
+function has_p (class, name, options)
+ checktype('has_p', 1, name, 'string')
+ checktype('has_p', 2, options or {}, 'table')
+
+ class['find_by_' .. name] = function (val)
+ if val == nil then
+ error "Cannot find without a value"
+ end
+ local cond = dado.AND { [name] = val }
+ return find_by_sql(class, dado.select('*', Meta.table_name(class), cond))
+ end
+
+ Meta.attribute(class, name)
+ Coat.has(class, name, options)
+end
+
+function _G.persistent (modname, options)
+ checktype('persistent', 1, modname, 'string')
+ options = options or {}
+ checktype('persistent', 2, options, 'table')
+ local primary_key = options.primary_key or 'id'
+ local table_name = options.table_name or modname:gsub('%.', '_')
+ local M = Coat._class(modname)
+ M.establish_connection = function (...) return establish_connection(M, ...) end
+ M.connection = function () return connection(M) end
+ M.save = function (...) return save(M, ...) end
+ M.delete = function (...) return delete(M, ...) end
+ M.create = function (...) return create(M, ...) end
+ M.find = function (...) return find(M, ...) end
+ M.has_p = setmetatable({}, { __newindex = function (t, k, v) has_p(M, k, v) end })
+ Meta.primary_key(M, primary_key)
+ Meta.table_name(M, table_name)
+ Meta.attribute(M, primary_key)
+ Coat.has(M, primary_key, { is = 'rw', isa = 'number' })
+end
+
+_VERSION = "0.0.1"
+_DESCRIPTION = "lua-CoatPersistent : an ORM for lua-Coat"
+_COPYRIGHT = "Copyright (c) 2010 Francois Perrad"
+--
+-- This library is licensed under the terms of the MIT/X11 license,
+-- like Lua itself.
+--
51 src/Coat/Persistent/Meta.lua
@@ -0,0 +1,51 @@
+
+--
+-- lua-Coat : <http://fperrad.github.com/lua-CoatPersistent/>
+--
+
+local next = next
+local table = require 'table'
+
+module 'Coat.Persistent.Meta'
+
+local _primary_key = {}
+function primary_key (class, val)
+ if val then
+ _primary_key[class] = val
+ else
+ return _primary_key[class]
+ end
+end
+
+local _table_name = {}
+function table_name (class, val)
+ if val then
+ _table_name[class] = val
+ else
+ return _table_name[class]
+ end
+end
+
+local _attr = {}
+
+function attribute (class, attr)
+ if not _attr[class] then
+ _attr[class] = {}
+ end
+ table.insert(_attr[class], attr)
+end
+
+function attributes (class)
+ local i = 0
+ return function ()
+ i = i + 1
+ return _attr[class][i]
+ end
+end
+
+--
+-- Copyright (c) 2010 Francois Perrad
+--
+-- This library is licensed under the terms of the MIT/X11 license,
+-- like Lua itself.
+--
21 test/000-require.t
@@ -0,0 +1,21 @@
+#!/usr/bin/env lua
+
+require 'Test.More'
+
+plan(9)
+
+if not require_ok 'Coat.Persistent' then
+ BAIL_OUT "no lib"
+end
+
+local m = require 'Coat.Persistent'
+type_ok( m, 'table' )
+is( m, Coat.Persistent )
+is( m, package.loaded.Coat.Persistent )
+like( m._COPYRIGHT, 'Perrad', "_COPYRIGHT" )
+like( m._DESCRIPTION, 'ORM', "_DESCRIPTION" )
+type_ok( m._VERSION, 'string', "_VERSION" )
+like( m._VERSION, '^%d%.%d%.%d$' )
+
+is( Coat.Persistent.math, nil, "check ns pollution" )
+
84 test/001-sqlite_binding.t
@@ -0,0 +1,84 @@
+#!/usr/bin/env lua
+
+require 'Coat.Persistent'
+
+persistent 'Person'
+
+has_p.name = { is = 'rw', isa = 'string' }
+has_p.age = { is = 'rw', isa = 'number' }
+
+sql_create = [[
+ CREATE TABLE person (
+ id INTEGER,
+ name CHAR(64),
+ age INTEGER
+ )
+]]
+
+require 'Test.More'
+
+plan(19)
+
+if os.getenv "GEN_PNG" and os.execute "dot -V" == 0 then
+ local f = io.popen("dot -T png -o 001.png", 'w')
+ f:write(require 'Coat.UML'.to_dot())
+ f:close()
+end
+
+os.remove 'test.db'
+local conn = Person.establish_connection('sqlite3', 'test.db')
+conn:execute(Person.sql_create)
+
+ok( Coat.Meta.Class.has( Person, 'id' ), "field id" )
+ok( Coat.Meta.Class.has( Person, 'name' ), "field name" )
+ok( Coat.Meta.Class.has( Person, 'age' ), "field age" )
+
+john = Person { name = 'John', age = 23 }
+is( john:type(), 'Person', "Person" )
+ok( john:isa 'Person' )
+
+is( john.id, nil, "john.id is nil" )
+ok( john:save(), "john:save()" )
+is( john.id, 1, "john.id is 1" )
+--john.age = john.age + 1
+--ok( john:save(), "john:save()" )
+
+brenda = Person { name = 'Brenda', age = 22 }
+is( brenda.id, nil, "brenda.id is nil" )
+ok( brenda:save(), "brenda:save()" )
+is( brenda.id, 2, "brenda.id is 2" )
+
+local p = Person.find(1)()
+ok( p, "Person.find(1) returns something" )
+ok( p:isa 'Person', "it is a Person" )
+is( p.name, 'John', "it is John" )
+is( p.age, 23 )
+
+local p = Person.find_by_name('Brenda')()
+ok( p, "Person.find_by_name returns something" )
+ok( p:isa 'Person', "it is a Person" )
+is( p.name, 'Brenda', "it is Brenda" )
+
+ok( brenda:delete(), "brenda:delete()" )
+
+--bob = Person.create { name = 'Bob', age = 23 }
+--ok( bob, "Person.create returns something" )
+--ok( bob:isa 'Person', "it is a Person" )
+--is( bob.id, 3 )
+
+--Person.create {
+-- { name = 'A', age = 20 },
+-- { name = 'B', age = 20 },
+--}
+--for p in Person.find_by_age(23) do
+-- print(p.name)
+--end
+
+--for p in Person.find() do
+-- print(p.name)
+--end
+
+--local p = brenda.find(1)()
+--ok( p, "Person.find(1) returns something" )
+--ok( p:isa 'Person', "it is a Person" )
+--is( p.name, 'John', "it is John" )

0 comments on commit 7b080eb

Please sign in to comment.
Something went wrong with that request. Please try again.