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.
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