tpitale-dm-rails 1.2.1

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 (38) hide show
  1. data/.document +5 -0
  2. data/Gemfile +63 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +595 -0
  5. data/Rakefile +27 -0
  6. data/VERSION +1 -0
  7. data/dm-rails.gemspec +94 -0
  8. data/lib/dm-rails/configuration.rb +76 -0
  9. data/lib/dm-rails/mass_assignment_security.rb +89 -0
  10. data/lib/dm-rails/middleware/identity_map.rb +20 -0
  11. data/lib/dm-rails/multiparameter_attributes.rb +167 -0
  12. data/lib/dm-rails/railtie.rb +100 -0
  13. data/lib/dm-rails/railties/controller_runtime.rb +45 -0
  14. data/lib/dm-rails/railties/database.rake +106 -0
  15. data/lib/dm-rails/railties/i18n_support.rb +12 -0
  16. data/lib/dm-rails/railties/log_listener.rb +39 -0
  17. data/lib/dm-rails/railties/log_subscriber.rb +54 -0
  18. data/lib/dm-rails/session_store.rb +77 -0
  19. data/lib/dm-rails/setup.rb +84 -0
  20. data/lib/dm-rails/storage.rb +209 -0
  21. data/lib/dm-rails.rb +1 -0
  22. data/lib/generators/data_mapper/migration/migration_generator.rb +30 -0
  23. data/lib/generators/data_mapper/migration/templates/migration.rb +23 -0
  24. data/lib/generators/data_mapper/model/model_generator.rb +23 -0
  25. data/lib/generators/data_mapper/model/templates/model.rb +11 -0
  26. data/lib/generators/data_mapper/observer/observer_generator.rb +19 -0
  27. data/lib/generators/data_mapper/observer/templates/observer.rb +7 -0
  28. data/lib/generators/data_mapper.rb +82 -0
  29. data/spec/models/fake.rb +31 -0
  30. data/spec/models/topic.rb +11 -0
  31. data/spec/spec.opts +3 -0
  32. data/spec/spec_helper.rb +19 -0
  33. data/spec/unit/mass_assignment_security_spec.rb +43 -0
  34. data/spec/unit/multiparameter_attributes_spec.rb +168 -0
  35. data/tasks/clean.rake +6 -0
  36. data/tasks/yard.rake +9 -0
  37. data/tasks/yardstick.rake +20 -0
  38. metadata +161 -0
@@ -0,0 +1,106 @@
1
+ require 'dm-rails/setup'
2
+ require 'dm-rails/storage'
3
+
4
+ namespace :db do
5
+
6
+ desc 'Create the database, load the schema, and initialize with the seed data'
7
+ task :setup => [ 'db:create', 'db:automigrate', 'db:seed' ]
8
+
9
+ namespace :create do
10
+ desc 'Create all the local databases defined in config/database.yml'
11
+ task :all => :environment do
12
+ Rails::DataMapper.storage.create_all
13
+ end
14
+ end
15
+
16
+ desc "Create all local databases defined for the current Rails.env"
17
+ task :create => :environment do
18
+ Rails::DataMapper.storage.create_environment(Rails::DataMapper.configuration.repositories[Rails.env])
19
+ end
20
+
21
+ namespace :drop do
22
+ desc 'Drop all the local databases defined in config/database.yml'
23
+ task :all => :environment do
24
+ Rails::DataMapper.storage.drop_all
25
+ end
26
+ end
27
+
28
+ desc "Drop all local databases defined for the current Rails.env"
29
+ task :drop => :environment do
30
+ Rails::DataMapper.storage.drop_environment(Rails::DataMapper.configuration.repositories[Rails.env])
31
+ end
32
+
33
+
34
+ desc 'Perform destructive automigration of all repositories in the current Rails.env'
35
+ task :automigrate => :environment do
36
+ require 'dm-migrations'
37
+ Rails::DataMapper.configuration.repositories[Rails.env].each do |repository, config|
38
+ ::DataMapper.auto_migrate!(repository.to_sym)
39
+ puts "[datamapper] Finished auto_migrate! for :#{repository} repository '#{config['database']}'"
40
+ end
41
+ end
42
+
43
+ desc 'Perform non destructive automigration of all repositories in the current Rails.env'
44
+ task :autoupgrade => :environment do
45
+ require 'dm-migrations'
46
+ Rails::DataMapper.configuration.repositories[Rails.env].each do |repository, config|
47
+ ::DataMapper.auto_upgrade!(repository.to_sym)
48
+ puts "[datamapper] Finished auto_upgrade! for :#{repository} repository '#{config['database']}'"
49
+ end
50
+ end
51
+
52
+ desc 'Load the seed data from db/seeds.rb'
53
+ task :seed => :environment do
54
+ seed_file = File.join(Rails.root, 'db', 'seeds.rb')
55
+ load(seed_file) if File.exist?(seed_file)
56
+ end
57
+
58
+ namespace :migrate do
59
+ task :load => :environment do
60
+ require 'dm-migrations/migration_runner'
61
+ FileList['db/migrate/*.rb'].each do |migration|
62
+ load migration
63
+ end
64
+ end
65
+
66
+ desc 'Migrate up using migrations'
67
+ task :up, [:version] => [:load] do |t, args|
68
+ ::DataMapper::MigrationRunner.migrate_up!(args[:version])
69
+ end
70
+
71
+ desc 'Migrate down using migrations'
72
+ task :down, [:version] => [:load] do |t, args|
73
+ ::DataMapper::MigrationRunner.migrate_down!(args[:version])
74
+ end
75
+ end
76
+
77
+ desc 'Migrate the database to the latest version'
78
+ task :migrate do
79
+ migrate_task = if Dir['db/migrate/*.rb'].empty?
80
+ 'db:autoupgrade'
81
+ else
82
+ 'db:migrate:up'
83
+ end
84
+
85
+ Rake::Task[migrate_task].invoke
86
+ end
87
+
88
+ namespace :sessions do
89
+ desc "Creates the sessions table for DataMapperStore"
90
+ task :create => :environment do
91
+ require 'dm-rails/session_store'
92
+ Rails::DataMapper::SessionStore::Session.auto_migrate!
93
+ database = Rails::DataMapper.configuration.repositories[Rails.env]['database']
94
+ puts "Created '#{database}.sessions'"
95
+ end
96
+
97
+ desc "Clear the sessions table for DataMapperStore"
98
+ task :clear => :environment do
99
+ require 'dm-rails/session_store'
100
+ Rails::DataMapper::SessionStore::Session.destroy!
101
+ database = Rails::DataMapper.configuration.repositories[Rails.env]['database']
102
+ puts "Deleted entries from '#{database}.sessions'"
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,12 @@
1
+ module Rails
2
+ module DataMapper
3
+
4
+ module I18nSupport
5
+ # Set the i18n scope to overwrite ActiveModel.
6
+ def i18n_scope #:nodoc:
7
+ :data_mapper
8
+ end
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_support/notifications'
2
+
3
+ # TODO This needs to be fixed upstream in active_support/notifications/instrumenter.rb
4
+ #
5
+ # We need to monkeypatch this for now, because the original implementation hardcodes the
6
+ # duration to the time elapsed between start and end of the event. The current upstream
7
+ # implementation is included here for reference:
8
+ #
9
+ # def duration
10
+ # @duration ||= 1000.0 * (@end - @time)
11
+ # end
12
+ #
13
+ # It should be safe to assume that explicitly provided duration information should be at
14
+ # least as precise as the current generic solution, if not more (as in our specific case).
15
+ #
16
+ module ActiveSupport
17
+ module Notifications
18
+ class Event
19
+ def duration
20
+ payload[:duration] ? (payload[:duration] / 1000.0) : 1000.0 * (self.end - self.time)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ module LogListener
27
+ def log(message)
28
+ ActiveSupport::Notifications.instrument('sql.data_mapper',
29
+ :name => 'SQL',
30
+ :sql => message.query, # TODO think about changing the key to :query
31
+ :start => message.start,
32
+ :duration => message.duration,
33
+ :connection_id => self.object_id
34
+ )
35
+ super
36
+ rescue Exception => e
37
+ ::DataMapper.logger.error "[datamapper] #{e.class.name}: #{e.message}: #{message.inspect}}"
38
+ end
39
+ end
@@ -0,0 +1,54 @@
1
+ module DataMapper
2
+ module Railties
3
+
4
+ class LogSubscriber < ActiveSupport::LogSubscriber
5
+
6
+ def self.runtime=(value)
7
+ Thread.current["data_mapper_sql_runtime"] = value
8
+ end
9
+
10
+ def self.runtime
11
+ Thread.current["data_mapper_sql_runtime"] ||= 0
12
+ end
13
+
14
+ def self.reset_runtime
15
+ rt, self.runtime = runtime, 0
16
+ rt
17
+ end
18
+
19
+ def initialize
20
+ super
21
+ @odd_or_even = false
22
+ end
23
+
24
+ def sql(event)
25
+ self.class.runtime += event.duration
26
+ return unless logger.debug?
27
+
28
+ name = '%s (%.3fms)' % [event.payload[:name], event.duration]
29
+ sql = event.payload[:sql].squeeze(' ')
30
+
31
+ if odd?
32
+ name = color(name, CYAN, true)
33
+ sql = color(sql, nil, true)
34
+ else
35
+ name = color(name, MAGENTA, true)
36
+ end
37
+
38
+ debug " #{name} #{sql}"
39
+ end
40
+
41
+ def odd?
42
+ @odd_or_even = !@odd_or_even
43
+ end
44
+
45
+ def logger
46
+ ::DataMapper.logger
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+ DataMapper::Railties::LogSubscriber.attach_to :data_mapper
@@ -0,0 +1,77 @@
1
+ require 'dm-core'
2
+ require 'active_support/core_ext/class/attribute'
3
+
4
+ # Implements DataMapper-specific session store.
5
+
6
+ module Rails
7
+ module DataMapper
8
+
9
+ class SessionStore < ActionDispatch::Session::AbstractStore
10
+
11
+ class Session
12
+
13
+ include ::DataMapper::Resource
14
+
15
+ property :id, Serial
16
+ property :session_id, String, :required => true, :unique => true, :length => 0..150
17
+ property :data, Object, :required => false
18
+ property :updated_at, DateTime, :index => true
19
+
20
+ def self.name
21
+ 'session'
22
+ end
23
+
24
+ def data
25
+ attribute_get(:data) || {}
26
+ end
27
+
28
+ end
29
+
30
+ # for backward compatibility with Rails 3.0
31
+ ENV_SESSION_OPTIONS_KEY = ::Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY unless const_defined?("ENV_SESSION_OPTIONS_KEY")
32
+ SESSION_RECORD_KEY = 'rack.session.record'.freeze
33
+
34
+ class_attribute :session_class
35
+ self.session_class = Session
36
+
37
+ private
38
+
39
+ def get_session(env, sid)
40
+ sid ||= generate_sid
41
+ session = find_session(sid)
42
+ env[SESSION_RECORD_KEY] = session
43
+ [ sid, session.data ]
44
+ end
45
+
46
+ def set_session(env, sid, session_data, options = {})
47
+ session = get_session_resource(env, sid)
48
+ session.data = session_data
49
+ session.updated_at = DateTime.now if session.dirty?
50
+ session.save ? sid : false
51
+ end
52
+
53
+ def get_session_resource(env, sid)
54
+ if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
55
+ env[SESSION_RECORD_KEY] = find_session(sid)
56
+ else
57
+ env[SESSION_RECORD_KEY] ||= find_session(sid)
58
+ end
59
+ end
60
+
61
+ def find_session(sid)
62
+ self.class.session_class.first_or_new(:session_id => sid)
63
+ end
64
+
65
+ def destroy_session(env, sid = nil, options = {})
66
+ sid ||= current_session_id(env)
67
+ find_session(sid).destroy
68
+ end
69
+
70
+ def destroy(env)
71
+ destroy_session(env)
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,84 @@
1
+ require 'active_support/core_ext/hash/except'
2
+
3
+ require 'dm-rails/configuration'
4
+ require 'dm-rails/railties/log_listener'
5
+
6
+ module Rails
7
+ module DataMapper
8
+
9
+ def self.setup(environment)
10
+ ::DataMapper.logger.info "[datamapper] Setting up the #{environment.inspect} environment:"
11
+ configuration.repositories[environment].each do |name, config|
12
+ setup_with_instrumentation(name.to_sym, config)
13
+ end
14
+ finalize
15
+ end
16
+
17
+ def self.setup_with_instrumentation(name, options)
18
+
19
+ adapter = if options['uri']
20
+ database_uri = ::Addressable::URI.parse(options['uri'])
21
+ ::DataMapper.logger.info "[datamapper] Setting up #{name.inspect} repository: '#{database_uri.path}' on #{database_uri.scheme}"
22
+ ::DataMapper.setup(name, database_uri)
23
+ else
24
+ ::DataMapper.logger.info "[datamapper] Setting up #{name.inspect} repository: '#{options['database']}' on #{options['adapter']}"
25
+ ::DataMapper.setup(name, options)
26
+ end
27
+
28
+ if convention = configuration.resource_naming_convention[name]
29
+ adapter.resource_naming_convention = convention
30
+ end
31
+ if convention = configuration.field_naming_convention[name]
32
+ adapter.field_naming_convention = convention
33
+ end
34
+ setup_log_listener(adapter.options['adapter'])
35
+ end
36
+
37
+ def self.setup_logger(logger)
38
+ ::DataMapper.logger = logger
39
+ end
40
+
41
+ def self.setup_log_listener(adapter_name)
42
+ adapter_name = 'sqlite3' if adapter_name == 'sqlite'
43
+ driver_name = ActiveSupport::Inflector.camelize(adapter_name)
44
+
45
+ setup_do_driver(driver_name) do |driver|
46
+ DataObjects::Connection.send(:include, LogListener)
47
+ # FIXME Setting DataMapper::Logger.new($stdout, :off) alone won't work because the #log
48
+ # method is currently only available in DO and needs an explicit DO Logger instantiated.
49
+ # We turn the logger :off because ActiveSupport::Notifications handles displaying log messages
50
+ driver.logger = DataObjects::Logger.new($stdout, :off)
51
+ end
52
+ end
53
+
54
+ def self.finalize
55
+ ::DataMapper.finalize
56
+ end
57
+
58
+ def self.preload_models(app)
59
+ app.config.paths['app/models'].each do |path|
60
+ Dir.glob("#{path}/**/*.rb").sort.each { |file| require_dependency file[path.length..-1] }
61
+ end
62
+ finalize
63
+ end
64
+
65
+ class << self
66
+ private
67
+
68
+ if RUBY_VERSION < '1.9'
69
+ def setup_do_driver(driver_name)
70
+ if Object.const_defined?('DataObjects') && DataObjects.const_defined?(driver_name)
71
+ yield DataObjects.const_get(driver_name)
72
+ end
73
+ end
74
+ else
75
+ def setup_do_driver(driver_name)
76
+ if Object.const_defined?('DataObjects', false) && DataObjects.const_defined?(driver_name, false)
77
+ yield DataObjects.const_get(driver_name, false)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,209 @@
1
+ module Rails
2
+ module DataMapper
3
+
4
+ def self.storage
5
+ Storage
6
+ end
7
+
8
+ class Storage
9
+ attr_reader :name, :config
10
+
11
+ def self.create_all
12
+ with_local_repositories { |config| create_environment(config) }
13
+ end
14
+
15
+ def self.drop_all
16
+ with_local_repositories { |config| drop_environment(config) }
17
+ end
18
+
19
+ def self.create_environment(config)
20
+ config.each { |repo_name, repo_config| new(repo_name, repo_config).create }
21
+ end
22
+
23
+ def self.drop_environment(config)
24
+ config.each { |repo_name, repo_config| new(repo_name, repo_config).drop }
25
+ end
26
+
27
+ def self.new(name, config)
28
+ klass = lookup_class(config['adapter'])
29
+ if klass.equal?(self)
30
+ super(name, config)
31
+ else
32
+ klass.new(name, config)
33
+ end
34
+ end
35
+
36
+ class << self
37
+ private
38
+
39
+ def with_local_repositories
40
+ Rails::DataMapper.configuration.repositories.each_value do |config|
41
+ if config['host'].blank? || %w[ 127.0.0.1 localhost ].include?(config['host'])
42
+ yield(config)
43
+ else
44
+ puts "This task only modifies local databases. #{config['database']} is on a remote host."
45
+ end
46
+ end
47
+ end
48
+
49
+ def lookup_class(adapter)
50
+ klass_name = normalized_adapter_name(adapter).camelize.to_sym
51
+
52
+ unless Storage.const_defined?(klass_name)
53
+ raise "Adapter #{adapter} not supported (#{klass_name.inspect})"
54
+ end
55
+
56
+ const_get(klass_name)
57
+ end
58
+
59
+ def normalized_adapter_name(adapter_name)
60
+ adapter_name.to_s == 'sqlite3' ? 'sqlite' : adapter_name
61
+ end
62
+
63
+ end
64
+
65
+ def initialize(name, config)
66
+ @name, @config = name.to_sym, config
67
+ end
68
+
69
+ def create
70
+ puts create_message if _create
71
+ end
72
+
73
+ def drop
74
+ puts drop_message if _drop
75
+ end
76
+
77
+ def database
78
+ @database ||= config['database'] || config['path']
79
+ end
80
+
81
+ def username
82
+ @username ||= config['username'] || ''
83
+ end
84
+
85
+ def password
86
+ @password ||= config['password'] || ''
87
+ end
88
+
89
+ def charset
90
+ @charset ||= config['charset'] || ENV['CHARSET'] || 'utf8'
91
+ end
92
+
93
+ def create_message
94
+ "[datamapper] Created database '#{database}'"
95
+ end
96
+
97
+ def drop_message
98
+ "[datamapper] Dropped database '#{database}'"
99
+ end
100
+
101
+ # Create the configured database
102
+ #
103
+ # This is a noop so that calling this method
104
+ # won't explode on people who use adapters that
105
+ # don't support creating a storage recepticle
106
+ def _create
107
+ true
108
+ end
109
+
110
+ # Drop the configured database
111
+ #
112
+ # This is a noop so that calling this method
113
+ # won't explode on people who use adapters that
114
+ # don't support dropping a storage recepticle
115
+ def _drop
116
+ true
117
+ end
118
+
119
+ class Sqlite < Storage
120
+
121
+ # This is a noop for sqlite
122
+ #
123
+ # Overwritten solely for documentation purposes
124
+ #
125
+ # Both auto_migrate!/auto_upgrade! will create the actual database
126
+ # if the connection has been setup properly and there actually
127
+ # are statements to execute (i.e. at least one model is declared)
128
+ #
129
+ # DataMapper.setup alone won't create the actual database so there
130
+ # really is no API to simply create an empty database for sqlite3.
131
+ #
132
+ # we return true to indicate success nevertheless
133
+ def _create
134
+ true
135
+ end
136
+
137
+ def _drop
138
+ return if in_memory?
139
+ path.unlink if path.file?
140
+ end
141
+
142
+ def create_message
143
+ "[datamapper] db:create is a noop for sqlite3, use db:automigrate instead (#{database})"
144
+ end
145
+
146
+ private
147
+
148
+ def in_memory?
149
+ database == ':memory:'
150
+ end
151
+
152
+ def path
153
+ @path ||= Pathname(File.expand_path(database, Rails.root))
154
+ end
155
+
156
+ end
157
+
158
+ class Mysql < Storage
159
+ def _create
160
+ execute("CREATE DATABASE `#{database}` DEFAULT CHARACTER SET #{charset} DEFAULT COLLATE #{collation}")
161
+ end
162
+
163
+ def _drop
164
+ execute("DROP DATABASE IF EXISTS `#{database}`")
165
+ end
166
+
167
+ private
168
+
169
+ def execute(statement)
170
+ system(
171
+ 'mysql',
172
+ (username.blank? ? '' : "--user=#{username}"),
173
+ (password.blank? ? '' : "--password=#{password}"),
174
+ '-e',
175
+ statement
176
+ )
177
+ end
178
+
179
+ def collation
180
+ @collation ||= config['collation'] || ENV['COLLATION'] || 'utf8_unicode_ci'
181
+ end
182
+
183
+ end
184
+
185
+ class Postgres < Storage
186
+ def _create
187
+ system(
188
+ 'createdb',
189
+ '-E',
190
+ charset,
191
+ '-U',
192
+ username,
193
+ database
194
+ )
195
+ end
196
+
197
+ def _drop
198
+ system(
199
+ 'dropdb',
200
+ '-U',
201
+ username,
202
+ database
203
+ )
204
+ end
205
+
206
+ end
207
+ end
208
+ end
209
+ end
data/lib/dm-rails.rb ADDED
@@ -0,0 +1 @@
1
+ require 'dm-rails/railtie'
@@ -0,0 +1,30 @@
1
+ require 'generators/data_mapper'
2
+
3
+ module DataMapper
4
+ module Generators
5
+
6
+ class MigrationGenerator < Base
7
+
8
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
9
+ class_option :id, :type => :numeric, :desc => "The id to be used in this migration"
10
+
11
+ def create_migration_file
12
+ set_local_assigns!
13
+ migration_template "migration.rb", "db/migrate/#{file_name}.rb"
14
+ end
15
+
16
+ protected
17
+
18
+ attr_reader :migration_action
19
+
20
+ def set_local_assigns!
21
+ if file_name =~ /^(add|remove|drop)_.*_(?:to|from)_(.*)/
22
+ @migration_action = $1 == 'add' ? 'add' : 'drop'
23
+ @table_name = $2.pluralize
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ migration <%= migration_number.to_i %>, :<%= migration_file_name %> do
2
+
3
+ up do
4
+ <% unless attributes.empty? -%>
5
+ modify_table :<%= table_name %> do
6
+ <% attributes.each do |attribute| -%>
7
+ <%= migration_action %>_column :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type_class %><% end -%>
8
+ <% end -%>
9
+ end
10
+ <% end -%>
11
+ end
12
+
13
+ down do
14
+ <% unless attributes.empty? -%>
15
+ modify_table :<%= table_name %> do
16
+ <% attributes.reverse.each do |attribute| -%>
17
+ <%= migration_action == 'add' ? 'drop' : 'add' %>_column :<%= attribute.name %><% if migration_action == 'drop' %>, :<%= attribute.type_class %><% end -%>
18
+ <% end -%>
19
+ end
20
+ <% end -%>
21
+ end
22
+
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'generators/data_mapper'
2
+
3
+ module DataMapper
4
+ module Generators
5
+
6
+ class ModelGenerator < Base
7
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
8
+
9
+ check_class_collision
10
+
11
+ class_option :timestamps, :type => :boolean
12
+ class_option :parent, :type => :string, :desc => "The parent class for the generated model"
13
+
14
+ def create_model_file
15
+ template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
16
+ end
17
+
18
+ hook_for :test_framework
19
+
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ class <%= class_name %><%= "< #{options[:parent].classify}" if options[:parent] %>
2
+
3
+ <% unless options[:parent] -%>
4
+ include DataMapper::Resource
5
+
6
+ property :id, Serial
7
+ <% end %>
8
+ <% attributes.each do |attribute| -%>
9
+ property :<%= attribute.name -%>, <%= attribute.type_class %>
10
+ <% end %>
11
+ end
@@ -0,0 +1,19 @@
1
+ require 'generators/data_mapper'
2
+
3
+ module DataMapper
4
+ module Generators
5
+
6
+ class ObserverGenerator < Base
7
+
8
+ check_class_collision :suffix => "Observer"
9
+
10
+ def create_observer_file
11
+ template 'observer.rb', File.join('app/models', class_path, "#{file_name}_observer.rb")
12
+ end
13
+
14
+ hook_for :test_framework
15
+
16
+ end
17
+
18
+ end
19
+ end