cramp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+