cramp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+
3
+ $: << File.join(File.dirname(__FILE__), "lib")
4
+ require 'cramp/model'
5
+
6
+ Cramp::Model.init(:username => 'root', :database => 'arel_development')
7
+
8
+ class User < Cramp::Model::Base
9
+ attribute :id, :type => Integer, :primary_key => true
10
+ attribute :name
11
+
12
+ validates_presence_of :name
13
+ end
14
+
15
+ EM.run do
16
+ user = User.new
17
+
18
+ user.save do |status|
19
+ if status.success?
20
+ puts "WTF!"
21
+ else
22
+ puts "Oops. Found errors : #{user.errors.inspect}"
23
+
24
+ user.name = 'Lush'
25
+ user.save do
26
+ User.where(User[:name].eq('Lush')).all {|users| puts users.inspect; EM.stop }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,114 @@
1
+ module Cramp
2
+ module Controller
3
+ class Base
4
+ ASYNC_RESPONSE = [-1, {}, []]
5
+
6
+ DEFAULT_STATUS = 200
7
+ DEFAULT_HEADERS = { 'Content-Type' => 'text/html' }
8
+
9
+ def self.call(env)
10
+ controller = new(env).process
11
+ end
12
+
13
+ def self.set_default_response(status = 200, headers = DEFAULT_HEADERS)
14
+ @@default_status = 200
15
+ @@default_headers = headers
16
+ end
17
+
18
+ def self.default_status
19
+ defined?(@@default_status) ? @@default_status : DEFAULT_STATUS
20
+ end
21
+
22
+ def self.default_headers
23
+ defined?(@@default_headers) ? @@default_headers : DEFAULT_HEADERS
24
+ end
25
+
26
+ def self.periodic_timer(method, options = {})
27
+ @@periodic_timers ||= []
28
+ @@periodic_timers << [method, options]
29
+ end
30
+
31
+ def self.periodic_timers
32
+ defined?(@@periodic_timers) ? @@periodic_timers : []
33
+ end
34
+
35
+ def self.keep_connection_alive(options = {})
36
+ options = { :every => 30 }.merge(options)
37
+ periodic_timer :keep_connection_alive, options
38
+ end
39
+
40
+ def initialize(env)
41
+ @env = env
42
+ @timers = []
43
+ end
44
+
45
+ def process
46
+ EM.next_tick { before_start }
47
+ ASYNC_RESPONSE
48
+ end
49
+
50
+ def request
51
+ @request ||= Rack::Request.new(@env)
52
+ end
53
+
54
+ def params
55
+ @params ||= @env['usher.params']
56
+ end
57
+
58
+ def render(body)
59
+ @body.call(body)
60
+ end
61
+
62
+ def send_initial_response(response_status, response_headers, response_body)
63
+ EM.next_tick { @env['async.callback'].call [response_status, response_headers, response_body] }
64
+ end
65
+
66
+ def finish
67
+ EM.next_tick { @body.succeed }
68
+ end
69
+
70
+ def before_start
71
+ continue
72
+ end
73
+
74
+ def halt(status, headers = self.class.default_headers, halt_body = '')
75
+ send_initial_response(status, headers, halt_body)
76
+ end
77
+
78
+ def continue
79
+ init_async_body
80
+ send_initial_response(self.class.default_status, self.class.default_headers, @body)
81
+
82
+ EM.next_tick { start_periodic_timers }
83
+ EM.next_tick { start } if respond_to?(:start)
84
+ end
85
+
86
+ def init_async_body
87
+ @body = Body.new
88
+ @body.callback { on_finish }
89
+ @body.errback { on_finish }
90
+
91
+ @body.callback { stop_periodic_timers }
92
+ @body.errback { stop_periodic_timers }
93
+ end
94
+
95
+ def start_periodic_timers
96
+ self.class.periodic_timers.each do |method, options|
97
+ @timers << EventMachine::PeriodicTimer.new(options[:every] || 1) { send(method) }
98
+ end
99
+ end
100
+
101
+ def stop_periodic_timers
102
+ @timers.each {|t| t.cancel }
103
+ end
104
+
105
+ def on_finish
106
+ end
107
+
108
+ def keep_connection_alive
109
+ render " "
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright 2008 James Tucker <raggi@rubyforge.org>.
2
+
3
+ module Cramp
4
+ module Controller
5
+ class Body
6
+
7
+ include EventMachine::Deferrable
8
+
9
+ def initialize
10
+ @queue = []
11
+ end
12
+
13
+ def call(body)
14
+ @queue << body
15
+ schedule_dequeue
16
+ end
17
+
18
+ def each &blk
19
+ @body_callback = blk
20
+ schedule_dequeue
21
+ end
22
+
23
+ def closed?
24
+ @deferred_status != :unknown
25
+ end
26
+
27
+ private
28
+
29
+ def schedule_dequeue
30
+ return unless @body_callback
31
+ EventMachine.next_tick do
32
+ next unless body = @queue.shift
33
+
34
+ body.each{|chunk| @body_callback.call(chunk) }
35
+ schedule_dequeue unless @queue.empty?
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'vendor/activesupport/lib'))
2
+ require 'active_support'
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+
6
+ require 'usher'
7
+ require 'rack'
8
+
9
+ module Cramp
10
+ module Controller
11
+ autoload :Base, "cramp/controller/base"
12
+ autoload :Body, "cramp/controller/body"
13
+ end
14
+ end
@@ -0,0 +1,66 @@
1
+ class Arel::Session
2
+ def create(insert, &block)
3
+ insert.call(&block)
4
+ end
5
+
6
+ def read(select, &block)
7
+ select.call(&block)
8
+ end
9
+
10
+ def update(update, &block)
11
+ update.call(&block)
12
+ end
13
+
14
+ def delete(delete, &block)
15
+ delete.call(&block)
16
+ end
17
+
18
+ end
19
+
20
+ class Arel::Relation
21
+ def call(&block)
22
+ engine.read(self, &block)
23
+ end
24
+
25
+ def all(&block)
26
+ session.read(self) {|rows| block.call(rows) }
27
+ end
28
+
29
+ def first(&block)
30
+ session.read(self) {|rows| block.call(rows[0]) }
31
+ end
32
+
33
+ def each(&block)
34
+ session.read(self) {|rows| rows.each {|r| block.call(r) } }
35
+ end
36
+
37
+ def insert(record, &block)
38
+ session.create(Arel::Insert.new(self, record), &block)
39
+ end
40
+
41
+ def update(assignments, &block)
42
+ session.update(Arel::Update.new(self, assignments), &block)
43
+ end
44
+
45
+ def delete(&block)
46
+ session.delete(Arel::Deletion.new(self), &block)
47
+ end
48
+ end
49
+
50
+ class Arel::Deletion
51
+ def call(&block)
52
+ engine.delete(self, &block)
53
+ end
54
+ end
55
+
56
+ class Arel::Insert
57
+ def call(&block)
58
+ engine.create(self, &block)
59
+ end
60
+ end
61
+
62
+ class Arel::Update
63
+ def call(&block)
64
+ engine.update(self, &block)
65
+ end
66
+ end
@@ -0,0 +1,104 @@
1
+ # Copyright (c) 2009 Koziarski Software Ltd
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ module Cramp
16
+ module Model
17
+ class Attribute
18
+
19
+ FORMATS = {}
20
+ FORMATS[Date] = /^\d{4}\/\d{2}\/\d{2}$/
21
+ FORMATS[Integer] = /^-?\d+$/
22
+ FORMATS[Float] = /^-?\d*\.\d*$/
23
+ FORMATS[Time] = /\A\s*
24
+ -?\d+-\d\d-\d\d
25
+ T
26
+ \d\d:\d\d:\d\d
27
+ (\.\d*)?
28
+ (Z|[+-]\d\d:\d\d)?
29
+ \s*\z/ix # lifted from the implementation of Time.xmlschema
30
+
31
+ CONVERTERS = {}
32
+ CONVERTERS[Date] = Proc.new do |str|
33
+ Date.strptime(str, "%Y/%m/%d")
34
+ end
35
+
36
+ CONVERTERS[Integer] = Proc.new do |str|
37
+ Integer(str)
38
+ end
39
+
40
+ CONVERTERS[Float] = Proc.new do |str|
41
+ Float(str)
42
+ end
43
+
44
+ CONVERTERS[Time] = Proc.new do |str|
45
+ Time.xmlschema(str)
46
+ end
47
+
48
+ attr_reader :name
49
+ def initialize(name, owner_class, options)
50
+ @name = name.to_s
51
+ @owner_class = owner_class
52
+ @options = options
53
+
54
+ # append_validations!
55
+ define_methods!
56
+ end
57
+
58
+ # I think this should live somewhere in Amo
59
+ def check_value!(value)
60
+ # Allow nil and Strings to fall back on the validations for typecasting
61
+ # Everything else gets checked with is_a?
62
+ if value.nil?
63
+ nil
64
+ elsif value.is_a?(String)
65
+ value
66
+ elsif value.is_a?(expected_type)
67
+ value
68
+ else
69
+ raise TypeError, "Expected #{expected_type.inspect} but got #{value.inspect}"
70
+ end
71
+ end
72
+
73
+ def expected_type
74
+ @options[:type] || String
75
+ end
76
+
77
+ def type_cast(value)
78
+ if value.is_a?(expected_type)
79
+ value
80
+ elsif (converter = CONVERTERS[expected_type]) && (value =~ FORMATS[expected_type])
81
+ converter.call(value)
82
+ else
83
+ value
84
+ end
85
+ end
86
+
87
+ def append_validations!
88
+ if f = FORMATS[expected_type]
89
+ @owner_class.validates_format_of @name, :with => f, :unless => lambda {|obj| obj.send(name).is_a? expected_type }, :allow_nil => @options[:allow_nil]
90
+ end
91
+ end
92
+
93
+ def define_methods!
94
+ @owner_class.define_attribute_methods(true)
95
+ end
96
+
97
+ def primary_key?
98
+ @options[:primary_key]
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,80 @@
1
+ # Copyright (c) 2009 Koziarski Software Ltd
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ module Cramp
16
+ module Model
17
+ module AttributeMethods
18
+
19
+ extend ActiveSupport::Concern
20
+ include ActiveModel::AttributeMethods
21
+
22
+ module ClassMethods
23
+ def attribute(name, options = {})
24
+ write_inheritable_hash(:model_attributes, {name => Attribute.new(name, self, options)})
25
+ end
26
+
27
+ def define_attribute_methods(force = false)
28
+ return unless model_attributes
29
+ undefine_attribute_methods if force
30
+ super(model_attributes.keys)
31
+ end
32
+ end
33
+
34
+ included do
35
+ class_inheritable_hash :model_attributes
36
+ undef_method(:id) if method_defined?(:id)
37
+
38
+ attribute_method_suffix("", "=")
39
+ end
40
+
41
+ def write_attribute(name, value)
42
+ if ma = self.class.model_attributes[name.to_sym]
43
+ value = ma.check_value!(value)
44
+ end
45
+ @attributes[name] = value
46
+ end
47
+
48
+ def read_attribute(name)
49
+ if ma = self.class.model_attributes[name]
50
+ ma.type_cast(@attributes[name])
51
+ else
52
+ @attributes[name]
53
+ end
54
+ end
55
+
56
+ def attributes=(attributes)
57
+ attributes.each do |(name, value)|
58
+ send("#{name}=", value)
59
+ end
60
+ end
61
+
62
+ protected
63
+
64
+ def attribute_method?(name)
65
+ @attributes.include?(name.to_sym) || model_attributes[name.to_sym]
66
+ end
67
+
68
+ private
69
+
70
+ def attribute(name)
71
+ read_attribute(name.to_sym)
72
+ end
73
+
74
+ def attribute=(name, value)
75
+ write_attribute(name.to_sym, value)
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,86 @@
1
+ module Cramp
2
+ module Model
3
+ class Base
4
+
5
+ extend Finders
6
+ include AttributeMethods
7
+ include ActiveModel::Validations
8
+
9
+ class << self
10
+ def columns
11
+ @columns ||= arel_table.columns
12
+ end
13
+
14
+ def column_names
15
+ columns.map(&:name)
16
+ end
17
+
18
+ def primary_key
19
+ @primary_key ||= model_attributes.detect {|k, v| v.primary_key? }[0]
20
+ end
21
+
22
+ def instantiate(record)
23
+ object = allocate
24
+ object.instance_variable_set("@attributes", record.with_indifferent_access)
25
+ object
26
+ end
27
+ end
28
+
29
+ attr_reader :attributes
30
+
31
+ def initialize(attributes = {})
32
+ @new_record = true
33
+ @attributes = {}.with_indifferent_access
34
+ self.attributes = attributes
35
+ end
36
+
37
+ def new_record?
38
+ @new_record
39
+ end
40
+
41
+ def save(&block)
42
+ if valid?
43
+ new_record? ? create_record(&block) : update_record(&block)
44
+ else
45
+ block.arity == 1 ? block.call(Status.new(self, false)) : block.call if block
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def create_record(&block)
52
+ self.class.arel_table.insert(arel_attributes(true)) do |new_id|
53
+ if new_id.present?
54
+ self.id = new_id
55
+ saved = true
56
+ @new_record = false
57
+ else
58
+ saved = false
59
+ end
60
+
61
+ block.arity == 1 ? block.call(Status.new(self, saved)) : block.call if block
62
+ end
63
+ end
64
+
65
+ def update_record(&block)
66
+ relation = self.class.arel_table.where(self.class[self.class.primary_key].eq(send(self.class.primary_key)))
67
+
68
+ relation.update(arel_attributes) do |updated_rows|
69
+ block.arity == 1 ? block.call(updated_rows) : block.call if block
70
+ end
71
+ end
72
+
73
+ def arel_attributes(exclude_primary_key = true, attribute_names = @attributes.keys)
74
+ attrs = {}
75
+
76
+ attribute_names.each do |name|
77
+ value = read_attribute(name)
78
+ attrs[self.class.arel_table[name]] = value
79
+ end
80
+
81
+ attrs
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,72 @@
1
+ # Some of it yanked from Rails
2
+
3
+ # Copyright (c) 2004-2009 David Heinemeier Hansson
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module Cramp
25
+ module Model
26
+ class Column < Struct.new(:name, :default, :sql_type, :null)
27
+ attr_reader :type
28
+
29
+ def initialize(name, default, sql_type, null)
30
+ super
31
+ @type = simplified_type(sql_type)
32
+ end
33
+
34
+ private
35
+
36
+ def simplified_type(field_type)
37
+ case field_type
38
+ when /int/i
39
+ :integer
40
+ when /float|double/i
41
+ :float
42
+ when /decimal|numeric|number/i
43
+ extract_scale(field_type) == 0 ? :integer : :decimal
44
+ when /datetime/i
45
+ :datetime
46
+ when /timestamp/i
47
+ :timestamp
48
+ when /time/i
49
+ :time
50
+ when /date/i
51
+ :date
52
+ when /clob/i, /text/i
53
+ :text
54
+ when /blob/i, /binary/i
55
+ :binary
56
+ when /char/i, /string/i
57
+ :string
58
+ when /boolean/i
59
+ :boolean
60
+ end
61
+ end
62
+
63
+ def extract_scale(sql_type)
64
+ case sql_type
65
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
66
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
67
+ end
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,21 @@
1
+ class EventedMysql
2
+ def self.execute_now(query)
3
+ @n ||= 0
4
+ connection = connection_pool[@n]
5
+ @n = 0 if (@n+=1) >= connection_pool.size
6
+ connection.execute_now(query)
7
+ end
8
+
9
+ def execute_now(sql)
10
+ log 'mysql sending', sql
11
+ @mysql.query(sql)
12
+ rescue Mysql::Error => e
13
+ log 'mysql error', e.message
14
+ if DisconnectErrors.include? e.message
15
+ @@queue << [response, sql, cblk, eblk]
16
+ return close
17
+ else
18
+ raise e
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ module Cramp
2
+ module Model
3
+ class Engine
4
+ include Quoting
5
+
6
+ def initialize(settings)
7
+ @settings = settings
8
+ @quoted_column_names, @quoted_table_names = {}, {}
9
+
10
+ EventedMysql.settings.update(settings)
11
+ end
12
+
13
+ def create(relation, &block)
14
+ EventedMysql.insert(relation.to_sql) {|rows| yield(rows) if block_given? }
15
+ end
16
+
17
+ def read(relation, &block)
18
+ EventedMysql.select(relation.to_sql) {|rows| yield(rows) }
19
+ end
20
+
21
+ def update(relation)
22
+ EventedMysql.update(relation.to_sql) {|rows| yield(rows) if block_given? }
23
+ end
24
+
25
+ def delete(relation)
26
+ EventedMysql.delete(relation.to_sql) {|rows| yield(rows) if block_given? }
27
+ end
28
+
29
+ def adapter_name
30
+ "Cramp MySQL Async Adapter"
31
+ end
32
+
33
+ def columns(table_name, name = nil)
34
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
35
+ columns = []
36
+ result = EventedMysql.execute_now(sql)
37
+
38
+ result.each { |field| columns << Column.new(field[0], field[4], field[1], field[2] == "YES") }
39
+ result.free
40
+ columns
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,298 @@
1
+ # Async MySQL driver for Ruby/EventMachine
2
+ # (c) 2008 Aman Gupta (tmm1)
3
+ # http://github.com/tmm1/em-mysql
4
+
5
+ require 'eventmachine'
6
+ require 'fcntl'
7
+
8
+ class Mysql
9
+ def result
10
+ @cur_result
11
+ end
12
+ end
13
+
14
+ class EventedMysql < EM::Connection
15
+ def initialize mysql, opts
16
+ @mysql = mysql
17
+ @fd = mysql.socket
18
+ @opts = opts
19
+ @current = nil
20
+ @@queue ||= []
21
+ @processing = false
22
+ @connected = true
23
+
24
+ log 'mysql connected'
25
+
26
+ self.notify_readable = true
27
+ EM.add_timer(0){ next_query }
28
+ end
29
+ attr_reader :processing, :connected, :opts
30
+ alias :settings :opts
31
+
32
+ DisconnectErrors = [
33
+ 'query: not connected',
34
+ 'MySQL server has gone away',
35
+ 'Lost connection to MySQL server during query'
36
+ ] unless defined? DisconnectErrors
37
+
38
+ def notify_readable
39
+ log 'readable'
40
+ if item = @current
41
+ @current = nil
42
+ start, response, sql, cblk, eblk = item
43
+ log 'mysql response', Time.now-start, sql
44
+ arg = case response
45
+ when :raw
46
+ result = @mysql.get_result
47
+ @mysql.instance_variable_set('@cur_result', result)
48
+ @mysql
49
+ when :select
50
+ ret = []
51
+ result = @mysql.get_result
52
+ result.each_hash{|h| ret << h }
53
+ log 'mysql result', ret
54
+ ret
55
+ when :update
56
+ result = @mysql.get_result
57
+ @mysql.affected_rows
58
+ when :insert
59
+ result = @mysql.get_result
60
+ @mysql.insert_id
61
+ else
62
+ result = @mysql.get_result
63
+ log 'got a result??', result if result
64
+ nil
65
+ end
66
+
67
+ @processing = false
68
+ # result.free if result.is_a? Mysql::Result
69
+ next_query
70
+ cblk.call(arg) if cblk
71
+ else
72
+ log 'readable, but nothing queued?! probably an ERROR state'
73
+ return close
74
+ end
75
+ rescue Mysql::Error => e
76
+ log 'mysql error', e.message
77
+ if e.message =~ /Deadlock/
78
+ @@queue << [response, sql, cblk, eblk]
79
+ @processing = false
80
+ next_query
81
+ elsif DisconnectErrors.include? e.message
82
+ @@queue << [response, sql, cblk, eblk]
83
+ return close
84
+ elsif cb = (eblk || @opts[:on_error])
85
+ cb.call(e)
86
+ @processing = false
87
+ next_query
88
+ else
89
+ raise e
90
+ end
91
+ # ensure
92
+ # res.free if res.is_a? Mysql::Result
93
+ # @processing = false
94
+ # next_query
95
+ end
96
+
97
+ def unbind
98
+ log 'mysql disconnect', $!
99
+ # cp = EventedMysql.instance_variable_get('@connection_pool') and cp.delete(self)
100
+ @connected = false
101
+
102
+ # XXX wait for the next tick until the current fd is removed completely from the reactor
103
+ #
104
+ # XXX in certain cases the new FD# (@mysql.socket) is the same as the old, since FDs are re-used
105
+ # XXX without next_tick in these cases, unbind will get fired on the newly attached signature as well
106
+ #
107
+ # XXX do _NOT_ use EM.next_tick here. if a bunch of sockets disconnect at the same time, we want
108
+ # XXX reconnects to happen after all the unbinds have been processed
109
+ EM.add_timer(0) do
110
+ log 'mysql reconnecting'
111
+ @processing = false
112
+ @mysql = EventedMysql._connect @opts
113
+ @fd = @mysql.socket
114
+
115
+ @signature = EM.attach_fd @mysql.socket, true
116
+ EM.set_notify_readable @signature, true
117
+ log 'mysql connected'
118
+ EM.instance_variable_get('@conns')[@signature] = self
119
+ @connected = true
120
+ make_socket_blocking
121
+ next_query
122
+ end
123
+ end
124
+
125
+ def execute sql, response = nil, cblk = nil, eblk = nil, &blk
126
+ cblk ||= blk
127
+
128
+ begin
129
+ unless @processing or !@connected
130
+ # begin
131
+ # log 'mysql ping', @mysql.ping
132
+ # # log 'mysql stat', @mysql.stat
133
+ # # log 'mysql errno', @mysql.errno
134
+ # rescue
135
+ # log 'mysql ping failed'
136
+ # @@queue << [response, sql, blk]
137
+ # return close
138
+ # end
139
+
140
+ @processing = true
141
+
142
+ log 'mysql sending', sql
143
+ @mysql.send_query(sql)
144
+ else
145
+ @@queue << [response, sql, cblk, eblk]
146
+ return
147
+ end
148
+ rescue Mysql::Error => e
149
+ log 'mysql error', e.message
150
+ if DisconnectErrors.include? e.message
151
+ @@queue << [response, sql, cblk, eblk]
152
+ return close
153
+ else
154
+ raise e
155
+ end
156
+ end
157
+
158
+ log 'queuing', response, sql
159
+ @current = [Time.now, response, sql, cblk, eblk]
160
+ end
161
+
162
+ def close
163
+ @connected = false
164
+ fd = detach
165
+ log 'detached fd', fd
166
+ end
167
+
168
+ private
169
+
170
+ def next_query
171
+ if @connected and !@processing and pending = @@queue.shift
172
+ response, sql, cblk, eblk = pending
173
+ execute(sql, response, cblk, eblk)
174
+ end
175
+ end
176
+
177
+ def log *args
178
+ return unless @opts[:logging]
179
+ p [Time.now, @fd, (@signature[-4..-1] if @signature), *args]
180
+ end
181
+
182
+ public
183
+
184
+ def self.connect opts
185
+ unless EM.respond_to?(:watch) and Mysql.method_defined?(:socket)
186
+ raise RuntimeError, 'mysqlplus and EM.watch are required for EventedMysql'
187
+ end
188
+
189
+ if conn = _connect(opts)
190
+ EM.watch conn.socket, self, conn, opts
191
+ else
192
+ EM.add_timer(5){ connect opts }
193
+ end
194
+ end
195
+
196
+ self::Mysql = ::Mysql unless defined? self::Mysql
197
+
198
+ # stolen from sequel
199
+ def self._connect opts
200
+ opts = settings.merge(opts)
201
+
202
+ conn = Mysql.init
203
+
204
+ # set encoding _before_ connecting
205
+ if charset = opts[:charset] || opts[:encoding]
206
+ conn.options(Mysql::SET_CHARSET_NAME, charset)
207
+ end
208
+
209
+ conn.options(Mysql::OPT_LOCAL_INFILE, 'client')
210
+
211
+ conn.real_connect(
212
+ opts[:host] || 'localhost',
213
+ opts[:user] || opts[:username] || 'root',
214
+ opts[:password],
215
+ opts[:database],
216
+ opts[:port],
217
+ opts[:socket],
218
+ 0 +
219
+ # XXX multi results require multiple callbacks to parse
220
+ # Mysql::CLIENT_MULTI_RESULTS +
221
+ # Mysql::CLIENT_MULTI_STATEMENTS +
222
+ (opts[:compress] == false ? 0 : Mysql::CLIENT_COMPRESS)
223
+ )
224
+
225
+ # increase timeout so mysql server doesn't disconnect us
226
+ # this is especially bad if we're disconnected while EM.attach is
227
+ # still in progress, because by the time it gets to EM, the FD is
228
+ # no longer valid, and it throws a c++ 'bad file descriptor' error
229
+ # (do not use a timeout of -1 for unlimited, it does not work on mysqld > 5.0.60)
230
+ conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}")
231
+
232
+ # we handle reconnecting (and reattaching the new fd to EM)
233
+ conn.reconnect = false
234
+
235
+ # By default, MySQL 'where id is null' selects the last inserted id
236
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
237
+ conn.query("set SQL_AUTO_IS_NULL=0")
238
+
239
+ # get results for queries
240
+ conn.query_with_result = true
241
+
242
+ conn
243
+ rescue Mysql::Error => e
244
+ if cb = opts[:on_error]
245
+ cb.call(e)
246
+ nil
247
+ else
248
+ raise e
249
+ end
250
+ end
251
+ end
252
+
253
+ class EventedMysql
254
+ def self.settings
255
+ @settings ||= { :connections => 4, :logging => false }
256
+ end
257
+
258
+ def self.execute query, type = nil, cblk = nil, eblk = nil, &blk
259
+ unless nil#connection = connection_pool.find{|c| not c.processing and c.connected }
260
+ @n ||= 0
261
+ connection = connection_pool[@n]
262
+ @n = 0 if (@n+=1) >= connection_pool.size
263
+ end
264
+
265
+ connection.execute(query, type, cblk, eblk, &blk)
266
+ end
267
+
268
+ %w[ select insert update delete raw ].each do |type| class_eval %[
269
+
270
+ def self.#{type} query, cblk = nil, eblk = nil, &blk
271
+ execute query, :#{type}, cblk, eblk, &blk
272
+ end
273
+
274
+ ] end
275
+
276
+ def self.all query, type = nil, &blk
277
+ responses = 0
278
+ connection_pool.each do |c|
279
+ c.execute(query, type) do
280
+ responses += 1
281
+ blk.call if blk and responses == @connection_pool.size
282
+ end
283
+ end
284
+ end
285
+
286
+ def self.connection_pool
287
+ @connection_pool ||= (1..settings[:connections]).map{ EventedMysql.connect(settings) }
288
+ # p ['connpool', settings[:connections], @connection_pool.size]
289
+ # (1..(settings[:connections]-@connection_pool.size)).each do
290
+ # @connection_pool << EventedMysql.connect(settings)
291
+ # end unless settings[:connections] == @connection_pool.size
292
+ # @connection_pool
293
+ end
294
+
295
+ def self.reset_connection_pool!
296
+ @connection_pool = nil
297
+ end
298
+ end
@@ -0,0 +1,33 @@
1
+ module Cramp
2
+ module Model
3
+ module Finders
4
+
5
+ def all
6
+ Relation.new(self, arel_table)
7
+ end
8
+
9
+ def first(&block)
10
+ Relation.new(self, arel_table).limit(1).each(&block)
11
+ end
12
+
13
+ def where(relation)
14
+ Relation.new(self, arel_table.where(relation))
15
+ end
16
+
17
+ def [](attribute)
18
+ arel_table[attribute]
19
+ end
20
+
21
+ def arel_table
22
+ @arel_table ||= Arel::Table.new(table_name)
23
+ end
24
+
25
+ private
26
+
27
+ def table_name
28
+ @table_name || self.to_s.pluralize
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,114 @@
1
+ # Yanked from Rails
2
+
3
+ # Copyright (c) 2004-2009 David Heinemeier Hansson
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module Cramp
25
+ module Model
26
+ module Quoting
27
+ def quote_column_name(name)
28
+ @quoted_column_names[name] ||= "`#{name}`"
29
+ end
30
+
31
+ def quote_table_name(name)
32
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
33
+ end
34
+
35
+ def quote(value, column = nil)
36
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
37
+ s = value.unpack("H*")[0]
38
+ "x'#{s}'"
39
+ elsif value.kind_of?(BigDecimal)
40
+ value.to_s("F")
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ def quote(value, column = nil)
47
+ # records are quoted as their primary key
48
+ return value.quoted_id if value.respond_to?(:quoted_id)
49
+
50
+ case value
51
+ when String, ActiveSupport::Multibyte::Chars
52
+ value = value.to_s
53
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
54
+ "#{quoted_string_prefix}'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
55
+ elsif column && [:integer, :float].include?(column.type)
56
+ value = column.type == :integer ? value.to_i : value.to_f
57
+ value.to_s
58
+ else
59
+ "#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode)
60
+ end
61
+ when NilClass then "NULL"
62
+ when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
63
+ when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
64
+ when Float, Fixnum, Bignum then value.to_s
65
+ # BigDecimals need to be output in a non-normalized form and quoted.
66
+ when BigDecimal then value.to_s('F')
67
+ else
68
+ if value.acts_like?(:date) || value.acts_like?(:time)
69
+ "'#{quoted_date(value)}'"
70
+ else
71
+ "#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'"
72
+ end
73
+ end
74
+ end
75
+
76
+ # Quotes a string, escaping any ' (single quote) and \ (backslash)
77
+ # characters.
78
+ def quote_string(s)
79
+ s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
80
+ end
81
+
82
+ # Quotes the column name. Defaults to no quoting.
83
+ def quote_column_name(column_name)
84
+ column_name
85
+ end
86
+
87
+ # Quotes the table name. Defaults to column name quoting.
88
+ def quote_table_name(table_name)
89
+ quote_column_name(table_name)
90
+ end
91
+
92
+ def quoted_true
93
+ "'t'"
94
+ end
95
+
96
+ def quoted_false
97
+ "'f'"
98
+ end
99
+
100
+ def quoted_date(value)
101
+ if value.acts_like?(:time)
102
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
103
+ value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
104
+ else
105
+ value
106
+ end.to_s(:db)
107
+ end
108
+
109
+ def quoted_string_prefix
110
+ ''
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,60 @@
1
+ module Cramp
2
+ module Model
3
+ class Relation
4
+
5
+ def initialize(klass, relation)
6
+ @klass, @relation = klass, relation
7
+ end
8
+
9
+ def each(&block)
10
+ @relation.each do |row|
11
+ object = @klass.instantiate(row)
12
+ block.call(object)
13
+ end
14
+ end
15
+
16
+ def all(&block)
17
+ @relation.all do |rows|
18
+ objects = rows.map {|r| @klass.instantiate(r) }
19
+ block.call(objects)
20
+ end
21
+ end
22
+
23
+ def first(&block)
24
+ @relation.first do |row|
25
+ object = row ? @klass.instantiate(row) : nil
26
+ block.call(object)
27
+ end
28
+ end
29
+
30
+ def select(selects)
31
+ Relation.new(@klass, @relation.project(selects))
32
+ end
33
+
34
+ def where(conditions)
35
+ Relation.new(@klass, @relation.where(conditions))
36
+ end
37
+
38
+ def select(selects)
39
+ Relation.new(@klass, @relation.project(selects))
40
+ end
41
+
42
+ def group(groups)
43
+ Relation.new(@klass, @relation.group(groups))
44
+ end
45
+
46
+ def order(orders)
47
+ Relation.new(@klass, @relation.order(orders))
48
+ end
49
+
50
+ def limit(limits)
51
+ Relation.new(@klass, @relation.take(limits))
52
+ end
53
+
54
+ def offset(offsets)
55
+ Relation.new(@klass, @relation.skip(offsets))
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ module Cramp
2
+ module Model
3
+ class Status
4
+
5
+ def initialize(record, success)
6
+ @record = record
7
+ @success = success
8
+ end
9
+
10
+ def success?
11
+ @success
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ require 'cramp/model/evented_mysql'
2
+ require 'cramp/model/emysql_ext'
3
+
4
+ require 'mysqlplus'
5
+
6
+ require 'arel'
7
+ require 'cramp/model/arel_monkey_patches'
8
+
9
+ require 'active_model'
10
+
11
+ module Cramp
12
+ module Model
13
+ autoload :Quoting, "cramp/model/quoting"
14
+ autoload :Engine, "cramp/model/engine"
15
+ autoload :Column, "cramp/model/column"
16
+ autoload :Relation, "cramp/model/relation"
17
+
18
+ autoload :Base, "cramp/model/base"
19
+ autoload :Finders, "cramp/model/finders"
20
+ autoload :Attribute, "cramp/model/attribute"
21
+ autoload :AttributeMethods, "cramp/model/attribute_methods"
22
+ autoload :Status, "cramp/model/status"
23
+
24
+ def self.init(settings)
25
+ Arel::Table.engine = Cramp::Model::Engine.new(settings)
26
+ end
27
+
28
+ end
29
+ end
30
+
data/lib/cramp.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'eventmachine'
2
+
3
+ require 'active_support'
4
+ require 'active_support/concern'
5
+ require 'active_support/core_ext'
6
+
7
+ module Cramp
8
+ VERSION = '0.1'
9
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cramp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pratik Naik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-23 00:00:00 +05:30
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 3.0.pre
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activemodel
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.pre
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: arel
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.2.pre
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: rack
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.0
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: mysqlplus
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: 0.1.1
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: eventmachine
67
+ type: :runtime
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ version: 0.1.1
74
+ version:
75
+ description: " Cramp provides ORM and controller layers for developing asynchronous web applications.\n"
76
+ email: pratiknaik@gmail.com
77
+ executables: []
78
+
79
+ extensions: []
80
+
81
+ extra_rdoc_files: []
82
+
83
+ files:
84
+ - README
85
+ - lib/cramp/controller/base.rb
86
+ - lib/cramp/controller/body.rb
87
+ - lib/cramp/controller.rb
88
+ - lib/cramp/model/arel_monkey_patches.rb
89
+ - lib/cramp/model/attribute.rb
90
+ - lib/cramp/model/attribute_methods.rb
91
+ - lib/cramp/model/base.rb
92
+ - lib/cramp/model/column.rb
93
+ - lib/cramp/model/emysql_ext.rb
94
+ - lib/cramp/model/engine.rb
95
+ - lib/cramp/model/evented_mysql.rb
96
+ - lib/cramp/model/finders.rb
97
+ - lib/cramp/model/quoting.rb
98
+ - lib/cramp/model/relation.rb
99
+ - lib/cramp/model/status.rb
100
+ - lib/cramp/model.rb
101
+ - lib/cramp.rb
102
+ has_rdoc: true
103
+ homepage: http://m.onkey.org
104
+ licenses: []
105
+
106
+ post_install_message:
107
+ rdoc_options: []
108
+
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: "0"
116
+ version:
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: "0"
122
+ version:
123
+ requirements: []
124
+
125
+ rubyforge_project:
126
+ rubygems_version: 1.3.5
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Async ORM and controller layer.
130
+ test_files: []
131
+