active_illusion 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +47 -0
- data/README.md +47 -0
- data/active_illusion.gemspec +15 -0
- data/lib/active_illusion.rb +99 -0
- data/spec/illusion_spec.rb +122 -0
- metadata +83 -0
data/.gitignore
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
.bundle
|
2
|
+
db/*.sqlite3
|
3
|
+
log/*.log
|
4
|
+
tmp/
|
5
|
+
.*.sw*
|
6
|
+
*.orig
|
7
|
+
.swp
|
8
|
+
public/system
|
9
|
+
*.*.BACKUP.*.lock
|
10
|
+
*.*.BASE.*.lock
|
11
|
+
*.*.REMOTE.*.lock
|
12
|
+
*.*.LOCAL.*.lock
|
13
|
+
.DS_Store
|
14
|
+
*.*.gz
|
15
|
+
Session.vim
|
16
|
+
err.txt
|
17
|
+
.sass-cache
|
18
|
+
public/stylesheets/*.css
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
active_illusion (0.0.1)
|
5
|
+
activerecord (>= 3.1.0.rc4)
|
6
|
+
squeel (>= 0.8.5)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activemodel (3.1.0.rc5)
|
12
|
+
activesupport (= 3.1.0.rc5)
|
13
|
+
bcrypt-ruby (~> 2.1.4)
|
14
|
+
builder (~> 3.0.0)
|
15
|
+
i18n (~> 0.6)
|
16
|
+
activerecord (3.1.0.rc5)
|
17
|
+
activemodel (= 3.1.0.rc5)
|
18
|
+
activesupport (= 3.1.0.rc5)
|
19
|
+
arel (~> 2.1.4)
|
20
|
+
tzinfo (~> 0.3.29)
|
21
|
+
activesupport (3.1.0.rc5)
|
22
|
+
multi_json (~> 1.0)
|
23
|
+
arel (2.1.4)
|
24
|
+
bcrypt-ruby (2.1.4)
|
25
|
+
builder (3.0.0)
|
26
|
+
diff-lcs (1.1.2)
|
27
|
+
i18n (0.6.0)
|
28
|
+
multi_json (1.0.3)
|
29
|
+
rspec (2.6.0)
|
30
|
+
rspec-core (~> 2.6.0)
|
31
|
+
rspec-expectations (~> 2.6.0)
|
32
|
+
rspec-mocks (~> 2.6.0)
|
33
|
+
rspec-core (2.6.4)
|
34
|
+
rspec-expectations (2.6.0)
|
35
|
+
diff-lcs (~> 1.1.2)
|
36
|
+
rspec-mocks (2.6.0)
|
37
|
+
squeel (0.8.6)
|
38
|
+
activerecord (~> 3.0)
|
39
|
+
activesupport (~> 3.0)
|
40
|
+
tzinfo (0.3.29)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
active_illusion!
|
47
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
Inherit from this class to create tableless models that are backed by a query. Behaves
|
2
|
+
a little like a classic SQL view.
|
3
|
+
|
4
|
+
For example
|
5
|
+
|
6
|
+
class Award < ActiveRecord::Base
|
7
|
+
has_many :award_type_view
|
8
|
+
end
|
9
|
+
|
10
|
+
class AwardTypeView < ActiveRecord::Illusion
|
11
|
+
column :award_type, :string
|
12
|
+
column :award_id, :integer
|
13
|
+
|
14
|
+
belongs_to :award
|
15
|
+
|
16
|
+
view
|
17
|
+
Award.select{
|
18
|
+
[awards.type.as(award_type), awards.id.as(award_id)]
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
The cool thing is, is that the relations work on both directions
|
25
|
+
|
26
|
+
a = AwardTypeView.first
|
27
|
+
|
28
|
+
SELECT "award_type_views".* FROM
|
29
|
+
(SELECT "awards"."type" AS award_type, "awards"."id" AS award_id FROM awards )
|
30
|
+
award_type_views LIMIT 1
|
31
|
+
|
32
|
+
a.award
|
33
|
+
|
34
|
+
SELECT "awards".* FROM "awards" WHERE "awards"."id" = 1 LIMIT 1
|
35
|
+
|
36
|
+
b = Award.first
|
37
|
+
|
38
|
+
SELECT "awards".* FROM "awards" LIMIT 1
|
39
|
+
|
40
|
+
b.award_type_views
|
41
|
+
|
42
|
+
SELECT "award_type_views".* FROM
|
43
|
+
(SELECT "awards"."type" AS award_type, "awards"."id" AS award_id FROM awards )
|
44
|
+
award_type_views
|
45
|
+
WHERE "award_type_views"."award_id" = 1
|
46
|
+
|
47
|
+
Currently this gem depends on the SQUEEL SQL gem
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Provide a simple gemspec so you can easily use your
|
2
|
+
# project in your rails apps through git.
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "active_illusion"
|
5
|
+
s.summary = "Create ActiveRecord models backed only by an SQL query NOT a table."
|
6
|
+
s.description = ""
|
7
|
+
s.files = `git ls-files`.split "\n"
|
8
|
+
s.authors = ["Brad Phelan"]
|
9
|
+
s.email = "bradphelan@xtargets.com"
|
10
|
+
s.homepage = "https://github.com/bradphelan/Active-Illusion"
|
11
|
+
s.version = "0.0.1"
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.add_dependency 'activerecord', '>= 3.1.0.rc4'
|
14
|
+
s.add_dependency 'squeel', '>= 0.8.5'
|
15
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support/all'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
# requires the squeel gem
|
6
|
+
#
|
7
|
+
# https://github.com/ernie/squeel
|
8
|
+
#
|
9
|
+
# Inherit from this class to create tableless
|
10
|
+
# models that are backed by a query.
|
11
|
+
#
|
12
|
+
# For example
|
13
|
+
#
|
14
|
+
# class Award < ActiveRecord::Base
|
15
|
+
# has_many :award_type_view
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# class AwardTypeView < Illusion
|
19
|
+
# column :award_type, :string
|
20
|
+
# column :award_id, :integer
|
21
|
+
#
|
22
|
+
# belongs_to :award
|
23
|
+
#
|
24
|
+
# view
|
25
|
+
# select{[awards.type.as(award_type), awards.id.as(award_id)]}.from("awards")
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# The cool thing is, is that the relations work on both directions
|
30
|
+
#
|
31
|
+
# a = AwardTypeView.first
|
32
|
+
#
|
33
|
+
# SELECT "award_type_views".* FROM
|
34
|
+
# (SELECT "awards"."type" AS award_type, "awards"."id" AS award_id FROM awards )
|
35
|
+
# award_type_views LIMIT 1
|
36
|
+
#
|
37
|
+
# a.award
|
38
|
+
#
|
39
|
+
# SELECT "awards".* FROM "awards" WHERE "awards"."id" = 1 LIMIT 1
|
40
|
+
#
|
41
|
+
# b = Award.first
|
42
|
+
#
|
43
|
+
# SELECT "awards".* FROM "awards" LIMIT 1
|
44
|
+
#
|
45
|
+
# b.award_type_views
|
46
|
+
#
|
47
|
+
# SELECT "award_type_views".* FROM
|
48
|
+
# (SELECT "awards"."type" AS award_type, "awards"."id" AS award_id FROM awards )
|
49
|
+
# award_type_views
|
50
|
+
# WHERE "award_type_views"."award_id" = 1
|
51
|
+
|
52
|
+
# end
|
53
|
+
class Illusion < ActiveRecord::Base
|
54
|
+
def self.columns()
|
55
|
+
@columns ||= []
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.columns_hash()
|
59
|
+
@columns_hash ||= {}
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.find_all
|
63
|
+
raise "please override"
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
def default_scope_with_wrap
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.view
|
72
|
+
meta = class << self;self;end
|
73
|
+
m = yield
|
74
|
+
table = self.to_s.underscore.pluralize
|
75
|
+
meta.send :define_method, :default_scope do
|
76
|
+
q = m.arel.as table
|
77
|
+
select{}.from(q)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.column(name, sql_type = :string, default = nil, null = true)
|
82
|
+
column = ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
|
83
|
+
|
84
|
+
columns << column
|
85
|
+
columns_hash[name.to_s] = column
|
86
|
+
end
|
87
|
+
|
88
|
+
self.abstract_class = true
|
89
|
+
|
90
|
+
def self.table_name
|
91
|
+
to_s.underscore.pluralize
|
92
|
+
end
|
93
|
+
|
94
|
+
def readonly?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_illusion'
|
3
|
+
require 'squeel'
|
4
|
+
|
5
|
+
TIMES = (ENV['N'] || 10000).to_i
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require "active_record"
|
9
|
+
|
10
|
+
conn = { :adapter => 'sqlite3', :database => ':memory:' }
|
11
|
+
ActiveRecord::Base.establish_connection(conn)
|
12
|
+
|
13
|
+
class User < ActiveRecord::Base
|
14
|
+
connection.create_table :users, :force => true do |t|
|
15
|
+
t.string :name, :email
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
has_many :exhibits
|
20
|
+
end
|
21
|
+
|
22
|
+
class Exhibit < ActiveRecord::Base
|
23
|
+
connection.create_table :exhibits, :force => true do |t|
|
24
|
+
t.belongs_to :user
|
25
|
+
t.string :name
|
26
|
+
t.text :notes
|
27
|
+
t.integer :ssn
|
28
|
+
t.timestamps
|
29
|
+
end
|
30
|
+
|
31
|
+
belongs_to :user
|
32
|
+
|
33
|
+
def look; attributes end
|
34
|
+
def feel; look; user.name end
|
35
|
+
|
36
|
+
def self.look(exhibits) exhibits.each { |e| e.look } end
|
37
|
+
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
38
|
+
end
|
39
|
+
|
40
|
+
module ActiveRecord
|
41
|
+
class Faker
|
42
|
+
LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. Praesent varius tincidunt commodo".split
|
43
|
+
def self.name
|
44
|
+
LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' '
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.email
|
48
|
+
LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join('@') + ".com"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Test0 < ActiveRecord::Illusion
|
54
|
+
column :name
|
55
|
+
column :exhibition
|
56
|
+
column :number
|
57
|
+
#belongs_to :user, :foreign_key => :name
|
58
|
+
|
59
|
+
view do
|
60
|
+
User.joins{exhibits}.select{
|
61
|
+
[users.name.as(name), exhibits.name.as(xname), exhibits.ssn.as(number) ]
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# pre-compute the insert statements and fake data compilation,
|
67
|
+
# so the benchmarks below show the actual runtime for the execute
|
68
|
+
# method, minus the setup steps
|
69
|
+
|
70
|
+
# Using the same paragraph for all exhibits because it is very slow
|
71
|
+
# to generate unique paragraphs for all exhibits.
|
72
|
+
notes = ActiveRecord::Faker::LOREM.join ' '
|
73
|
+
today = Date.today
|
74
|
+
|
75
|
+
|
76
|
+
describe ActiveRecord::Illusion do
|
77
|
+
before :each do
|
78
|
+
|
79
|
+
User.destroy_all
|
80
|
+
|
81
|
+
Exhibit.destroy_all
|
82
|
+
|
83
|
+
puts 'Inserting 100 users and exhibits...'
|
84
|
+
100.times do |i|
|
85
|
+
user = User.create(
|
86
|
+
:created_at => today,
|
87
|
+
:name => ActiveRecord::Faker.name,
|
88
|
+
:email => ActiveRecord::Faker.email
|
89
|
+
)
|
90
|
+
|
91
|
+
Exhibit.create(
|
92
|
+
:created_at => today,
|
93
|
+
:name => ActiveRecord::Faker.name,
|
94
|
+
:user => user,
|
95
|
+
:notes => notes,
|
96
|
+
:ssn => i
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should have 100 users" do
|
103
|
+
User.count.should == 100
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should have 100 exhibits" do
|
107
|
+
Exhibit.count.should == 100
|
108
|
+
end
|
109
|
+
|
110
|
+
describe Test0 do
|
111
|
+
it "should retrieve 100 rows" do
|
112
|
+
Test0.where{}.to_sql.should ==
|
113
|
+
%Q[SELECT \"test0s\".* FROM (SELECT \"users\".\"name\" AS name, \"exhibits\".\"name\" AS xname, \"exhibits\".\"ssn\" AS number FROM \"users\" INNER JOIN \"exhibits\" ON \"exhibits\".\"user_id\" = \"users\".\"id\") test0s ]
|
114
|
+
Test0.count.should == 100
|
115
|
+
|
116
|
+
|
117
|
+
Test0.where{number < 20}.to_sql.should ==
|
118
|
+
%Q[SELECT "test0s".* FROM (SELECT "users"."name" AS name, "exhibits"."name" AS xname, "exhibits"."ssn" AS number FROM "users" INNER JOIN "exhibits" ON "exhibits"."user_id" = "users"."id") test0s WHERE "test0s"."number" < 20]
|
119
|
+
Test0.where{number < 20}.count.should == 20
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_illusion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brad Phelan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-05 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: activerecord
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 3.1.0.rc4
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: squeel
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.8.5
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
description: ""
|
39
|
+
email: bradphelan@xtargets.com
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- Gemfile
|
49
|
+
- Gemfile.lock
|
50
|
+
- README.md
|
51
|
+
- active_illusion.gemspec
|
52
|
+
- lib/active_illusion.rb
|
53
|
+
- spec/illusion_spec.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: https://github.com/bradphelan/Active-Illusion
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.6.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Create ActiveRecord models backed only by an SQL query NOT a table.
|
82
|
+
test_files: []
|
83
|
+
|