Skip to content

Commit

Permalink
Moving files from cramp and updating module names
Browse files Browse the repository at this point in the history
  • Loading branch information
lifo committed Jul 17, 2010
1 parent 8c958e0 commit 41f41f0
Show file tree
Hide file tree
Showing 19 changed files with 1,210 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1 @@
.bundle
10 changes: 10 additions & 0 deletions Gemfile
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,10 @@
source 'http://rubygems.org'

gem "tramp", :path => File.dirname(__FILE__)

gem 'activesupport', '3.0.0.beta4'
gem 'activemodel', '3.0.0.beta4'

gem "arel", '>= 0.4.0'
gem "mysqlplus", "0.1.1"
gem "eventmachine", "0.12.10"
20 changes: 20 additions & 0 deletions MIT-LICENSE
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2009-2010 Pratik Naik

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.
31 changes: 31 additions & 0 deletions examples/orm.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,31 @@
require "rubygems"
require "bundler"
Bundler.setup

require 'tramp'

Tramp.init(:username => 'root', :database => 'arel_development')

class User < Tramp::Base
attribute :id, :type => Integer, :primary_key => true
attribute :name

validates_presence_of :name
end

EM.run do
user = User.new

user.save do |status|
if status.success?
puts "WTF!"
else
puts "Oops. Found errors : #{user.errors.inspect}"

user.name = 'Lush'
user.save do
User.where(User[:name].eq('Lush')).all {|users| puts users.inspect; EM.stop }
end
end
end
end
49 changes: 49 additions & 0 deletions lib/tramp.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'eventmachine'
EM.epoll

require 'active_support'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/buffered_logger'

require 'tramp/evented_mysql'
require 'tramp/emysql_ext'
require 'mysqlplus'
require 'arel'
require 'tramp/arel_monkey_patches'
require 'active_model'

module Tramp
VERSION = '0.1'

mattr_accessor :logger

autoload :Quoting, "tramp/quoting"
autoload :Engine, "tramp/engine"
autoload :Column, "tramp/column"
autoload :Relation, "tramp/relation"

autoload :Base, "tramp/base"
autoload :Finders, "tramp/finders"
autoload :Attribute, "tramp/attribute"
autoload :AttributeMethods, "tramp/attribute_methods"
autoload :Status, "tramp/status"
autoload :Callbacks, "tramp/callbacks"

def self.init(settings)
Arel::Table.engine = Tramp::Engine.new(settings)
end

def self.select(query, callback = nil, &block)
callback ||= block

EventedMysql.select(query) do |rows|
callback.arity == 1 ? callback.call(rows) : callback.call if callback
end
end
end
66 changes: 66 additions & 0 deletions lib/tramp/arel_monkey_patches.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,66 @@
class Arel::Session
def create(insert, &block)
insert.call(&block)
end

def read(select, &block)
select.call(&block)
end

def update(update, &block)
update.call(&block)
end

def delete(delete, &block)
delete.call(&block)
end

end

module Arel::Relation
def call(&block)
engine.read(self, &block)
end

def all(&block)
session.read(self) {|rows| block.call(rows) }
end

def first(&block)
session.read(self) {|rows| block.call(rows[0]) }
end

def each(&block)
session.read(self) {|rows| rows.each {|r| block.call(r) } }
end

def insert(record, &block)
session.create(Arel::Insert.new(self, record), &block)
end

def update(assignments, &block)
session.update(Arel::Update.new(self, assignments), &block)
end

def delete(&block)
session.delete(Arel::Deletion.new(self), &block)
end
end

class Arel::Deletion
def call(&block)
engine.delete(self, &block)
end
end

class Arel::Insert
def call(&block)
engine.create(self, &block)
end
end

class Arel::Update
def call(&block)
engine.update(self, &block)
end
end
101 changes: 101 additions & 0 deletions lib/tramp/attribute.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright (c) 2009 Koziarski Software Ltd
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

module Tramp
class Attribute

FORMATS = {}
FORMATS[Date] = /^\d{4}\/\d{2}\/\d{2}$/
FORMATS[Integer] = /^-?\d+$/
FORMATS[Float] = /^-?\d*\.\d*$/
FORMATS[Time] = /\A\s*
-?\d+-\d\d-\d\d
T
\d\d:\d\d:\d\d
(\.\d*)?
(Z|[+-]\d\d:\d\d)?
\s*\z/ix # lifted from the implementation of Time.xmlschema

CONVERTERS = {}
CONVERTERS[Date] = Proc.new do |str|
Date.strptime(str, "%Y/%m/%d")
end

CONVERTERS[Integer] = Proc.new do |str|
Integer(str)
end

CONVERTERS[Float] = Proc.new do |str|
Float(str)
end

CONVERTERS[Time] = Proc.new do |str|
Time.xmlschema(str)
end

attr_reader :name
def initialize(name, owner_class, options)
@name = name.to_s
@owner_class = owner_class
@options = options

# append_validations!
define_methods!
end

# I think this should live somewhere in Amo
def check_value!(value)
# Allow nil and Strings to fall back on the validations for typecasting
# Everything else gets checked with is_a?
if value.nil?
nil
elsif value.is_a?(String)
value
elsif value.is_a?(expected_type)
value
else
raise TypeError, "Expected #{expected_type.inspect} but got #{value.inspect}"
end
end

def expected_type
@options[:type] || String
end

def type_cast(value)
if value.is_a?(expected_type)
value
elsif (converter = CONVERTERS[expected_type]) && (value =~ FORMATS[expected_type])
converter.call(value)
else
value
end
end

def append_validations!
if f = FORMATS[expected_type]
@owner_class.validates_format_of @name, :with => f, :unless => lambda {|obj| obj.send(name).is_a? expected_type }, :allow_nil => @options[:allow_nil]
end
end

def define_methods!
@owner_class.define_attribute_methods(true)
end

def primary_key?
@options[:primary_key]
end

end
end
81 changes: 81 additions & 0 deletions lib/tramp/attribute_methods.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright (c) 2009 Koziarski Software Ltd
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

module Tramp
module AttributeMethods

extend ActiveSupport::Concern
include ActiveModel::AttributeMethods

module ClassMethods
def attribute(name, options = {})
write_inheritable_hash(:model_attributes, {name => Attribute.new(name, self, options)})
end

def define_attribute_methods(force = false)
return unless model_attributes
undefine_attribute_methods if force
super(model_attributes.keys)
end
end

included do
class_inheritable_hash :model_attributes
undef_method(:id) if method_defined?(:id)

attribute_method_suffix("", "=")
end

def write_attribute(name, value)
if ma = self.class.model_attributes[name.to_sym]
value = ma.check_value!(value)
end
if(@attributes[name] != value)
send "#{name}_will_change!".to_sym
@attributes[name] = value
end
end

def read_attribute(name)
if ma = self.class.model_attributes[name]
ma.type_cast(@attributes[name])
else
@attributes[name]
end
end

def attributes=(attributes)
attributes.each do |(name, value)|
send("#{name}=", value)
end
end

protected

def attribute_method?(name)
@attributes.include?(name.to_sym) || model_attributes[name.to_sym]
end

private

def attribute(name)
read_attribute(name.to_sym)
end

def attribute=(name, value)
write_attribute(name.to_sym, value)
end

end
end
Loading

9 comments on commit 41f41f0

@igrigorik
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really recommend you look at mysql2 EM module - much better and more modern API. EventedMySQL is great, but it's a bit of a hack.

@lifo
Copy link
Owner Author

@lifo lifo commented on 41f41f0 Jul 18, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was contemplating doing that. But now with 1.9 + Fibers + em_synchrony, I'm not too sure if we even need a separate async ORM. And that was one of the big reasons I moved it out of Cramp.

@igrigorik
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the fiber stuff would be layered on top (em-synchrony), but you still need to pick an underlying async driver. I've made a fork (em-mysqlplus) of aman's original code and cleaned it up a bit, but mysql2 is probably the better bet at this point (they have an em driver inside). Now just need to build a small wrapper for the mysql2 em driver to work with synchrony, and you should be good to go.

@lifo
Copy link
Owner Author

@lifo lifo commented on 41f41f0 Jul 18, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I understand the technical details. I'm just not sure if we need a special async ORM now that you can just use AR asynchronously.

@lifo
Copy link
Owner Author

@lifo lifo commented on 41f41f0 Jul 18, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or am I missing something ?

@tobi
Copy link

@tobi tobi commented on 41f41f0 Jul 18, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the mysql2 gem will give you a lot of stuff for free. The main thing is that you don't have to do type casting anymore because that's done in C for you and that ends up being a tremendous performance win for larger result sets. That's just one of many reasons on why you want to use it.

@brianmario
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also FYI - mperham recently contributed a fibered AR adapter that shiped with mysql2 0.1.9

@lifo
Copy link
Owner Author

@lifo lifo commented on 41f41f0 Jul 18, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're all on the same page here :) I love mysql2 gem. And as Brian pointed out, it already has AR adapter. Because of the same, I see tramp as a dead library - you should just use AR.

@igrigorik
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes.. now I'm with you - behind on caffeine today. Yes, you're right, you can just use straight up AR, just swap in mysql2 as the driver name in the config.

On a different, but related note.. Tobi, to your comments about type casting: I've done some tests on the gem, and while the base API is faster across the board, I didn't find the casting to be of any noticeable difference, with only exception: Date class. Parsing dates in C is vs using Date.parse, or even Time.parse makes a huge difference. So, when you run the mixed benchmarks, the difference you see is all basically due to that difference.

Please sign in to comment.