tpitale-dm-rails 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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