public
Rubygem
Description: JSON Web App Framework
Homepage: http://halcyon.rubyforge.org/
Clone URL: git://github.com/mtodd/halcyon.git
Search Repo:
Click here to lend your support to: halcyon and make a donation at www.pledgie.com !
Added WeeDB example Halcyon app. There are sufficient sample apps for now. 
[#23 state:resolved]
mtodd (author)
Mon May 12 20:39:54 -0700 2008
commit  3a3108bb9889d215ef74519c49e3dfd4ac7189dc
tree    4b3855262f99a58f0cfb3f742064804bf05d637a
parent  08cf8650bf861533a37cd30a074191df2314b74b
...
 
 
...
1
2
0
@@ -1 +1,3 @@
0
+config/database.yml
0
+log/*.log
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
0
@@ -1 +1,63 @@
0
+= WeeDB
0
+
0
+The Halcyon clone of TinyDB (http://tinydb.org/).
0
+
0
+
0
+== Introduction
0
+
0
+This is a simple
0
+
0
+
0
+== Installation & Setup
0
+
0
+Install the dependencies listed below, set up the database, and run the
0
+migrations.
0
+
0
+=== Dependencies
0
+
0
+* Halcyon (halcyon >= 0.5.0)
0
+* Sequel (sequel >= 1.5.1)
0
+
0
+=== Database
0
+
0
+Set up a database, @weedb_development@ for example, and create a user,
0
+@wee_user@ should work fine. Copy the @config/database.sample.yml@ file to
0
+@config/database.yml@ and update the values to match the database name and user
0
+set up.
0
+
0
+Now, run the migrations:
0
+
0
+ $ rake db:migrate
0
+
0
+== Usage
0
+
0
+=== Start The Server
0
+
0
+ $ halcyon start -p 4647
0
+
0
+=== Interacting With The Server
0
+
0
+In Ruby:
0
+
0
+ $ irb -r lib/client
0
+ >> client = WeeDB::Client.new("http://localhost:4647/")
0
+ >> client << {'foo' => 'bar'}
0
+ => '1aB2'
0
+ >> client['1aB2']
0
+ => {'foo' => 'bar'}
0
+
0
+Elsewhere:
0
+
0
+ $ curl --data '' http://localhost:4647/?foo=bar
0
+ {'status':200,'body':'2bC3'}
0
+ $ curl http://localhost:4647/2bC3
0
+ {'status':200,'body':{'foo':'bar'}}
0
+
0
+== License
0
+
0
+WeeDB is licensed under the MIT License.
0
+
0
+== Contact
0
+
0
+Matt Todd <chiology@gmail.com>
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
0
@@ -1 +1,52 @@
0
+$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
0
+%w(rubygems rake rake/clean rake/rdoctask fileutils pp logger rack/mock halcyon).each{|dep|require dep}
0
+
0
+include FileUtils
0
+
0
+# Halcyon.root => the root application directory
0
+# Halcyon.app => the application name
0
+
0
+desc "Start the application on port 4647"
0
+task :start do
0
+ sh "halcyon start -p 4647"
0
+end
0
+
0
+desc "Generate RDoc documentation"
0
+Rake::RDocTask.new(:rdoc) do |rdoc|
0
+ rdoc.options << '--line-numbers' << '--inline-source' <<
0
+ '--main' << 'README' <<
0
+ '--title' << "#{Halcyon.app} Documentation" <<
0
+ '--charset' << 'utf-8'
0
+ rdoc.rdoc_dir = "doc"
0
+ rdoc.rdoc_files.include('README')
0
+ rdoc.rdoc_files.include('app/**/*.rb')
0
+ rdoc.rdoc_files.include('lib/**/*.rb')
0
+end
0
+
0
+# = Custom Rake Tasks
0
+#
0
+# Add your custom rake tasks here.
0
+
0
+desc "Load up the application environment"
0
+task :env do
0
+ $log = ''
0
+ $logger = Logger.new(StringIO.new($log))
0
+ Halcyon.config = {:logger => $logger}
0
+ Halcyon::Runner.new
0
+end
0
+
0
+namespace(:db) do
0
+
0
+ desc "Migrate the database to the latest version"
0
+ task :migrate => :env do
0
+ current_version = Sequel::Migrator.get_current_migration_version(WeeDB::DB)
0
+ latest_version = Sequel::Migrator.apply(WeeDB::DB, Halcyon.paths[:lib]/'migrations')
0
+ puts "Database successfully migrated to latest version (#{latest_version})." if current_version < latest_version
0
+ puts "Migrations finished successfully."
0
+ end
0
+
0
+end
0
+
0
+# = Default Task
0
+task :default => Rake::Task['start']
...
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
0
@@ -1 +1,14 @@
0
+class Application < Halcyon::Controller
0
+
0
+ # Shortcuts
0
+
0
+ def read
0
+ #
0
+ end
0
+
0
+ def write
0
+ #
0
+ end
0
+
0
+end
...
 
...
1
0
@@ -1 +1,2 @@
0
+class Record < Sequel::Model; end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
0
@@ -1 +1,50 @@
0
+class Records < Application
0
+
0
+ # GET /records
0
+ def index
0
+ raise NotImplemented
0
+ end
0
+
0
+ # GET /records/1
0
+ def show
0
+ if record = Record[:url => params[:id]]
0
+ ok JSON.parse(record[:data])
0
+ else
0
+ raise NotFound
0
+ end
0
+ end
0
+
0
+ # POST /records
0
+ # POST /records?key1=value1&key2=value2&...
0
+ def create
0
+ verify_data_validity!
0
+
0
+ record = Record.new
0
+ record.url = WeeDB.generate_unique_url_key
0
+ record.data = (JSON.parse(params[:data]) rescue nil || {}).merge(query_params).to_json
0
+
0
+ if record.save
0
+ ok record.url
0
+ else
0
+ raise Exception
0
+ end
0
+ end
0
+
0
+ # PUT /records/1
0
+ def update
0
+ raise NotImplemented
0
+ end
0
+
0
+ # DELETE /records/1
0
+ def destroy
0
+ raise NotImplemented
0
+ end
0
+
0
+ private
0
+
0
+ def verify_data_validity!
0
+ raise BadRequest unless (JSON.parse(params[:data]) rescue nil).is_a?(Hash) or params[:data].nil?
0
+ end
0
+
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
0
@@ -1 +1,30 @@
0
+# = Framework
0
+#
0
+# <tt>allow_from</tt>: specifies what connections to accept;
0
+# * <tt>all</tt>: allow connections from all clients
0
+# * <tt>local</tt>: only allow connections from the same host (localhost et al)
0
+# * <tt>halcyon_clients</tt>: only Halcyon clients (tests the User-Agent only)
0
+allow_from: all
0
+
0
+# = Logging
0
+#
0
+# Configures the logging client in the framework, including destination,
0
+# level filter, and what logger to use.
0
+#
0
+# <tt>type</tt>: the logger to use (defaults to Ruby's <tt>Logger</tt>)
0
+# * <tt>Logger</tt>
0
+# * <tt>Analogger</tt>
0
+# * <tt>Logging</tt>
0
+# * <tt>Log4r</tt>
0
+# <tt>file</tt>: the log file; leave unset for STDOUT
0
+# <tt>level</tt>: the message filter level (default to <tt>debug</tt>)
0
+# * specific to the client used, often is: debug, info, warn, error, fatal
0
+logging:
0
+ type: Logger
0
+ # file: # STDOUT
0
+ level: debug
0
+
0
+# = Application
0
+#
0
+# Your application-specific configuration options here.
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0
@@ -1 +1,24 @@
0
+development:
0
+ adapter: mysql
0
+ encoding: utf8
0
+ database: weedb_development
0
+ username: wee_user
0
+ password: password
0
+ host: localhost
0
+
0
+test:
0
+ adapter: mysql
0
+ encoding: utf8
0
+ database: weedb_development
0
+ username: wee_user
0
+ password: password
0
+ host: localhost
0
+
0
+production:
0
+ adapter: mysql
0
+ encoding: utf8
0
+ database: weedb_development
0
+ username: wee_user
0
+ password: password
0
+ host: localhost
...
 
 
 
 
 
 
...
1
2
3
4
5
6
0
@@ -1 +1,7 @@
0
+# = Database
0
+#
0
+# Load the database configuration for the current environment.
0
+
0
+Halcyon.db = Halcyon::Runner.load_config(Halcyon::Runner.config_path('database'))
0
+Halcyon.db = Halcyon.db[(Halcyon.environment || :development).to_sym]
...
 
 
 
 
...
1
2
3
4
0
@@ -1 +1,5 @@
0
+# = Environment
0
+
0
+Halcyon.configurable_attr :environment
0
+Halcyon.environment = :development unless Halcyon.environment
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
0
@@ -1 +1,48 @@
0
+# = Hooks
0
+#
0
+# Specify actions to run at specific times during the application's execution,
0
+# such as the startup or shutdown events.
0
+#
0
+# Available Hooks
0
+# +startup+
0
+# +shutdown+
0
+#
0
+# All hooks take the current application configuration as its sole block param.
0
+#
0
+# Examples
0
+# Halcyon::Application.startup do |config|
0
+# # Halcyon.db set in config/initialize/database.rb
0
+# ::DB = Sequel(Halcyon.db)
0
+# logger.info "Connected to database"
0
+# end
0
+#
0
+# The +logger+ object is available to log messages on status or otherwise.
0
+
0
+# = Startup
0
+#
0
+# Run when the Halcyon::Application object is instanciated, after all
0
+# initializers are loaded.
0
+#
0
+# Ideal for establishing connections to resources like databases.
0
+# Establish configuration options in initializer files, though.
0
+Halcyon::Application.startup do |config|
0
+ # Connect to DB
0
+ WeeDB::DB = Sequel.connect(Halcyon.db)
0
+ WeeDB::DB.logger = Halcyon.logger if $DEBUG
0
+ logger.info 'Connected to Database'
0
+
0
+ # Load models
0
+ Dir.glob([Halcyon.paths[:model]/'*.rb']).each do |model|
0
+ logger.debug "Load: #{File.basename(model).chomp('.rb').camel_case} Model" if require model
0
+ end
0
+end
0
+
0
+# = Shutdown
0
+#
0
+# Run <tt>at_exit</tt>. Should run in most cases of termination.
0
+#
0
+# Ideal for closing connections to resources.
0
+Halcyon::Application.shutdown do |config|
0
+ # logger.info 'Define shutdown tasks in config/init/hooks.rb'
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
0
@@ -1 +1,14 @@
0
+# = Required Libraries
0
+#
0
+# Specify required libraries specific to the operation of your application.
0
+#
0
+# Examples
0
+# %(digest/md5 tempfile date).each{|dep|require dep}
0
+#
0
+# RubyGems should already be required by Halcyon, so don't include it.
0
+
0
+%w(weedb sequel digest/md5).each{|dep|require dep}
0
+
0
+# JSON is also another requirement, but Halcyon already handles the complexity
0
+# of loading the appropriate JSON Gem.
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
0
@@ -1 +1,56 @@
0
+# = Routes
0
+#
0
+# Halcyon::Application::Router is the request routing mapper for the merb
0
+# framework.
0
+#
0
+# You can route a specific URL to a controller / action pair:
0
+#
0
+# r.match("/contact").
0
+# to(:controller => "info", :action => "contact")
0
+#
0
+# You can define placeholder parts of the url with the :symbol notation. These
0
+# placeholders will be available in the params hash of your controllers. For example:
0
+#
0
+# r.match("/books/:book_id/:action").
0
+# to(:controller => "books")
0
+#
0
+# Or, use placeholders in the "to" results for more complicated routing, e.g.:
0
+#
0
+# r.match("/admin/:module/:controller/:action/:id").
0
+# to(:controller => ":module/:controller")
0
+#
0
+# You can also use regular expressions, deferred routes, and many other options.
0
+# See merb/specs/merb/router.rb for a fairly complete usage sample.
0
+#
0
+# Stolen directly from generated Merb app. All documentation applies.
0
+# Read more about the Merb router at http://merbivore.com/.
0
+Halcyon::Application.route do |r|
0
+
0
+ r.resources :records
0
+
0
+ r.match('/:id', :method => 'get').to(:controller => 'records', :action => 'show')
0
+ r.match('/', :method => 'post').to(:controller => 'records', :action => 'create')
0
+
0
+ # Sample route for the sample functionality in Application.
0
+ # Safe to remove!
0
+ # r.match('/time').to(:controller => 'application', :action => 'time')
0
+
0
+ # RESTful routes
0
+ # r.resources :posts
0
+
0
+ # This is the default route for /:controller/:action/:id
0
+ # This is fine for most cases. If you're heavily using resource-based
0
+ # routes, you may want to comment/remove this line to prevent
0
+ # clients from calling your create or destroy actions with a GET
0
+ # r.default_routes
0
+
0
+ # Change this for the default route to be available at /
0
+ # r.match('/').to(:controller => 'application', :action => 'index')
0
+ # It can often be useful to respond with available functionality if the
0
+ # application is a public-facing service.
0
+
0
+ # Default not-found route
0
+ {:action => 'not_found'}
0
+
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
0
@@ -1 +1,112 @@
0
+%w(rubygems halcyon).each{|dep|require dep}
0
+
0
+module WeeDB
0
+
0
+ # = Client
0
+ #
0
+ # The client interface for accessing services provided by the Halcyon
0
+ # application as defined in the controllers in <tt>app/</tt>.
0
+ #
0
+ # == Usage
0
+ #
0
+ # To use the Client in your application, create an instance and call methods
0
+ # defined here. For example:
0
+ #
0
+ # client = Weedb::Client.new('http://localhost:4647/')
0
+ # client.time #=> "Tue Apr 15 21:04:15 -0400 2008"
0
+ #
0
+ # You can just as easily call the primary <tt>get</tt>, <tt>post</tt>,
0
+ # <tt>put</tt>, and <tt>delete</tt> methods as well, passing in the +path+
0
+ # and any params. For example:
0
+ #
0
+ # client.get('/time') #=> "Tue Apr 15 21:04:15 -0400 2008"
0
+ #
0
+ # By default, if you enter a bad (non-existent) path or the application
0
+ # raises an exception and cannot complete successfully, the standard response
0
+ # format will be returned but with more appropriate +status+ and +body+
0
+ # values. For instance:
0
+ #
0
+ # client.get('/nonexistent/path') #=> {:status=>404,:body=>"Not Found"}
0
+ #
0
+ # Exceptions can be raised on any +status+ returned other than +200+ if you
0
+ # set <tt>Halcyon::Client#raise_exceptions!</tt> to +true+ (which is the
0
+ # default param).
0
+ #
0
+ # client.raise_exceptions! #=> true
0
+ # client.get('/nonexistent/path') #=> NotFound exception is raised
0
+ #
0
+ # These exceptions all inherit from <tt>Halcyon::Exceptions::Base</tt> so
0
+ # <tt>rescue</tt>ing just normal Halcyon errors is trivial.
0
+ #
0
+ # However, setting this value can cause the meaning and the appropriate
0
+ # error-handling measures put in place in actions. Although each method
0
+ # could just as easily set the +raise_exceptions+ configuration option
0
+ # itself, it is not advised to do so due to the possibility of non-
0
+ # consistent and confusing behavior it can cause.
0
+ #
0
+ # If raising exceptions is preferred, it should be set as soon as the
0
+ # client is created and the client methods should be designed accordingly.
0
+ class Client < Halcyon::Client
0
+
0
+ def self.version
0
+ VERSION.join('.')
0
+ end
0
+
0
+ # Send data to the WeeDB
0
+ # +data+ the data to save
0
+ #
0
+ # Examples
0
+ # client.push({'foo'=>'bar'}) #=> '1aB2'
0
+ #
0
+ # Returns String:record_id
0
+ #
0
+ def push(data)
0
+ if (response = post('/records', :data => data.to_json))[:status] == 200
0
+ response[:body]
0
+ else
0
+ # failure; return all response data for individual attention
0
+ response
0
+ end
0
+ end
0
+ alias_method :store, :push
0
+
0
+ # Alias for <tt>push</tt>.
0
+ #
0
+ # Examples
0
+ # client << {'key' => 'value'} #=> '2bC3'
0
+ #
0
+ def <<(data)
0
+ push(data)
0
+ end
0
+
0
+ # Retrieve the data from the WeeDB
0
+ # +key+ the ID the data is saved under
0
+ #
0
+ # Examples
0
+ # client.retrieve('1aB2') #=> {'foo'=>'bar'}
0
+ #
0
+ # Returns *:data
0
+ #
0
+ def pull(key)
0
+ if (response = get("/records/#{key}"))[:status] == 200
0
+ response[:body]
0
+ else
0
+ # failure; return all response data for individual attention
0
+ response
0
+ end
0
+ end
0
+ alias_method :retrieve, :pull
0
+
0
+ # Alias for <tt>pull</tt>.
0
+ #
0
+ # Examples
0
+ # client['2bC3'] #=> {'key' => 'value'}
0
+ #
0
+ def [](key)
0
+ pull(key)
0
+ end
0
+
0
+ end
0
+
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
0
@@ -1 +1,13 @@
0
+class CreateRecords < Sequel::Migration
0
+ def up
0
+ create_table :records do
0
+ primary_key :id
0
+ varchar :url, :size => 4, :unique => true, :null => false
0
+ text :data
0
+ end
0
+ end
0
+ def down
0
+ execute 'DROP TABLE records'
0
+ end
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0
@@ -1 +1,22 @@
0
+# The WeeDB database.
0
+module WeeDB
0
+
0
+ VERSION = [0,0,0]
0
+
0
+ class << self
0
+
0
+ def version
0
+ VERSION.join('.')
0
+ end
0
+
0
+ def generate_unique_url_key
0
+ loop do
0
+ key = Digest::MD5.hexdigest(Time.now.usec.to_s)[0..3]
0
+ return key if Record[:url => key].nil?
0
+ end
0
+ end
0
+
0
+ end
0
+
0
+end
...
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
0
@@ -1 +1,9 @@
0
+require 'halcyon'
0
+
0
+$:.unshift(Halcyon.root/'lib')
0
+
0
+puts "(Starting in #{Halcyon.root})"
0
+
0
+Thin::Logging.silent = true if defined? Thin
0
+run Halcyon::Runner.new

Comments

    No one has commented yet.