sentiql 0.1.2
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/.gitignore +6 -0
- data/Gemfile +4 -0
- data/Rakefile +52 -0
- data/config/database.yml +7 -0
- data/db/migrate/20110515_test_tables.rb +24 -0
- data/lib/sentiql/version.rb +3 -0
- data/lib/sentiql.rb +136 -0
- data/sentiql.gemspec +27 -0
- data/spec/sentiql_spec.rb +134 -0
- data/spec/spec_helper.rb +40 -0
- metadata +109 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'yaml'
|
3
|
+
require 'logger'
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
|
8
|
+
|
9
|
+
MIGRATIONS_DIR = 'db/migrate'
|
10
|
+
APP_PATH = File.join(File.dirname(__FILE__))
|
11
|
+
|
12
|
+
namespace :db do
|
13
|
+
|
14
|
+
task :environment do
|
15
|
+
ENV['RACK_ENV'] ||= 'development'
|
16
|
+
@config = YAML.load_file('config/database.yml')[ENV['RACK_ENV']]
|
17
|
+
ActiveRecord::Base.establish_connection @config
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Migrate the database (options: VERSION=x, VERBOSE=false).'
|
21
|
+
task :migrate => :environment do
|
22
|
+
ActiveRecord::Migration.verbose = true
|
23
|
+
ActiveRecord::Migrator.migrate MIGRATIONS_DIR, ENV['VERSION'] ? ENV['VERSION'].to_i : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
|
27
|
+
task :rollback => :configure_connection do
|
28
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
29
|
+
ActiveRecord::Migrator.rollback MIGRATIONS_DIR, step
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Retrieves the current schema version number"
|
33
|
+
task :version => :configure_connection do
|
34
|
+
puts "Current version: #{ActiveRecord::Migrator.current_version}"
|
35
|
+
end
|
36
|
+
|
37
|
+
namespace :test do
|
38
|
+
|
39
|
+
desc "Creates testing database by cloning main database"
|
40
|
+
task :load do
|
41
|
+
@config = YAML.load_file('config/database.yml')['test']
|
42
|
+
ActiveRecord::Base.establish_connection @config
|
43
|
+
tables = ActiveRecord::Base.connection.select_all("SHOW TABLES").map { |m| m.values.first }
|
44
|
+
tables.each do |table_name|
|
45
|
+
ActiveRecord::Base::connection.execute("DROP TABLE #{table_name}")
|
46
|
+
end
|
47
|
+
ActiveRecord::Migration.verbose = true
|
48
|
+
ActiveRecord::Migrator.migrate MIGRATIONS_DIR
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/config/database.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
class TestTables < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
create_table :users do |t|
|
5
|
+
t.string :name
|
6
|
+
t.string :full_name
|
7
|
+
t.string :email
|
8
|
+
t.string :crypted_password
|
9
|
+
t.string :salt
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :after_save do |t|
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.down
|
20
|
+
drop_table :users
|
21
|
+
drop_table :after_save
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/sentiql.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require "mysql2"
|
2
|
+
|
3
|
+
module SentiQL
|
4
|
+
|
5
|
+
class Base
|
6
|
+
attr_accessor :attrs, :errors
|
7
|
+
|
8
|
+
def initialize attrs={}
|
9
|
+
@attrs = {}
|
10
|
+
attrs.each_pair do |key, value|
|
11
|
+
@attrs[key.to_sym] = value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def [] key
|
16
|
+
@attrs ||= {}
|
17
|
+
@attrs[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(key, value)
|
21
|
+
@attrs ||= {}
|
22
|
+
@attrs[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def save
|
27
|
+
|
28
|
+
filter_with :before_save_filters
|
29
|
+
|
30
|
+
if @attrs[:id]
|
31
|
+
i = @attrs.select { |k| self.class.schema.include?(k) }
|
32
|
+
values = i.keys.map { |k| @attrs[k] }
|
33
|
+
values << @attrs[:id]
|
34
|
+
|
35
|
+
return self unless valid?
|
36
|
+
|
37
|
+
SentiQL::Base.execute "UPDATE #{self.class.table} SET #{i.keys.map{|m| "#{m.to_s}=?"}.join(",")} WHERE id=?", values
|
38
|
+
|
39
|
+
else
|
40
|
+
|
41
|
+
filter_with :before_create_filters
|
42
|
+
|
43
|
+
i = @attrs.select { |k| self.class.schema.include?(k) }
|
44
|
+
values = i.keys.map { |k| @attrs[k] }
|
45
|
+
|
46
|
+
return self unless valid?
|
47
|
+
|
48
|
+
id = SentiQL::Base.insert "INSERT INTO #{self.class.table} (#{i.keys.map{|k| k.to_s}.join(",")}) VALUES (#{i.map{|k| k="?"}.join(",")})", values
|
49
|
+
@attrs[:id] = id
|
50
|
+
|
51
|
+
filter_with :after_create_filters
|
52
|
+
end
|
53
|
+
|
54
|
+
filter_with :after_save_filters
|
55
|
+
|
56
|
+
return self
|
57
|
+
end
|
58
|
+
|
59
|
+
def filter_with filter
|
60
|
+
self.class.send(filter).each do |f|
|
61
|
+
self.send f
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing method_id, *args
|
66
|
+
if @attrs.has_key? method_id
|
67
|
+
return @attrs[method_id.to_sym]
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid?; true; end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
def connection; @@connection; end
|
77
|
+
def connection=(value); @@connection = value; end
|
78
|
+
|
79
|
+
def set_schema *args; @schema = args; end
|
80
|
+
def schema; @schema; end
|
81
|
+
|
82
|
+
def before_save *args; @before_save_filters = args; end
|
83
|
+
def before_save_filters; @before_save_filters ||={} ; end
|
84
|
+
def before_create *args; @before_create_filters = args; end
|
85
|
+
def before_create_filters; @before_create_filters ||={}; end
|
86
|
+
def after_create *args; @after_create_filters = args; end
|
87
|
+
def after_create_filters; @after_create_filters ||={}; end
|
88
|
+
def after_save *args; @after_save_filters= args; end
|
89
|
+
def after_save_filters; @after_save_filters ||={}; end
|
90
|
+
|
91
|
+
def set_table name; @table= name.to_s; end
|
92
|
+
def table; @table.to_sym; end
|
93
|
+
|
94
|
+
|
95
|
+
def create hash
|
96
|
+
obj = self.new hash
|
97
|
+
return obj.save
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_by hash
|
101
|
+
field_name = hash.keys.first.to_s
|
102
|
+
value = hash.values.first
|
103
|
+
r = first "SELECT * FROM #{self.table} WHERE #{field_name}=? LIMIT 1", [value]
|
104
|
+
return r ? self.new(r) : nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def first sql, args=[]
|
108
|
+
results = self.execute sql, args
|
109
|
+
return results.nil? ? nil : results.first
|
110
|
+
end
|
111
|
+
|
112
|
+
def insert sql, args=[]
|
113
|
+
execute sql, args
|
114
|
+
connection.last_id
|
115
|
+
end
|
116
|
+
|
117
|
+
def execute sql, args=[]
|
118
|
+
esced = args.map { |v| connection.escape(v.to_s) }
|
119
|
+
ix = 0
|
120
|
+
escq = sql.gsub(/\?/) do |m|
|
121
|
+
raise Mysql2::Error.new("Not enough arguments for prepared statement") if ix >= esced.count
|
122
|
+
val = esced[ix]
|
123
|
+
ix += 1
|
124
|
+
"'#{val}'"
|
125
|
+
end
|
126
|
+
raise Mysql2::Error.new("Too many arguments for prepared statement (required: #{ix}, passed: #{esced.count})") if ix < esced.count
|
127
|
+
return connection.query escq
|
128
|
+
end
|
129
|
+
|
130
|
+
alias :all :execute
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
data/sentiql.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "sentiql/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "sentiql"
|
7
|
+
s.version = SentiQL::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Martynas Miliauskas"]
|
10
|
+
s.email = ["miliauskas@facebook.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{A minimalistic Ruby wrapper for MySQL}
|
13
|
+
s.description = %q{This is a work in progress. SentiQL promotes use of SQL for data selection, OM for CUD operations and being as lean as possible.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "sentiql"
|
16
|
+
|
17
|
+
s.add_runtime_dependency 'mysql2', '< 0.3'
|
18
|
+
|
19
|
+
s.add_development_dependency 'rspec'
|
20
|
+
s.add_development_dependency 'database_cleaner'
|
21
|
+
s.add_development_dependency 'activerecord', '~> 3.0.5'
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class User < SentiQL::Base
|
5
|
+
|
6
|
+
set_schema :name, :full_name, :email, :crypted_password, :salt, :created_at, :updated_at
|
7
|
+
set_table :users
|
8
|
+
|
9
|
+
before_save :set_updated_at
|
10
|
+
before_create :set_created_at
|
11
|
+
after_create :touch_after_create
|
12
|
+
after_save :touch_after_save
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def count
|
17
|
+
r = self.first "SELECT count(*) AS count FROM #{table}"
|
18
|
+
r[:count]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def set_updated_at
|
26
|
+
self[:updated_at] = Time.now
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_created_at
|
30
|
+
self[:created_at] = Time.now
|
31
|
+
end
|
32
|
+
|
33
|
+
def touch_after_create
|
34
|
+
self[:after_create_touched] = Time.now
|
35
|
+
end
|
36
|
+
|
37
|
+
def touch_after_save
|
38
|
+
self[:after_save_touched] = Time.now
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
describe SentiQL::Base do
|
45
|
+
|
46
|
+
describe User do
|
47
|
+
|
48
|
+
describe '.create' do
|
49
|
+
it 'creates a new record in DB' do
|
50
|
+
lambda { User.create :name=>'Natalie' }.should change(User, :count).by(1)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns and instance of User' do
|
54
|
+
u = User.create :name=>'Natalie'
|
55
|
+
u.should be_an_instance_of(User)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'sets :id of record in DB to an instance' do
|
59
|
+
u = User.create :name=>'Natalie'
|
60
|
+
u[:id].should_not be_nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '.save' do
|
65
|
+
it "creates new record if instance is new" do
|
66
|
+
u = User.new :name=>'Natalie'
|
67
|
+
lambda{ u.save }.should change(User, :count).by(1)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "updates existing record if instance was already created" do
|
71
|
+
u = User.new :name=>'Natalie'
|
72
|
+
u.save
|
73
|
+
u[:full_name] = 'Natalie Portman'
|
74
|
+
lambda { u.save }.should_not change(User, :count)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'filters' do
|
79
|
+
|
80
|
+
before do
|
81
|
+
end
|
82
|
+
|
83
|
+
it "executes before_save every time instance is saved" do
|
84
|
+
u = User.new :name=>'Natalie'
|
85
|
+
u.save
|
86
|
+
sleep(1)
|
87
|
+
lambda { u.save }.should change(u, :updated_at)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "executes before_create filters only when record is created" do
|
91
|
+
u = User.new :name=>'Natalie'
|
92
|
+
u.save
|
93
|
+
sleep(1)
|
94
|
+
lambda { u.save }.should_not change(u, :created_at)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "executes after_create filters only after record is created" do
|
98
|
+
u = User.new :name=>'Natalie'
|
99
|
+
u.save
|
100
|
+
lambda { u.save }.should_not change(u, :after_create_touched)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "executes after_save filters after everytime instance is saved" do
|
104
|
+
u = User.new :name=>'Natalie'
|
105
|
+
u.save
|
106
|
+
lambda { u.save }.should change(u, :after_save_touched)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '.first' do
|
113
|
+
it 'returns nil when no records found' do
|
114
|
+
r = SentiQL.first 'SHOW TABLES LIKE ?', ['non_existing_tbl%']
|
115
|
+
r.should == nil
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'returns a hash if record found with keys representing column values' do
|
119
|
+
r = SentiQL.first 'SELECT CONCAT(?,?) AS str', ['a','b']
|
120
|
+
r.should be_an_instance_of(Hash)
|
121
|
+
r[:str].should == 'ab'
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '.all' do
|
126
|
+
it 'returns Mysql2::Result which holds data in Hashes' do
|
127
|
+
r = SentiQL.all 'SHOW OPEN TABLES'
|
128
|
+
r.should be_an_instance_of(Mysql2::Result)
|
129
|
+
r.first.should be_an_instance_of(Hash)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'database_cleaner'
|
2
|
+
require 'yaml'
|
3
|
+
require 'rspec'
|
4
|
+
require 'active_record'
|
5
|
+
require 'mysql2'
|
6
|
+
|
7
|
+
require 'sentiql'
|
8
|
+
|
9
|
+
ROOT_PATH = File.dirname(__FILE__)
|
10
|
+
|
11
|
+
|
12
|
+
@config = YAML.load_file(File.join(ROOT_PATH, '..', "config/database.yml"))['test']
|
13
|
+
|
14
|
+
DB = Mysql2::Client.new(
|
15
|
+
:host=>@config["host"],
|
16
|
+
:username=>@config["username"],
|
17
|
+
:password=>@config["password"],
|
18
|
+
:database=>@config["database"]
|
19
|
+
)
|
20
|
+
DB.query_options.merge!(:symbolize_keys=>true)
|
21
|
+
SentiQL::Base.connection = DB
|
22
|
+
|
23
|
+
ActiveRecord::Base.establish_connection @config
|
24
|
+
|
25
|
+
RSpec.configure do |config|
|
26
|
+
|
27
|
+
config.before(:suite) do
|
28
|
+
DatabaseCleaner.strategy = :transaction
|
29
|
+
DatabaseCleaner.clean_with(:truncation)
|
30
|
+
end
|
31
|
+
|
32
|
+
config.before(:each) do
|
33
|
+
DatabaseCleaner.start
|
34
|
+
end
|
35
|
+
|
36
|
+
config.after(:each) do
|
37
|
+
DatabaseCleaner.clean
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sentiql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Martynas Miliauskas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-15 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: mysql2
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - <
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0.3"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: database_cleaner
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: activerecord
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 3.0.5
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id004
|
60
|
+
description: This is a work in progress. SentiQL promotes use of SQL for data selection, OM for CUD operations and being as lean as possible.
|
61
|
+
email:
|
62
|
+
- miliauskas@facebook.com
|
63
|
+
executables: []
|
64
|
+
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
extra_rdoc_files: []
|
68
|
+
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- Gemfile
|
72
|
+
- Rakefile
|
73
|
+
- config/database.yml
|
74
|
+
- db/migrate/20110515_test_tables.rb
|
75
|
+
- lib/sentiql.rb
|
76
|
+
- lib/sentiql/version.rb
|
77
|
+
- sentiql.gemspec
|
78
|
+
- spec/sentiql_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
has_rdoc: true
|
81
|
+
homepage: ""
|
82
|
+
licenses: []
|
83
|
+
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project: sentiql
|
104
|
+
rubygems_version: 1.6.2
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: A minimalistic Ruby wrapper for MySQL
|
108
|
+
test_files: []
|
109
|
+
|