vex 0.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/Manifest +112 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/config/README +2 -0
- data/config/dependencies.rb +10 -0
- data/config/gem.yml +7 -0
- data/init.rb +36 -0
- data/lib/nokogiri/nokogiri_ext.rb +96 -0
- data/lib/vex.rb +5 -0
- data/lib/vex/action_controller.rb +4 -0
- data/lib/vex/action_controller/verify_action.rb +97 -0
- data/lib/vex/action_controller/whitelisted_actions.rb +45 -0
- data/lib/vex/active_record.rb +3 -0
- data/lib/vex/active_record/__init__.rb +0 -0
- data/lib/vex/active_record/advisory_lock.rb +11 -0
- data/lib/vex/active_record/advisory_lock/mysql_adapter.rb +16 -0
- data/lib/vex/active_record/advisory_lock/sqlite_adapter.rb +78 -0
- data/lib/vex/active_record/belongs_to_many.rb +143 -0
- data/lib/vex/active_record/find_by_extension.rb +70 -0
- data/lib/vex/active_record/gem.rb +8 -0
- data/lib/vex/active_record/lite_table.rb +139 -0
- data/lib/vex/active_record/lite_view.rb +140 -0
- data/lib/vex/active_record/mass_load.rb +65 -0
- data/lib/vex/active_record/mysql_backup.rb +21 -0
- data/lib/vex/active_record/plugins/default_value_for/LICENSE.TXT +19 -0
- data/lib/vex/active_record/plugins/default_value_for/README.rdoc +421 -0
- data/lib/vex/active_record/plugins/default_value_for/Rakefile +6 -0
- data/lib/vex/active_record/plugins/default_value_for/init.rb +91 -0
- data/lib/vex/active_record/plugins/default_value_for/test.rb +279 -0
- data/lib/vex/active_record/plugins/default_value_for/test.sqlite3 +0 -0
- data/lib/vex/active_record/random_id.rb +56 -0
- data/lib/vex/active_record/resolver.rb +49 -0
- data/lib/vex/active_record/serialize_hash.rb +125 -0
- data/lib/vex/active_record/to_html.rb +53 -0
- data/lib/vex/active_record/validate.rb +76 -0
- data/lib/vex/active_record/validation_error_ext.rb +68 -0
- data/lib/vex/base.rb +2 -0
- data/lib/vex/base/app.rb +75 -0
- data/lib/vex/base/array/at_random.rb +17 -0
- data/lib/vex/base/array/cross.rb +26 -0
- data/lib/vex/base/array/each_batch.rb +32 -0
- data/lib/vex/base/array/parallel_map.rb +98 -0
- data/lib/vex/base/deprecation.rb +41 -0
- data/lib/vex/base/enumerable/deep.rb +95 -0
- data/lib/vex/base/enumerable/enumerable_ext.rb +59 -0
- data/lib/vex/base/enumerable/progress.rb +71 -0
- data/lib/vex/base/filesystem/fast_copy.rb +61 -0
- data/lib/vex/base/filesystem/grep.rb +34 -0
- data/lib/vex/base/filesystem/lock.rb +43 -0
- data/lib/vex/base/filesystem/lock.rb.test.lck +0 -0
- data/lib/vex/base/filesystem/lock.rb.test.pid +1 -0
- data/lib/vex/base/filesystem/make_dirs.rb +94 -0
- data/lib/vex/base/filesystem/parse_filename.rb +36 -0
- data/lib/vex/base/filesystem/tmp_file.rb +87 -0
- data/lib/vex/base/filesystem/write.rb +43 -0
- data/lib/vex/base/hash/compact.rb +38 -0
- data/lib/vex/base/hash/cross.rb +117 -0
- data/lib/vex/base/hash/easy_access.rb +141 -0
- data/lib/vex/base/hash/ensure_keys.rb +18 -0
- data/lib/vex/base/hash/extract.rb +71 -0
- data/lib/vex/base/hash/extras.rb +62 -0
- data/lib/vex/base/hash/inspect.rb +17 -0
- data/lib/vex/base/hash/simple_access_methods.rb +74 -0
- data/lib/vex/base/invalid_argument/invalid_argument.rb +97 -0
- data/lib/vex/base/local_conf.rb +35 -0
- data/lib/vex/base/net/http_ext.rb +227 -0
- data/lib/vex/base/net/socket_ext.rb +43 -0
- data/lib/vex/base/object/insp.rb +123 -0
- data/lib/vex/base/object/multiple_attributes.rb +58 -0
- data/lib/vex/base/object/singleton_methods.rb +23 -0
- data/lib/vex/base/object/with_benchmark.rb +110 -0
- data/lib/vex/base/range_array.rb +40 -0
- data/lib/vex/base/range_ext.rb +28 -0
- data/lib/vex/base/safe_token.rb +156 -0
- data/lib/vex/base/string/string_ext.rb +136 -0
- data/lib/vex/base/thread/deferred.rb +52 -0
- data/lib/vex/base/thread/sleep.rb +11 -0
- data/lib/vex/base/time/date_ext.rb +12 -0
- data/lib/vex/boot.rb +40 -0
- data/lib/vex/boot/array.rb +22 -0
- data/lib/vex/boot/blank.rb +41 -0
- data/lib/vex/boot/string.rb +60 -0
- data/migration/create_request_log.rb +28 -0
- data/r.rb +35 -0
- data/script/console +19 -0
- data/script/rebuild +7 -0
- data/tasks/echoe.rake +52 -0
- data/tasks/validate_db.rake +14 -0
- data/test/ar.rb +30 -0
- data/test/auto.rb +31 -0
- data/test/base-tests/local_conf.rb +25 -0
- data/test/base.rb +2 -0
- data/test/boot.rb +3 -0
- data/test/config/local.defaults.yml +4 -0
- data/test/config/local.yml +8 -0
- data/test/test.sqlite3 +0 -0
- data/test/test.sqlite3.Class#create.lck +0 -0
- data/test/test.sqlite3.Class#create.lck.lck +0 -0
- data/test/test.sqlite3.Class#create.lck.pid +1 -0
- data/test/test.sqlite3.Class#create.pid +1 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck +0 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.lck +0 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.pid +1 -0
- data/test/test.sqlite3.LiteView.make.holders__view_dummy.pid +1 -0
- data/test/test.sqlite3.vex.lck +0 -0
- data/test/test_helper.rb +49 -0
- data/test/tmp/copy.dat +1 -0
- data/test/tmp/lock.sqlite3 +0 -0
- data/test/tmp/lock.sqlite3.etest.lck +0 -0
- data/test/tmp/lock.sqlite3.etest.pid +1 -0
- data/test/tmp/somedata.dat +61 -0
- data/vex.gemspec +49 -0
- data/vex.tmproj +186 -0
- metadata +305 -0
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require "active_record/connection_adapters/abstract_adapter"
|
|
2
|
+
|
|
3
|
+
module ActiveRecord::AdvisoryLock
|
|
4
|
+
TIMEOUT=10
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# --- load advisory lock extensions -----------------------------------
|
|
8
|
+
|
|
9
|
+
load "#{File.dirname(__FILE__)}/advisory_lock/mysql_adapter.rb"
|
|
10
|
+
# load "#{File.dirname(__FILE__)}/advisory_lock/postgresql_adapter.rb"
|
|
11
|
+
load "#{File.dirname(__FILE__)}/advisory_lock/sqlite_adapter.rb"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module ActiveRecord::ConnectionAdapters
|
|
2
|
+
class MysqlAdapter < AbstractAdapter
|
|
3
|
+
TIMEOUT=5
|
|
4
|
+
|
|
5
|
+
def locked(lock, opts = {})
|
|
6
|
+
lock = "#{current_database}.rails.#{lock}"
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
execute "SELECT GET_LOCK(#{quote(lock)},#{opts[:timeout] || TIMEOUT})"
|
|
10
|
+
yield
|
|
11
|
+
ensure
|
|
12
|
+
execute "SELECT RELEASE_LOCK(#{quote(lock)})"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module ActiveRecord::ConnectionAdapters
|
|
2
|
+
class SQLiteAdapter < AbstractAdapter
|
|
3
|
+
def locked(lock, opts = {}, &block)
|
|
4
|
+
database = instance_variable_get("@config")[:database]
|
|
5
|
+
return yield if database == ":memory:"
|
|
6
|
+
|
|
7
|
+
File.locked("#{database}.#{lock}", &block)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Note: these tests are un within multiple threads of a single application.
|
|
14
|
+
# This is not the usual intention of locking code: AbstractAdapter#lock
|
|
15
|
+
# should be used to lock across processes!
|
|
16
|
+
#
|
|
17
|
+
# Consequently locking does not work on in-memory sqlite databases.
|
|
18
|
+
module ActiveRecord::AdvisoryLock::Etest
|
|
19
|
+
class Test < ActiveRecord::Base
|
|
20
|
+
establish_connection :adapter => "sqlite3",
|
|
21
|
+
:database => "#{App.tmpdir}/lock.sqlite3"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_sqlite
|
|
25
|
+
@run = 0
|
|
26
|
+
|
|
27
|
+
connection = Test.connection
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# In this example th2 would run first, because th1 goes to sleep
|
|
31
|
+
# first.
|
|
32
|
+
th1 = Thread.new {
|
|
33
|
+
Thread.sleep 0.1
|
|
34
|
+
connection.locked("etest") do
|
|
35
|
+
assert_equal(2, @run)
|
|
36
|
+
@run = 1
|
|
37
|
+
end
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
th2 = Thread.new {
|
|
41
|
+
Thread.sleep 0.05
|
|
42
|
+
connection.locked("etest") do
|
|
43
|
+
@run = 2
|
|
44
|
+
end
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
th1.join
|
|
48
|
+
th2.join
|
|
49
|
+
|
|
50
|
+
assert_equal(1, @run)
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# In this example th1 would lock using the connection before th2 tries to
|
|
54
|
+
# lock it. Consequenty the th1 action would run first.
|
|
55
|
+
@run = 0
|
|
56
|
+
|
|
57
|
+
th1 = Thread.new {
|
|
58
|
+
connection.locked("etest") do
|
|
59
|
+
Thread.sleep 0.1
|
|
60
|
+
assert_equal(0, @run)
|
|
61
|
+
@run = 1
|
|
62
|
+
end
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
th2 = Thread.new {
|
|
66
|
+
Thread.sleep 0.05
|
|
67
|
+
connection.locked("etest") do
|
|
68
|
+
assert_equal(1, @run)
|
|
69
|
+
@run = 2
|
|
70
|
+
end
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
th1.join
|
|
74
|
+
th2.join
|
|
75
|
+
|
|
76
|
+
assert_equal(2, @run)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# belongs_to_many gives you a behaviour that you would typically (and
|
|
2
|
+
# for a reason) do via has_and_belongs_to_many or has_many. You would
|
|
3
|
+
# do this for performance reasons under a quite small set of circumstances:
|
|
4
|
+
#
|
|
5
|
+
# * your habtm code runs slow, because of the huge number of database
|
|
6
|
+
# transactions involved
|
|
7
|
+
# * you will never search by the associated data
|
|
8
|
+
#
|
|
9
|
+
# In these cases belongs_to_many might help. It not only denormalizes the
|
|
10
|
+
# database structure, it uses a single text column in the host table to
|
|
11
|
+
# hold the referenced ids, and uses ActiveRecord's read_attribute and
|
|
12
|
+
# write_attribute methods to automatically convert between IDs involved
|
|
13
|
+
# and actual objects.
|
|
14
|
+
#
|
|
15
|
+
# belongs_to_many has a number of limitations over AR' has_and_belongs_to_many
|
|
16
|
+
# and has_many associations:
|
|
17
|
+
#
|
|
18
|
+
# * it is not an association proxy: that means you cannot extend it,
|
|
19
|
+
# cannot search through it, etc.
|
|
20
|
+
# * The '<<' operator is not allowed and throws an exceptions: As
|
|
21
|
+
# we wanted to stay pretty close to rails default behaviour we had
|
|
22
|
+
# to disable that method. Otherwise all performance gain would be
|
|
23
|
+
# lost anyways.
|
|
24
|
+
# * You cannot search via the associated data, because there is no
|
|
25
|
+
# easy way to join the associated tables.
|
|
26
|
+
# * There might be a bogus write when setting the column
|
|
27
|
+
#
|
|
28
|
+
# ONCE MORE! THIS IS NOT A MORE PERFORMANT replacement for
|
|
29
|
+
# has_and_belongs_to_many or has_many!
|
|
30
|
+
#
|
|
31
|
+
module BelongsToMany
|
|
32
|
+
def self.included(base)
|
|
33
|
+
base.extend ClassMethods
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module ClassMethods
|
|
37
|
+
#
|
|
38
|
+
# define a belongs_to_many pseudo-association.
|
|
39
|
+
# Supported options:
|
|
40
|
+
#
|
|
41
|
+
# :class_name .. The name of the class that is referenced
|
|
42
|
+
# :supersedes .. code to roll out the old behaviour. This is needed
|
|
43
|
+
# for the model to survive migrations and to semi-automatically
|
|
44
|
+
# migrate the data.
|
|
45
|
+
#
|
|
46
|
+
# Example:
|
|
47
|
+
=begin
|
|
48
|
+
belongs_to_many :havings,
|
|
49
|
+
:supersedes => lambda { |name|
|
|
50
|
+
has_and_belongs_to_many name, :join_table => :belongings_havings, :class_name => "Having"
|
|
51
|
+
}
|
|
52
|
+
=end
|
|
53
|
+
#
|
|
54
|
+
def belongs_to_many(name, opts = {})
|
|
55
|
+
s = name.to_s.singularize
|
|
56
|
+
ids_name = "#{s}_ids"
|
|
57
|
+
|
|
58
|
+
if supersedes = opts.delete(:supersedes)
|
|
59
|
+
# if the belongs_to_many column exists we instanciate the superseded
|
|
60
|
+
# association with the name + "_superseded" and build the belongs_to_many
|
|
61
|
+
# association; if not, we only build the old association with the
|
|
62
|
+
# current name.
|
|
63
|
+
#
|
|
64
|
+
|
|
65
|
+
if column_names.include?(ids_name)
|
|
66
|
+
supersedes.call("#{name}_superseded")
|
|
67
|
+
belongs_to_many(name, opts)
|
|
68
|
+
else
|
|
69
|
+
supersedes.call(name)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
return
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
class_name = opts[:class_name] || s.camelize.constantize
|
|
77
|
+
|
|
78
|
+
self.class_eval code = <<RUBY
|
|
79
|
+
def #{name}
|
|
80
|
+
#{class_name}.find(#{ids_name}).freeze
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def #{name}=(array)
|
|
84
|
+
self.#{ids_name} = array.collect(&:id).uniq
|
|
85
|
+
save! unless new_record?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def #{ids_name}
|
|
89
|
+
read_attribute(:#{ids_name}).to_s.split(/[^0-9]+/).reject(&:blank?).collect { |s| Integer(s) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def #{ids_name}=(array)
|
|
93
|
+
write_attribute(:#{ids_name}, array.empty? ? nil : "/" + array.join("/") + "/")
|
|
94
|
+
end
|
|
95
|
+
RUBY
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
module Migrations
|
|
100
|
+
#
|
|
101
|
+
# e.g.
|
|
102
|
+
#
|
|
103
|
+
# migrate_belongs_to_many(:old_belongings => :belongings)
|
|
104
|
+
#
|
|
105
|
+
def migrate(klass, up_or_down, *assocs)
|
|
106
|
+
klass.reset_column_information
|
|
107
|
+
|
|
108
|
+
case up_or_down
|
|
109
|
+
when :down then assocs.map! { |assoc| [ "#{assoc}", "#{assoc}_superseded" ] }
|
|
110
|
+
when :up then assocs.map! { |assoc| [ "#{assoc}_superseded", "#{assoc}" ] }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
klass.all.with_progress.each do |model|
|
|
114
|
+
assocs.each do |from, to|
|
|
115
|
+
model.send "#{to}=", model.send(from)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Try to save. Warn, if that fails.
|
|
119
|
+
unless model.save
|
|
120
|
+
STDERR.puts "#{model.class}##{model.id}: #{errors.full_messages.join(", ")}"
|
|
121
|
+
model.save_without_validation
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# verify migration...
|
|
125
|
+
|
|
126
|
+
new_model = klass.find(model.id)
|
|
127
|
+
|
|
128
|
+
assocs.each do |from, to|
|
|
129
|
+
next if new_model.send(to) == model.send(from)
|
|
130
|
+
STDERR.puts "#{model.class}##{model.id}: Mismatch on ##{from.inspect} -> #{to.inspect}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
extend Migrations
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class ActiveRecord::Base
|
|
142
|
+
include BelongsToMany
|
|
143
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module ActiveRecord::FindByExtension
|
|
2
|
+
def find_all_by(args, opts = nil)
|
|
3
|
+
return find(:all, :conditions => args) if opts.nil?
|
|
4
|
+
|
|
5
|
+
with_scope(:find => opts) do find_all_by(args) end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def find_by(args, opts = nil)
|
|
9
|
+
return find(:first, :conditions => args) if opts.nil?
|
|
10
|
+
|
|
11
|
+
with_scope(:find => opts) do find_by(args) end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def find_by!(args, opts = nil)
|
|
15
|
+
find_by(args, opts) ||
|
|
16
|
+
raise(ActiveRecord::RecordNotFound, "Couldn't find #{self} with #{args.inspect}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class ActiveRecord::Base
|
|
21
|
+
extend ActiveRecord::FindByExtension
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ActiveRecord::FindByExtension::Etest
|
|
25
|
+
class Data < ActiveRecord::Base
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def setup
|
|
29
|
+
Data.lite_table do
|
|
30
|
+
string :name
|
|
31
|
+
string :age
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Data.create! :name => "name", :age => 2
|
|
35
|
+
Data.create! :name => "name", :age => 3
|
|
36
|
+
Data.create! :name => "name", :age => 4
|
|
37
|
+
Data.create! :name => "name2", :age => 2
|
|
38
|
+
Data.create! :name => "name2", :age => 3
|
|
39
|
+
Data.create! :name => "name2", :age => 4
|
|
40
|
+
|
|
41
|
+
assert_equal(6, Data.count)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def teardown
|
|
45
|
+
Data.destroy_all
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_find_all_by
|
|
49
|
+
assert_equal(3, Data.find_all_by(:name => "name").length)
|
|
50
|
+
assert_equal(3, Data.find_all_by(:name => [ "name" ]).length)
|
|
51
|
+
assert_raise(ActiveRecord::StatementInvalid) {
|
|
52
|
+
assert_equal(3, Data.find_all_by(:unknown => [ "name" ]).length)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
assert_equal(2, Data.find_all_by(:name => [ "name" ], :age => [1, 2, 3]).length)
|
|
56
|
+
assert_equal(2, Data.find_all_by({:name => [ "name" ]}, :conditions => { :age => [1, 2, 3]}).length)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_find_by
|
|
60
|
+
assert_equal("name", Data.find_by(:name => "name").name)
|
|
61
|
+
assert_equal(nil, Data.find_by(:name => "namex"))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_find_by!
|
|
65
|
+
assert_equal("name", Data.find_by!(:name => "name").name)
|
|
66
|
+
assert_raise(ActiveRecord::RecordNotFound) do
|
|
67
|
+
Data.find_by!(:name => "namex")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module ActiveRecord::LiteTable
|
|
2
|
+
class ColumnTypeMismatch < RuntimeError; end
|
|
3
|
+
|
|
4
|
+
class Validator
|
|
5
|
+
attr :klass
|
|
6
|
+
attr :options
|
|
7
|
+
|
|
8
|
+
def initialize(klass, options)
|
|
9
|
+
@klass = klass
|
|
10
|
+
@options = options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def column(name, type, opts = {})
|
|
14
|
+
if !existing_column = klass.columns_hash[name.to_s]
|
|
15
|
+
index_opt = opts.delete :index
|
|
16
|
+
klass.connection.add_column(klass.table_name, name, type, opts)
|
|
17
|
+
klass.reset_column_information
|
|
18
|
+
|
|
19
|
+
index(name, :unique => (index == :unique)) if index_opt
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return if existing_column.type == type
|
|
24
|
+
|
|
25
|
+
raise ColumnTypeMismatch,
|
|
26
|
+
"Column type mismatch on #{klass}##{name}: is #{existing_column.type}, but should be #{type}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def timestamps(*cols)
|
|
30
|
+
opts = cols.extract_options!
|
|
31
|
+
cols = [ :created_at, :updated_at ] if cols.empty?
|
|
32
|
+
|
|
33
|
+
index_opts = case index_opts = opts[:index]
|
|
34
|
+
when Symbol then [ index_opts ]
|
|
35
|
+
when Array then index_opts
|
|
36
|
+
when true then [:created_at, :updated_at]
|
|
37
|
+
else []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
index_opts &= cols
|
|
41
|
+
|
|
42
|
+
cols.each do |col|
|
|
43
|
+
column(col, :datetime, :index => index_opts.include?(col))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
SHORTCUTS=%w(primary_key string text integer float
|
|
48
|
+
decimal datetime timestamp time date binary boolean)
|
|
49
|
+
|
|
50
|
+
SHORTCUTS.map(&:to_sym).each do |shortcut|
|
|
51
|
+
define_method(shortcut) do |name, *opts|
|
|
52
|
+
column(name, shortcut, *opts)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def index(column_name, options={})
|
|
57
|
+
begin
|
|
58
|
+
klass.connection.add_index klass.table_name, column_name, options
|
|
59
|
+
rescue
|
|
60
|
+
# TODO: It would be *great* to have a unique exception type here!
|
|
61
|
+
# But even in this case we have to check the options for identity!
|
|
62
|
+
case $!
|
|
63
|
+
when ActiveRecord::StatementInvalid, SQLite3::SQLException
|
|
64
|
+
return if $!.to_s =~ /Duplicate key name/ # for MySQL
|
|
65
|
+
return if $!.to_s =~ /index .* already exists/ # for Sqlite3
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
raise
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def lite_table(opts={}, &block)
|
|
74
|
+
# if table does not exist: create it, according to specs.
|
|
75
|
+
connection.tables.include?(table_name) ||
|
|
76
|
+
connection.create_table(table_name, opts) {}
|
|
77
|
+
|
|
78
|
+
# run validator: This creates missing columns and indices. It never drops
|
|
79
|
+
# any data from the database, though.
|
|
80
|
+
validator = Validator.new(self, opts)
|
|
81
|
+
Proc.new.bind(validator).call
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def remove_columns(*columns)
|
|
85
|
+
connection.remove_columns table_name, *columns
|
|
86
|
+
reset_column_information
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
ActiveRecord::Base.extend ActiveRecord::LiteTable
|
|
91
|
+
|
|
92
|
+
module ActiveRecord::LiteTable::Etest
|
|
93
|
+
class TestLiteModel < ActiveRecord::Base
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_lite_table
|
|
97
|
+
TestLiteModel.lite_table do
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
assert_equal(%w(id), TestLiteModel.column_names.sort)
|
|
101
|
+
|
|
102
|
+
# we can use this class.
|
|
103
|
+
m = TestLiteModel.create!
|
|
104
|
+
assert_not_nil(m)
|
|
105
|
+
assert_not_nil(m.id)
|
|
106
|
+
|
|
107
|
+
# we cannot change a column type
|
|
108
|
+
assert_raise(ActiveRecord::LiteTable::ColumnTypeMismatch) {
|
|
109
|
+
TestLiteModel.lite_table do
|
|
110
|
+
string :id
|
|
111
|
+
end
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# we can add additional columns
|
|
115
|
+
TestLiteModel.lite_table do
|
|
116
|
+
string :name
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# we can use this class.
|
|
120
|
+
m = TestLiteModel.create! :name => "name"
|
|
121
|
+
assert_not_nil(m)
|
|
122
|
+
assert_not_nil(m.id)
|
|
123
|
+
assert_equal("name", m.name)
|
|
124
|
+
|
|
125
|
+
# we can add indices: we test index creation by creating a unique index
|
|
126
|
+
# on a column that contains two identical entries.
|
|
127
|
+
m = TestLiteModel.create! :name => "name"
|
|
128
|
+
|
|
129
|
+
assert_raises(ActiveRecord::StatementInvalid) {
|
|
130
|
+
TestLiteModel.lite_table do
|
|
131
|
+
index :name, :unique => true
|
|
132
|
+
end
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
TestLiteModel.lite_table do
|
|
136
|
+
index :name
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|