mack-data_mapper 0.5.5 → 0.6.0

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