ardm-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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +35 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +47 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +595 -0
  8. data/Rakefile +4 -0
  9. data/VERSION +1 -0
  10. data/ardm-rails.gemspec +27 -0
  11. data/lib/ardm-rails.rb +1 -0
  12. data/lib/dm-rails/configuration.rb +76 -0
  13. data/lib/dm-rails/mass_assignment_security.rb +89 -0
  14. data/lib/dm-rails/middleware/identity_map.rb +20 -0
  15. data/lib/dm-rails/multiparameter_attributes.rb +167 -0
  16. data/lib/dm-rails/railtie.rb +100 -0
  17. data/lib/dm-rails/railties/controller_runtime.rb +45 -0
  18. data/lib/dm-rails/railties/database.rake +106 -0
  19. data/lib/dm-rails/railties/i18n_support.rb +12 -0
  20. data/lib/dm-rails/railties/log_listener.rb +39 -0
  21. data/lib/dm-rails/railties/log_subscriber.rb +54 -0
  22. data/lib/dm-rails/session_store.rb +70 -0
  23. data/lib/dm-rails/setup.rb +84 -0
  24. data/lib/dm-rails/storage.rb +209 -0
  25. data/lib/dm-rails/version.rb +5 -0
  26. data/lib/dm-rails.rb +1 -0
  27. data/lib/generators/data_mapper/migration/migration_generator.rb +30 -0
  28. data/lib/generators/data_mapper/migration/templates/migration.rb +23 -0
  29. data/lib/generators/data_mapper/model/model_generator.rb +23 -0
  30. data/lib/generators/data_mapper/model/templates/model.rb +11 -0
  31. data/lib/generators/data_mapper/observer/observer_generator.rb +19 -0
  32. data/lib/generators/data_mapper/observer/templates/observer.rb +7 -0
  33. data/lib/generators/data_mapper.rb +82 -0
  34. data/spec/models/fake.rb +31 -0
  35. data/spec/models/topic.rb +11 -0
  36. data/spec/spec.opts +3 -0
  37. data/spec/spec_helper.rb +19 -0
  38. data/spec/unit/mass_assignment_security_spec.rb +43 -0
  39. data/spec/unit/multiparameter_attributes_spec.rb +168 -0
  40. data/tasks/clean.rake +6 -0
  41. data/tasks/spec.rake +17 -0
  42. data/tasks/yard.rake +9 -0
  43. data/tasks/yardstick.rake +20 -0
  44. metadata +188 -0
@@ -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,70 @@
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
17
+ property :data, Object, :required => true
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
+ SESSION_RECORD_KEY = 'rack.session.record'.freeze
31
+
32
+ class_attribute :session_class
33
+ self.session_class = Session
34
+
35
+ private
36
+
37
+ def get_session(env, sid)
38
+ sid ||= generate_sid
39
+ session = find_session(sid)
40
+ env[SESSION_RECORD_KEY] = session
41
+ [ sid, session.data ]
42
+ end
43
+
44
+ def set_session(env, sid, session_data)
45
+ session = get_session_resource(env, sid)
46
+ session.data = session_data
47
+ session.updated_at = DateTime.now if session.dirty?
48
+ session.save ? sid : false
49
+ end
50
+
51
+ def get_session_resource(env, sid)
52
+ if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
53
+ env[SESSION_RECORD_KEY] = find_session(sid)
54
+ else
55
+ env[SESSION_RECORD_KEY] ||= find_session(sid)
56
+ end
57
+ end
58
+
59
+ def find_session(sid)
60
+ self.class.session_class.first_or_new(:session_id => sid)
61
+ end
62
+
63
+ def destroy(env)
64
+ find_session(current_session_id(env)).destroy
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+ 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
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module Rails
3
+ VERSION = '1.2.1'
4
+ end
5
+ 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
@@ -0,0 +1,7 @@
1
+ class <%= class_name %>Observer
2
+
3
+ include DataMapper::Observer
4
+
5
+ observe <%= class_name %>
6
+
7
+ end
@@ -0,0 +1,82 @@
1
+ require 'rails/generators/named_base'
2
+ require 'rails/generators/migration'
3
+ require 'rails/generators/active_model'
4
+
5
+ module DataMapper
6
+ module Generators
7
+
8
+ class Base < ::Rails::Generators::NamedBase #:nodoc:
9
+
10
+ include ::Rails::Generators::Migration
11
+
12
+ def self.source_root
13
+ @_datamapper_source_root ||=
14
+ File.expand_path("../#{base_name}/#{generator_name}/templates", __FILE__)
15
+ end
16
+
17
+ protected
18
+
19
+ # Datamapper does not care if migrations have the same name as long as
20
+ # they have different ids.
21
+ #
22
+ def migration_exists?(dirname, file_name) #:nodoc:
23
+ false
24
+ end
25
+
26
+ # Implement the required interface for Rails::Generators::Migration.
27
+ #
28
+ def self.next_migration_number(dirname) #:nodoc:
29
+ "%.3d" % (current_migration_number(dirname) + 1)
30
+ end
31
+
32
+ end
33
+
34
+ class ActiveModel < ::Rails::Generators::ActiveModel #:nodoc:
35
+ def self.all(klass)
36
+ "#{klass}.all"
37
+ end
38
+
39
+ def self.find(klass, params=nil)
40
+ "#{klass}.get(#{params})"
41
+ end
42
+
43
+ def self.build(klass, params=nil)
44
+ if params
45
+ "#{klass}.new(#{params})"
46
+ else
47
+ "#{klass}.new"
48
+ end
49
+ end
50
+
51
+ def save
52
+ "#{name}.save"
53
+ end
54
+
55
+ def update_attributes(params=nil)
56
+ "#{name}.update(#{params})"
57
+ end
58
+
59
+ def errors
60
+ "#{name}.errors"
61
+ end
62
+
63
+ def destroy
64
+ "#{name}.destroy"
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+
71
+ module Rails
72
+
73
+ module Generators
74
+ class GeneratedAttribute #:nodoc:
75
+ def type_class
76
+ return 'DateTime' if type.to_s == 'datetime'
77
+ return type.to_s.camelcase
78
+ end
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,31 @@
1
+ module Rails; module DataMapper; module Models
2
+ class Composite
3
+ attr_accessor :args
4
+
5
+ def initialize(*args)
6
+ @args = args
7
+ end
8
+
9
+ def ==(other)
10
+ eql?(other)
11
+ end
12
+
13
+ def eql?(other)
14
+ !other.nil? && other.args == args
15
+ end
16
+ end
17
+
18
+ class Fake
19
+ super_module = Module.new do
20
+ def _super_attributes=(*args)
21
+ end
22
+
23
+ def attributes=(*args)
24
+ self.send(:_super_attributes=, *args)
25
+ end
26
+ end
27
+ include super_module
28
+
29
+ include ::Rails::DataMapper::MultiparameterAttributes
30
+ end
31
+ end; end; end
@@ -0,0 +1,11 @@
1
+ module Rails; module DataMapper; module Models
2
+ class Topic
3
+ include ::DataMapper::Resource
4
+ include ::Rails::DataMapper::MultiparameterAttributes
5
+
6
+ property :id, Serial
7
+ property :last_read, Date
8
+ property :written_on, Time
9
+ property :updated_at, DateTime
10
+ end
11
+ end; end; end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --format progress
2
+ --color
3
+ --backtrace
@@ -0,0 +1,19 @@
1
+ $:.unshift File.expand_path File.dirname(__FILE__) + '../lib'
2
+ require 'dm-core/spec/setup'
3
+ require 'dm-core/spec/lib/adapter_helpers'
4
+ require 'dm-core/spec/lib/spec_helper'
5
+ require 'dm-core/spec/lib/pending_helpers'
6
+
7
+ DataMapper::Spec.setup
8
+ DataMapper.finalize
9
+
10
+ Spec::Runner.configure do |config|
11
+
12
+ config.extend(DataMapper::Spec::Adapters::Helpers)
13
+ config.include(DataMapper::Spec::PendingHelpers)
14
+
15
+ config.after :all do
16
+ DataMapper::Spec.cleanup_models
17
+ end
18
+
19
+ end