vex 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. data/Manifest +112 -0
  2. data/Rakefile +53 -0
  3. data/VERSION +1 -0
  4. data/config/README +2 -0
  5. data/config/dependencies.rb +10 -0
  6. data/config/gem.yml +7 -0
  7. data/init.rb +36 -0
  8. data/lib/nokogiri/nokogiri_ext.rb +96 -0
  9. data/lib/vex.rb +5 -0
  10. data/lib/vex/action_controller.rb +4 -0
  11. data/lib/vex/action_controller/verify_action.rb +97 -0
  12. data/lib/vex/action_controller/whitelisted_actions.rb +45 -0
  13. data/lib/vex/active_record.rb +3 -0
  14. data/lib/vex/active_record/__init__.rb +0 -0
  15. data/lib/vex/active_record/advisory_lock.rb +11 -0
  16. data/lib/vex/active_record/advisory_lock/mysql_adapter.rb +16 -0
  17. data/lib/vex/active_record/advisory_lock/sqlite_adapter.rb +78 -0
  18. data/lib/vex/active_record/belongs_to_many.rb +143 -0
  19. data/lib/vex/active_record/find_by_extension.rb +70 -0
  20. data/lib/vex/active_record/gem.rb +8 -0
  21. data/lib/vex/active_record/lite_table.rb +139 -0
  22. data/lib/vex/active_record/lite_view.rb +140 -0
  23. data/lib/vex/active_record/mass_load.rb +65 -0
  24. data/lib/vex/active_record/mysql_backup.rb +21 -0
  25. data/lib/vex/active_record/plugins/default_value_for/LICENSE.TXT +19 -0
  26. data/lib/vex/active_record/plugins/default_value_for/README.rdoc +421 -0
  27. data/lib/vex/active_record/plugins/default_value_for/Rakefile +6 -0
  28. data/lib/vex/active_record/plugins/default_value_for/init.rb +91 -0
  29. data/lib/vex/active_record/plugins/default_value_for/test.rb +279 -0
  30. data/lib/vex/active_record/plugins/default_value_for/test.sqlite3 +0 -0
  31. data/lib/vex/active_record/random_id.rb +56 -0
  32. data/lib/vex/active_record/resolver.rb +49 -0
  33. data/lib/vex/active_record/serialize_hash.rb +125 -0
  34. data/lib/vex/active_record/to_html.rb +53 -0
  35. data/lib/vex/active_record/validate.rb +76 -0
  36. data/lib/vex/active_record/validation_error_ext.rb +68 -0
  37. data/lib/vex/base.rb +2 -0
  38. data/lib/vex/base/app.rb +75 -0
  39. data/lib/vex/base/array/at_random.rb +17 -0
  40. data/lib/vex/base/array/cross.rb +26 -0
  41. data/lib/vex/base/array/each_batch.rb +32 -0
  42. data/lib/vex/base/array/parallel_map.rb +98 -0
  43. data/lib/vex/base/deprecation.rb +41 -0
  44. data/lib/vex/base/enumerable/deep.rb +95 -0
  45. data/lib/vex/base/enumerable/enumerable_ext.rb +59 -0
  46. data/lib/vex/base/enumerable/progress.rb +71 -0
  47. data/lib/vex/base/filesystem/fast_copy.rb +61 -0
  48. data/lib/vex/base/filesystem/grep.rb +34 -0
  49. data/lib/vex/base/filesystem/lock.rb +43 -0
  50. data/lib/vex/base/filesystem/lock.rb.test.lck +0 -0
  51. data/lib/vex/base/filesystem/lock.rb.test.pid +1 -0
  52. data/lib/vex/base/filesystem/make_dirs.rb +94 -0
  53. data/lib/vex/base/filesystem/parse_filename.rb +36 -0
  54. data/lib/vex/base/filesystem/tmp_file.rb +87 -0
  55. data/lib/vex/base/filesystem/write.rb +43 -0
  56. data/lib/vex/base/hash/compact.rb +38 -0
  57. data/lib/vex/base/hash/cross.rb +117 -0
  58. data/lib/vex/base/hash/easy_access.rb +141 -0
  59. data/lib/vex/base/hash/ensure_keys.rb +18 -0
  60. data/lib/vex/base/hash/extract.rb +71 -0
  61. data/lib/vex/base/hash/extras.rb +62 -0
  62. data/lib/vex/base/hash/inspect.rb +17 -0
  63. data/lib/vex/base/hash/simple_access_methods.rb +74 -0
  64. data/lib/vex/base/invalid_argument/invalid_argument.rb +97 -0
  65. data/lib/vex/base/local_conf.rb +35 -0
  66. data/lib/vex/base/net/http_ext.rb +227 -0
  67. data/lib/vex/base/net/socket_ext.rb +43 -0
  68. data/lib/vex/base/object/insp.rb +123 -0
  69. data/lib/vex/base/object/multiple_attributes.rb +58 -0
  70. data/lib/vex/base/object/singleton_methods.rb +23 -0
  71. data/lib/vex/base/object/with_benchmark.rb +110 -0
  72. data/lib/vex/base/range_array.rb +40 -0
  73. data/lib/vex/base/range_ext.rb +28 -0
  74. data/lib/vex/base/safe_token.rb +156 -0
  75. data/lib/vex/base/string/string_ext.rb +136 -0
  76. data/lib/vex/base/thread/deferred.rb +52 -0
  77. data/lib/vex/base/thread/sleep.rb +11 -0
  78. data/lib/vex/base/time/date_ext.rb +12 -0
  79. data/lib/vex/boot.rb +40 -0
  80. data/lib/vex/boot/array.rb +22 -0
  81. data/lib/vex/boot/blank.rb +41 -0
  82. data/lib/vex/boot/string.rb +60 -0
  83. data/migration/create_request_log.rb +28 -0
  84. data/r.rb +35 -0
  85. data/script/console +19 -0
  86. data/script/rebuild +7 -0
  87. data/tasks/echoe.rake +52 -0
  88. data/tasks/validate_db.rake +14 -0
  89. data/test/ar.rb +30 -0
  90. data/test/auto.rb +31 -0
  91. data/test/base-tests/local_conf.rb +25 -0
  92. data/test/base.rb +2 -0
  93. data/test/boot.rb +3 -0
  94. data/test/config/local.defaults.yml +4 -0
  95. data/test/config/local.yml +8 -0
  96. data/test/test.sqlite3 +0 -0
  97. data/test/test.sqlite3.Class#create.lck +0 -0
  98. data/test/test.sqlite3.Class#create.lck.lck +0 -0
  99. data/test/test.sqlite3.Class#create.lck.pid +1 -0
  100. data/test/test.sqlite3.Class#create.pid +1 -0
  101. data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck +0 -0
  102. data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.lck +0 -0
  103. data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.pid +1 -0
  104. data/test/test.sqlite3.LiteView.make.holders__view_dummy.pid +1 -0
  105. data/test/test.sqlite3.vex.lck +0 -0
  106. data/test/test_helper.rb +49 -0
  107. data/test/tmp/copy.dat +1 -0
  108. data/test/tmp/lock.sqlite3 +0 -0
  109. data/test/tmp/lock.sqlite3.etest.lck +0 -0
  110. data/test/tmp/lock.sqlite3.etest.pid +1 -0
  111. data/test/tmp/somedata.dat +61 -0
  112. data/vex.gemspec +49 -0
  113. data/vex.tmproj +186 -0
  114. metadata +305 -0
@@ -0,0 +1,3 @@
1
+ require "#{File.dirname(__FILE__)}/boot"
2
+ Vex.load_directory "base"
3
+ Vex.load_directory "active_record"
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,8 @@
1
+ #
2
+ # Initialize the gem
3
+
4
+ require "active_record"
5
+
6
+ if defined?(ActiveRecord)
7
+ load "#{File.dirname(__FILE__)}/plugins/default_value_for/init.rb"
8
+ 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