mack-data_mapper 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/README +6 -0
  2. data/lib/database.rb +94 -0
  3. data/lib/dm_patches/confirmation_validation.rb +19 -0
  4. data/lib/dm_patches/dm-cli.rb +1 -0
  5. data/lib/dm_patches/migrations.rb +23 -0
  6. data/lib/dm_patches/pooling.rb +229 -0
  7. data/lib/genosaurus_helpers.rb +40 -0
  8. data/lib/helpers/orm_helpers.rb +50 -21
  9. data/lib/mack-data_mapper.rb +43 -16
  10. data/lib/migration_generator/migration_generator.rb +28 -17
  11. data/lib/migration_generator/templates/db/migrations/%=@migration_name%.rb.template +6 -6
  12. data/lib/model_column.rb +42 -0
  13. data/lib/model_generator/manifest.yml +9 -2
  14. data/lib/model_generator/model_generator.rb +25 -20
  15. data/lib/model_generator/templates/model.rb.template +4 -4
  16. data/lib/model_generator/templates/rspec.rb.template +7 -0
  17. data/lib/model_generator/templates/{test.rb.template → test_case.rb.template} +0 -0
  18. data/lib/resource.rb +17 -0
  19. data/lib/runner.rb +17 -0
  20. data/lib/scaffold_generator/manifest.yml +14 -3
  21. data/lib/scaffold_generator/scaffold_generator.rb +4 -4
  22. data/lib/scaffold_generator/templates/app/controllers/controller.rb.template +10 -9
  23. data/lib/scaffold_generator/templates/app/helpers/controllers/helper.rb.template +7 -0
  24. data/lib/scaffold_generator/templates/app/views/edit.html.erb.template +1 -1
  25. data/lib/scaffold_generator/templates/app/views/new.html.erb.template +1 -1
  26. data/lib/scaffold_generator/templates/test/functional/rspec.rb.template +47 -0
  27. data/lib/scaffold_generator/templates/{test.rb.template → test/functional/test_case.rb.template} +0 -0
  28. data/lib/tasks/db_create_drop_tasks.rake +43 -62
  29. data/lib/tasks/db_migration_tasks.rake +25 -62
  30. data/lib/tasks/test_tasks.rake +12 -0
  31. data/lib/test_extensions.rb +90 -0
  32. metadata +28 -52
  33. data/lib/configuration.rb +0 -22
  34. data/lib/persistence.rb +0 -9
  35. data/test/database.yml +0 -3
  36. data/test/fixtures/add_users_migration.rb.fixture +0 -9
  37. data/test/fixtures/album.rb.fixture +0 -4
  38. data/test/fixtures/album_unit_test.rb.fixture +0 -9
  39. data/test/fixtures/album_with_cols.rb.fixture +0 -7
  40. data/test/fixtures/create_users_migration.rb.fixture +0 -12
  41. data/test/fixtures/routes.rb.fixture +0 -3
  42. data/test/fixtures/zoo_no_cols/edit.html.erb.fixture +0 -11
  43. data/test/fixtures/zoo_no_cols/index.html.erb.fixture +0 -20
  44. data/test/fixtures/zoo_no_cols/new.html.erb.fixture +0 -11
  45. data/test/fixtures/zoo_no_cols/show.html.erb.fixture +0 -6
  46. data/test/fixtures/zoo_with_cols/edit.html.erb.fixture +0 -19
  47. data/test/fixtures/zoo_with_cols/index.html.erb.fixture +0 -26
  48. data/test/fixtures/zoo_with_cols/new.html.erb.fixture +0 -19
  49. data/test/fixtures/zoo_with_cols/show.html.erb.fixture +0 -22
  50. data/test/fixtures/zoo_with_cols/zoo.rb.fixture +0 -4
  51. data/test/fixtures/zoo_with_cols/zoos_controller.rb.fixture +0 -50
  52. data/test/generators/migration_generator_test.rb +0 -71
  53. data/test/generators/model_generator_test.rb +0 -37
  54. data/test/generators/scaffold_generator_test.rb +0 -61
  55. data/test/lib/user.rb +0 -6
  56. data/test/tasks/db_migration_tasks_test.rb +0 -57
  57. data/test/test_helper.rb +0 -77
data/README CHANGED
@@ -1,3 +1,9 @@
1
1
  README
2
2
  ========================================================================
3
3
  mack-data_mapper was developed by: markbates
4
+
5
+ This gem provides Mack integration with the ORM Framework, DataMapper.
6
+
7
+ For more information on DataMapper please visit:
8
+
9
+ http://www.datamapper.org
@@ -0,0 +1,94 @@
1
+ module Mack
2
+ module Database
3
+
4
+ # Sets up and establishes connections to the database based on the specified environment
5
+ # and the settings in the database.yml file.
6
+ def self.establish_connection(env = Mack.env)
7
+ dbs = YAML::load(ERB.new(IO.read(File.join(Mack.root, "config", "database.yml"))).result)
8
+ settings = dbs[env]
9
+ settings.symbolize_keys!
10
+ if settings[:default]
11
+ settings.each do |k,v|
12
+ DataMapper.setup(k, v.symbolize_keys)
13
+ end
14
+ else
15
+ DataMapper.setup(:default, settings)
16
+ end
17
+ end # establish_connection
18
+
19
+ # Creates a database, if it doesn't already exist for the specified environment
20
+ def self.create(env = Mack.env, repis = :default)
21
+ Mack::Database.establish_connection(env)
22
+ create_database(repis)
23
+ end
24
+
25
+ # Drops a database, if it exists for the specified environment
26
+ def self.drop(env = Mack.env, repis = :default)
27
+ Mack::Database.establish_connection(env)
28
+ drop_database(repis)
29
+ end
30
+
31
+ private
32
+ def self.setup_temp(uri, adapter)
33
+ DataMapper.setup(:tmp, {
34
+ :adapter => adapter,
35
+ :host => "localhost",
36
+ :database => adapter,
37
+ :username => ENV["DB_USERNAME"] || uri.user,
38
+ :password => ENV["DB_PASSWORD"] || uri.password
39
+ })
40
+ end
41
+
42
+ def self.create_database(repis = :default)
43
+ uri = repository(repis).adapter.uri
44
+ case repository(repis).adapter.class.name
45
+ when /Mysql/
46
+ setup_temp(uri, "mysql")
47
+ repository(:tmp) do |repo|
48
+ puts "Creating (MySQL): #{uri.basename}"
49
+ repo.adapter.execute "CREATE DATABASE `#{uri.basename}` DEFAULT CHARACTER SET `utf8`"
50
+ end
51
+ when /Postgres/
52
+ setup_temp(uri, "postgres")
53
+ repository(:tmp) do |repo|
54
+ puts "Creating (PostgreSQL): #{uri.basename}"
55
+ repo.adapter.execute "CREATE DATABASE #{uri.basename} ENCODING = 'utf8'"
56
+ end
57
+ when /Sqlite3/
58
+ db_dir = File.join(Mack.root, "db")
59
+ puts "Creating (SQLite3): #{uri.basename}"
60
+ FileUtils.mkdir_p(db_dir)
61
+ FileUtils.touch(File.join(db_dir, uri.basename))
62
+ else
63
+ raise "Task not supported for '#{repository(repis).adapter.class.name}'"
64
+ end
65
+ end
66
+
67
+
68
+ def self.drop_database(repis = :default)
69
+ uri = repository(repis).adapter.uri
70
+ case repository(repis).adapter.class.name
71
+ when /Mysql/
72
+ setup_temp(uri, "mysql")
73
+ repository(:tmp) do |repo|
74
+ puts "Dropping (MySQL): #{uri.basename}"
75
+ repo.adapter.execute "DROP DATABASE IF EXISTS `#{uri.basename}`"
76
+ end
77
+ when /Postgres/
78
+ setup_temp(uri, "postgres")
79
+ repository(:tmp) do |repo|
80
+ puts "Dropping (PostgreSQL): #{uri.basename}"
81
+ repo.adapter.execute "DROP DATABASE IF EXISTS #{uri.basename}"
82
+ end
83
+ when /Sqlite3/
84
+ puts "Dropping (SQLite3): #{uri.basename}"
85
+ db_dir = File.join(Mack.root, "db")
86
+ FileUtils.rm_rf(File.join(db_dir.to_s, uri.basename))
87
+ else
88
+ raise "Task not supported for '#{repository(repis).adapter.class.name}'"
89
+ end
90
+ end
91
+
92
+ end # Database
93
+
94
+ end # Mack
@@ -0,0 +1,19 @@
1
+ module DataMapper # :nodoc:
2
+ module Validate # :nodoc:
3
+
4
+ class ConfirmationValidator < GenericValidator # :nodoc:
5
+
6
+ def valid?(target)
7
+ field_value = target.instance_variable_get("@#{@field_name}")
8
+ return true if @options[:allow_nil] && field_value.nil?
9
+ return false if !@options[:allow_nil] && field_value.nil?
10
+ return true unless target.attribute_dirty?(@field_name)
11
+
12
+ confirm_value = target.instance_variable_get("@#{@confirm_field_name}")
13
+ field_value == confirm_value
14
+ end
15
+
16
+ end # class ConfirmationValidator
17
+
18
+ end # module Validate
19
+ end # module DataMapper
@@ -0,0 +1 @@
1
+ require 'data_mapper/cli'
@@ -0,0 +1,23 @@
1
+ module DataMapper # :nodoc:
2
+ module MigrationRunner # :nodoc:
3
+
4
+ def reset!
5
+ @@migrations = []
6
+ end
7
+
8
+ end
9
+ end
10
+
11
+ module SQL # :nodoc:
12
+ class TableCreator # :nodoc:
13
+ class Column # :nodoc:
14
+
15
+ def build_type(type_class)
16
+ schema = {:name => @name, :quote_column_name => quoted_name}.merge(@opts)
17
+ schema = @adapter.class.type_map[type_class].merge(schema)
18
+ @adapter.property_schema_statement(schema)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,229 @@
1
+ require 'set'
2
+ require 'thread'
3
+
4
+ module Extlib # :nodoc:
5
+ # ==== Notes
6
+ # Provides pooling support to class it got included in.
7
+ #
8
+ # Pooling of objects is a faster way of aquiring instances
9
+ # of objects compared to regular allocation and initialization
10
+ # because instances are keeped in memory reused.
11
+ #
12
+ # Classes that include Pooling module have re-defined new
13
+ # method that returns instances acquired from pool.
14
+ #
15
+ # Term resource is used for any type of poolable objects
16
+ # and should NOT be thought as DataMapper Resource or
17
+ # ActiveResource resource and such.
18
+ #
19
+ # In Data Objects connections are pooled so that it is
20
+ # unnecessary to allocate and initialize connection object
21
+ # each time connection is needed, like per request in a
22
+ # web application.
23
+ #
24
+ # Pool obviously has to be thread safe because state of
25
+ # object is reset when it is released.
26
+ module Pooling # :nodoc:
27
+
28
+ def self.scavenger
29
+ @scavenger || begin
30
+ @scavenger = Thread.new do
31
+ loop do
32
+ lock.synchronize do
33
+ pools.each do |pool|
34
+ # This is a useful check, but non-essential, and right now it breaks lots of stuff.
35
+ # if pool.expired?
36
+ pool.lock.synchronize do
37
+ if pool.reserved_count == 0
38
+ pool.dispose
39
+ end
40
+ end
41
+ # end
42
+ end
43
+ end
44
+ sleep(scavenger_interval)
45
+ end # loop
46
+ end
47
+
48
+ @scavenger.priority = -10
49
+ @scavenger
50
+ end
51
+ end
52
+
53
+ def self.pools
54
+ @pools ||= Set.new
55
+ end
56
+
57
+ def self.append_pool(pool)
58
+ lock.synchronize do
59
+ pools << pool
60
+ end
61
+ Extlib::Pooling::scavenger
62
+ end
63
+
64
+ def self.lock
65
+ @lock ||= Mutex.new
66
+ end
67
+
68
+ class CrossPoolError < StandardError # :nodoc:
69
+ end
70
+
71
+ class OrphanedObjectError < StandardError # :nodoc:
72
+ end
73
+
74
+ class ThreadStopError < StandardError # :nodoc:
75
+ end
76
+
77
+ def self.included(target)
78
+ target.class_eval do
79
+ class << self
80
+ alias __new new
81
+ end
82
+
83
+ @__pools = Hash.new { |h,k| __pool_lock.synchronize { h[k] = Pool.new(target.pool_size, target, k) } }
84
+ @__pool_lock = Mutex.new
85
+
86
+ def self.__pool_lock
87
+ @__pool_lock
88
+ end
89
+
90
+ def self.new(*args)
91
+ @__pools[args].new
92
+ end
93
+
94
+ def self.__pools
95
+ @__pools
96
+ end
97
+
98
+ def self.pool_size
99
+ 8
100
+ end
101
+ end
102
+ end
103
+
104
+ def release
105
+ @__pool.release(self)
106
+ end
107
+
108
+ class Pool # :nodoc:
109
+ def initialize(max_size, resource, args)
110
+ raise ArgumentError.new("+max_size+ should be a Fixnum but was #{max_size.inspect}") unless Fixnum === max_size
111
+ raise ArgumentError.new("+resource+ should be a Class but was #{resource.inspect}") unless Class === resource
112
+
113
+ @max_size = max_size
114
+ @resource = resource
115
+ @args = args
116
+
117
+ @available = []
118
+ @reserved_count = 0
119
+ end
120
+
121
+ def lock
122
+ @resource.__pool_lock
123
+ end
124
+
125
+ def scavenge_interval
126
+ @resource.scavenge_interval
127
+ end
128
+
129
+ def new
130
+ instance = nil
131
+
132
+ lock.synchronize do
133
+ instance = acquire
134
+ end
135
+
136
+ Extlib::Pooling::append_pool(self)
137
+
138
+ if instance.nil?
139
+ # Account for the current thread, and the pool scavenger.
140
+ if ThreadGroup::Default.list.size == 2 && @reserved_count >= @max_size
141
+ raise ThreadStopError.new(size)
142
+ else
143
+ sleep(0.05)
144
+ new
145
+ end
146
+ else
147
+ instance
148
+ end
149
+ end
150
+
151
+ def release(instance)
152
+ lock.synchronize do
153
+ instance.instance_variable_set(:@__pool, nil)
154
+ @reserved_count -= 1
155
+ @available.push(instance)
156
+ end
157
+ nil
158
+ end
159
+
160
+ def size
161
+ @available.size + @reserved_count
162
+ end
163
+ alias length size
164
+
165
+ def inspect
166
+ "#<Extlib::Pooling::Pool<#{@resource.name}> available=#{@available.size} reserved_count=#{@reserved_count}>"
167
+ end
168
+
169
+ def flush!
170
+ until @available.empty?
171
+ instance = @available.pop
172
+ instance.dispose
173
+ end
174
+ @available.clear
175
+ end
176
+
177
+ def dispose
178
+ flush!
179
+ @resource.__pools.delete(@args)
180
+ !Extlib::Pooling::pools.delete?(self).nil?
181
+ end
182
+
183
+ # Disabled temporarily.
184
+ #
185
+ # def expired?
186
+ # lock.synchronize do
187
+ # @available.each do |instance|
188
+ # if instance.instance_variable_get(:@__allocated_in_pool) + scavenge_interval < Time.now
189
+ # instance.dispose
190
+ # @available.delete(instance)
191
+ # end
192
+ # end
193
+ #
194
+ # size == 0
195
+ # end
196
+ # end
197
+
198
+ def reserved_count
199
+ @reserved_count
200
+ end
201
+
202
+ private
203
+
204
+ def acquire
205
+ instance = if !@available.empty?
206
+ @available.pop
207
+ elsif size < @max_size
208
+ @resource.__new(*@args)
209
+ else
210
+ nil
211
+ end
212
+
213
+ if instance.nil?
214
+ instance
215
+ else
216
+ raise CrossPoolError.new(instance) if instance.instance_variable_get(:@__pool)
217
+ @reserved_count += 1
218
+ instance.instance_variable_set(:@__pool, self)
219
+ instance.instance_variable_set(:@__allocated_in_pool, Time.now)
220
+ instance
221
+ end
222
+ end
223
+ end
224
+
225
+ def self.scavenger_interval
226
+ 60
227
+ end
228
+ end # module Pooling
229
+ end # module Extlib
@@ -0,0 +1,40 @@
1
+ module Mack
2
+ module Genosaurus # :nodoc:
3
+ module DataMapper # :nodoc:
4
+ module Helpers
5
+
6
+ def columns(name = param(:name))
7
+ ivar_cache("form_columns") do
8
+ cs = []
9
+ cols = (param(:cols) || param(:columns))
10
+ if cols
11
+ cols.split(",").each do |x|
12
+ cs << Mack::Genosaurus::DataMapper::ModelColumn.new(name, x)
13
+ end
14
+ end
15
+ cs
16
+ end
17
+ end
18
+
19
+ def db_directory
20
+ File.join(Mack.root, "db")
21
+ end
22
+
23
+ def migrations_directory
24
+ File.join(db_directory, "migrations")
25
+ end
26
+
27
+ def next_migration_number
28
+ last = Dir.glob(File.join(migrations_directory, "*.rb")).last
29
+ if last
30
+ return File.basename(last).match(/^\d+/).to_s.succ
31
+ end
32
+ return "001"
33
+ end
34
+
35
+ ::Genosaurus.send(:include, self)
36
+
37
+ end # Helpers
38
+ end # DataMapper
39
+ end # Genosaurus
40
+ end # Mack
@@ -1,47 +1,76 @@
1
1
  module Mack
2
- module ViewHelpers
3
- module OrmHelpers
2
+ module ViewHelpers # :nodoc:
3
+ module DataMapperHelpers
4
+
4
5
  DEFAULT_PARTIAL = %{
5
- <div>
6
- <div class="errorExplanation" id="errorExplanation">
7
- <h2><%= pluralize_word(errors.size, "error") %> occured.</h2>
8
- <ul>
9
- <% for error in errors %>
10
- <li><%= error %></li>
11
- <% end %>
12
- </ul>
13
- </div>
14
- </div>
15
- }
16
-
6
+ <div>
7
+ <div class="errorExplanation" id="errorExplanation">
8
+ <h2><%= pluralize_word(errors.size, "error") %> occured.</h2>
9
+ <ul>
10
+ <% for error in errors %>
11
+ <li><%= error %></li>
12
+ <% end %>
13
+ </ul>
14
+ </div>
15
+ </div>
16
+ } unless Mack::ViewHelpers::DataMapperHelpers.const_defined?("DEFAULT_PARTIAL")
17
+
18
+ # Provides view level support for printing out all the errors associated with the
19
+ # models you tell it.
20
+ # The DEFAULT_PARTIAL constant provides a simple, default, set of HTML for displaying
21
+ # the errors. If you wish to change this HTML there are two simple ways of doing it.
22
+ # First if you have a partial named: app/views/application/_error_messages.html.erb,
23
+ # then it will use that default, and not DEFAULT_PARTIAL. The other option is to pass
24
+ # in a path to partial as the second argument and that partial will be rendered.
17
25
  def error_messages_for(object_names = [], view_partial = nil)
18
26
  object_names = [object_names].flatten
19
27
  app_errors = []
20
28
  object_names.each do |name|
21
29
  object = instance_variable_get("@#{name}")
22
30
  if object
23
- if object.is_a?(DataMapper::Persistence)
24
- app_errors << object.errors.full_messages
31
+ if object.is_a?(DataMapper::Resource)
32
+ app_errors << object.errors.full_messages.uniq
25
33
  end
26
34
  end
27
35
  end
28
36
  app_errors.flatten!
29
- File.join(Mack::Configuration.views_directory, "application", "_error_messages.html.erb")
30
37
  unless app_errors.empty?
31
38
  if view_partial.nil?
32
- if File.exist?(File.join(Mack::Configuration.views_directory, "application", "_error_messages.html.erb"))
39
+ if File.exist?(File.join(Mack.root, "app", "views", "application", "_error_messages.html.erb"))
33
40
  render(:partial, "application/error_messages", :locals => {:errors => app_errors})
34
41
  else
35
- render(:text, DEFAULT_PARTIAL, :locals => {:errors => app_errors})
42
+ render(:inline, DEFAULT_PARTIAL, :locals => {:errors => app_errors})
36
43
  end
37
- else
44
+ else
38
45
  render(:partial, view_partial, :locals => {:errors => app_errors})
39
46
  end
40
47
  else
41
48
  ""
42
49
  end
43
50
  end
44
- # self.include_safely_into(Mack::ViewBinder)
51
+
52
+ # Generates a text input tag for a given model and field
53
+ #
54
+ # Example:
55
+ # model_text_field(@user, :username) # => <input id="user_username" name="user[username]" type="text" value="<@user.username's value>" />
56
+ def model_text_field(model, property, options = {})
57
+ m_name = model.class.to_s.underscore
58
+ non_content_tag(:input, {:type => :text, :name => "#{m_name}[#{property}]", :id => "#{m_name}_#{property}", :value => model.send(property)}.merge(options))
59
+ end
60
+
61
+ # Generates a password input tag for a given model and field
62
+ #
63
+ # Example:
64
+ # model_password_field(@user, :password) # => <input id="user_username" name="user[username]" type="password" value="<@user.username's value>" />
65
+ def model_password_field(model, property, options = {})
66
+ model_text_field(model, property, {:type => :password}.merge(options))
67
+ end
68
+
69
+ def model_textarea(model, property, options = {})
70
+ m_name = model.class.to_s.underscore
71
+ content_tag(:textarea, {:name => "#{m_name}[#{property}]", :id => "#{m_name}_#{property}", :cols => 60, :rows => 20}.merge(options), model.send(property))
72
+ end
73
+
45
74
  end # OrmHelpers
46
75
  end # ViewHelpers
47
76
  end # Mack