Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of git@github.com:sam/dm-core

  • Loading branch information...
commit 1e78ba8f55b3570d0831ee611b952738fb8d0a67 2 parents 47c6c6c + cef2804
@david david authored
Showing with 1,108 additions and 987 deletions.
  1. +19 −15 .autotest
  2. +17 −14 Rakefile
  3. +5 −5 environment.rb
  4. +52 −3 lib/data_mapper.rb
  5. +21 −21 lib/data_mapper/adapters/abstract_adapter.rb
  6. +71 −70 lib/data_mapper/adapters/data_objects_adapter.rb
  7. +1 −1  lib/data_mapper/adapters/mysql_adapter.rb
  8. +4 −4 lib/data_mapper/adapters/postgres_adapter.rb
  9. +21 −15 lib/data_mapper/associations.rb
  10. +9 −5 lib/data_mapper/associations/many_to_many.rb
  11. +32 −22 lib/data_mapper/associations/many_to_one.rb
  12. +47 −32 lib/data_mapper/associations/one_to_many.rb
  13. +15 −9 lib/data_mapper/associations/one_to_one.rb
  14. +32 −23 lib/data_mapper/associations/parent_to_child_association.rb
  15. +41 −32 lib/data_mapper/associations/relationship.rb
  16. +6 −5 lib/data_mapper/auto_migrations.rb
  17. +6 −7 lib/data_mapper/dependency_queue.rb
  18. +14 −14 lib/data_mapper/identity_map.rb
  19. +3 −1 lib/data_mapper/is/tree.rb
  20. +28 −30 lib/data_mapper/loaded_set.rb
  21. +2 −2 lib/data_mapper/logger.rb
  22. +63 −64 lib/data_mapper/property.rb
  23. +6 −4 lib/data_mapper/property_set.rb
  24. +57 −42 lib/data_mapper/query.rb
  25. +31 −97 lib/data_mapper/repository.rb
  26. +47 −47 lib/data_mapper/resource.rb
  27. +1 −7 lib/data_mapper/scope.rb
  28. +10 −1 lib/data_mapper/support/kernel.rb
  29. +6 −8 lib/data_mapper/type.rb
  30. +28 −0 lib/data_mapper/types/csv.rb
  31. +31 −0 lib/data_mapper/types/yaml.rb
  32. +1 −1  spec/integration/association_spec.rb
  33. +3 −3 spec/integration/mysql_adapter_spec.rb
  34. +1 −1  spec/integration/postgres_adapter_spec.rb
  35. +12 −12 spec/integration/property_spec.rb
  36. +17 −17 spec/integration/query_spec.rb
  37. +2 −2 spec/integration/repository_spec.rb
  38. +2 −2 spec/integration/sqlite3_adapter_spec.rb
  39. +30 −46 spec/integration/type_spec.rb
  40. +4 −6 spec/{ → lib}/mock_adapter.rb
  41. +1 −1  spec/spec.opts
  42. +7 −6 spec/spec_helper.rb
  43. +10 −10 spec/{ → unit}/adapters/abstract_adapter_spec.rb
  44. +0 −1  spec/{ → unit/adapters}/adapter_shared_spec.rb
  45. +39 −39 spec/{ → unit}/adapters/data_objects_adapter_spec.rb
  46. +6 −6 spec/{ → unit}/associations/many_to_many_spec.rb
  47. +2 −4 spec/{ → unit}/associations/many_to_one_spec.rb
  48. +3 −3 spec/{ → unit}/associations/one_to_many_spec.rb
  49. +3 −2 spec/{ → unit}/associations/one_to_one_spec.rb
  50. +32 −28 spec/{ → unit}/associations/relationship_spec.rb
  51. +23 −24 spec/{ → unit}/cli_spec.rb
  52. +9 −9 spec/{ → unit}/dependency_spec.rb
  53. +40 −37 spec/{ → unit}/hook_spec.rb
  54. +38 −39 spec/{ → unit}/identity_map_spec.rb
  55. +4 −4 spec/{ → unit}/loaded_set_spec.rb
  56. +2 −2 spec/{ → unit}/naming_conventions_spec.rb
  57. +1 −1  spec/{ → unit}/property_set_spec.rb
  58. +3 −3 spec/{ → unit}/property_spec.rb
  59. +1 −1  spec/{ → unit}/query_spec.rb
  60. +8 −8 spec/{ → unit}/repository_spec.rb
  61. +10 −5 spec/{ → unit}/resource_spec.rb
  62. +1 −1  spec/{ → unit}/scope_spec.rb
  63. +38 −36 spec/{ → unit}/support/aliasinghash_spec.rb
  64. +1 −1  spec/{ → unit}/support/blank_spec.rb
  65. +1 −1  spec/{ → unit/support}/inflection_spec.rb
  66. +4 −4 spec/{ → unit}/support/object_spec.rb
  67. +2 −2 spec/{ → unit}/support/string_spec.rb
  68. +6 −6 spec/{ → unit}/support/struct_spec.rb
  69. +15 −13 spec/{ → unit}/type_spec.rb
View
34 .autotest
@@ -1,22 +1,26 @@
Autotest.add_hook :initialize do |at|
- ignore = %w{.git burn www log plugins script tasks bin CHANGELOG FAQ MIT-LICENSE PERFORMANCE QUICKLINKS README}
+ ignore = %w[ .git burn www log plugins script tasks bin CHANGELOG FAQ MIT-LICENSE PERFORMANCE QUICKLINKS README ]
+
unless ENV['AUTOTEST'] == 'integration'
ignore << 'spec/integration'
end
-
- ignore.each do |exception|
+
+ ignore.each do |exception|
at.add_exception(exception)
end
-
+
at.clear_mappings
- at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _|
- filename
- }
- at.add_mapping(%r%^lib/data_mapper/(.*)\.rb$%) { |_, m|
- ["spec/#{m[1]}_spec.rb"]
- }
- at.add_mapping(%r%^spec/spec_helper.rb$%) {
- at.files_matching %r%^spec/.*_spec\.rb$%
- }
-
-end
+
+ at.add_mapping(%r{^spec/.+_spec\.rb$}) do |filename,_|
+ filename
+ end
+
+ at.add_mapping(%r{^lib/data_mapper/(.+)\.rb$}) do |_,match|
+ [ "spec/unit/#{match[1]}_spec.rb" ] +
+ at.files_matching(%r{^spec/integration/.+_spec\.rb$})
+ end
+
+ at.add_mapping(%r{^spec/spec_helper\.rb$}) do
+ at.files_matching(%r{^spec/.+_spec\.rb$})
+ end
+end
View
31 Rakefile
@@ -22,17 +22,20 @@ namespace :dm do
task :environment do
require 'environment'
end
-
+
desc "Run specifications"
Spec::Rake::SpecTask.new('spec') do |t|
t.spec_opts = ["--format", "specdoc", "--colour"]
t.spec_files = Pathname.glob(ENV['FILES'] || __DIR__ + 'spec/**/*_spec.rb')
unless ENV['NO_RCOV']
t.rcov = true
- t.rcov_opts = ['--exclude', 'spec,environment.rb']
+ t.rcov_opts << '--exclude' << 'spec,environment.rb'
+ t.rcov_opts << '--text-summary'
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
+ t.rcov_opts << '--only-uncovered'
end
end
-
+
desc "Run comparison with ActiveRecord"
task :perf do
load __DIR__ + 'script/performance.rb'
@@ -46,7 +49,7 @@ end
PACKAGE_VERSION = '0.9.0'
-PACKAGE_FILES = Pathname.glob([
+PACKAGE_FILES = [
'README',
'FAQ',
'QUICKLINKS',
@@ -57,7 +60,7 @@ PACKAGE_FILES = Pathname.glob([
'spec/**/*.{rb,yaml}',
'tasks/**/*',
'plugins/**/*'
-]).reject { |path| path =~ /(\/db|Makefile|\.bundle|\.log|\.o)\z/ }
+].collect { |pattern| Pathname.glob(pattern) }.flatten.reject { |path| path.to_s =~ /(\/db|Makefile|\.bundle|\.log|\.o)\z/ }
DOCUMENTED_FILES = PACKAGE_FILES.reject do |path|
path.directory? || path.to_s.match(/(?:^spec|\/spec|\/swig\_)/)
@@ -78,19 +81,19 @@ rd = Rake::RDocTask.new do |rdoc|
end
gem_spec = Gem::Specification.new do |s|
- s.platform = Gem::Platform::RUBY
- s.name = PROJECT
+ s.platform = Gem::Platform::RUBY
+ s.name = PROJECT
s.summary = "An Object/Relational Mapper for Ruby"
s.description = "Faster, Better, Simpler."
- s.version = PACKAGE_VERSION
-
+ s.version = PACKAGE_VERSION
+
s.authors = "Sam Smoot"
s.email = "ssmoot@gmail.com"
- s.rubyforge_project = PROJECT
- s.homepage = "http://datamapper.org"
-
+ s.rubyforge_project = PROJECT
+ s.homepage = "http://datamapper.org"
+
s.files = PACKAGE_FILES.map { |f| f.to_s }
-
+
s.require_path = "lib"
s.requirements << "none"
s.executables = ["dm"]
@@ -101,7 +104,7 @@ gem_spec = Gem::Specification.new do |s|
s.add_dependency("data_objects", ">=0.9.0")
s.add_dependency("english")
- s.has_rdoc = true
+ s.has_rdoc = true
s.rdoc_options << "--line-numbers" << "--inline-source" << "--main" << "README"
s.extra_rdoc_files = DOCUMENTED_FILES.map { |f| f.to_s }
end
View
10 environment.rb
@@ -2,11 +2,11 @@
# Require the DataMapper, and a Mock Adapter.
require 'pathname'
require Pathname(__FILE__).dirname.expand_path + 'lib/data_mapper'
- require __DIR__ + 'spec/mock_adapter'
+ require __DIR__ + 'spec/lib/mock_adapter'
require 'fileutils'
adapter = ENV["ADAPTER"] || "sqlite3"
-
+
repository_uri = URI.parse case ENV["ADAPTER"]
when 'mysql' then "mysql://localhost/data_mapper_1"
when 'postgres' then "postgres://localhost/data_mapper_1"
@@ -14,14 +14,14 @@
end
DataMapper.setup(:default, "mock://localhost")
-
+
# Determine log path.
ENV['_'] =~ /(\w+)/
log_path = __DIR__ + "log/#{$1 == 'opt' ? 'spec' : $1}.log"
-
+
FileUtils::mkdir_p(File.dirname(log_path))
# FileUtils::rm(log_path) if File.exists?(log_path)
-
+
DataMapper::Logger.new(log_path, 0)
at_exit { DataMapper.logger.close }
View
55 lib/data_mapper.rb
@@ -5,7 +5,7 @@
# * Sets the applications root and environment for compatibility with rails or merb
# * Checks for the database.yml and loads it if it exists
# * Sets up the database using the config from the yaml file or from the environment
-#
+#
# Require the basics...
require 'pathname'
@@ -30,11 +30,60 @@
require __DIR__ + 'data_mapper/support/inflection'
require __DIR__ + 'data_mapper/support/struct'
+require __DIR__ + 'data_mapper/logger'
require __DIR__ + 'data_mapper/dependency_queue'
+require __DIR__ + 'data_mapper/repository'
require __DIR__ + 'data_mapper/resource'
+require __DIR__ + 'data_mapper/query'
require __DIR__ + 'data_mapper/adapters/abstract_adapter'
require __DIR__ + 'data_mapper/cli'
-require __DIR__ + 'data_mapper/scope'
-require __DIR__ + 'data_mapper/query'
+module DataMapper
+ def self.setup(name, uri, options = {})
+ uri = String === uri ? URI.parse(uri) : uri
+
+ raise ArgumentError, "+name+ must be a Symbol, but was #{name.class}", caller unless Symbol === name
+ raise ArgumentError, "+uri+ must be a URI or String, but was #{uri.class}", caller unless URI === uri
+ unless Adapters::const_defined?(DataMapper::Inflection.classify(uri.scheme) + 'Adapter')
+ begin
+ require __DIR__ + "data_mapper/adapters/#{DataMapper::Inflection.underscore(uri.scheme)}_adapter"
+ rescue LoadError
+ require "#{DataMapper::Inflection.underscore(uri.scheme)}_adapter"
+ end
+ end
+
+ adapter = Adapters::const_get(DataMapper::Inflection.classify(uri.scheme) + 'Adapter').new(name, uri)
+
+ Repository.adapters[name] = adapter
+ end
+
+ # ===Block Syntax:
+ # Pushes the named repository onto the context-stack,
+ # yields a new session, and pops the context-stack.
+ #
+ # results = DataMapper.repository(:second_database) do |current_context|
+ # ...
+ # end
+ #
+ # ===Non-Block Syntax:
+ # Returns the current session, or if there is none,
+ # a new Session.
+ #
+ # current_repository = DataMapper.repository
+ def self.repository(name = :default) # :yields: current_context
+ unless block_given?
+ begin
+ Repository.context.last || Repository.new(name)
+ #rescue NoMethodError
+ # raise RepositoryNotSetupError, "#{name.inspect} repository not set up."
+ end
+ else
+ begin
+ return yield(Repository.context.push(Repository.new(name)))
+ ensure
+ Repository.context.pop
+ end
+ end
+ end
+end
View
42 lib/data_mapper/adapters/abstract_adapter.rb
@@ -1,26 +1,10 @@
-require __DIR__.parent + 'loaded_set'
+require __DIR__.parent + 'naming_conventions'
+
module DataMapper
module Adapters
-
class AbstractAdapter
-
- # Instantiate an Adapter by passing it a DataMapper::Repository
- # connection string for configuration.
- def initialize(name, uri_or_options)
- @name = name
- @uri = uri(uri_or_options)
-
- @resource_naming_convention = NamingConventions::UnderscoredAndPluralized
- @field_naming_convention = NamingConventions::Underscored
- end
-
- def batch_insertable?
- false
- end
-
attr_reader :name
- attr_accessor :resource_naming_convention
- attr_accessor :field_naming_convention
+ attr_accessor :resource_naming_convention, :field_naming_convention
# Methods dealing with a single resource object
def create(repository, resource)
@@ -66,12 +50,28 @@ def delete_set(repository, query)
# raise ArgumentError unless block_given?
# end
-
def uri(uri_or_options)
uri_or_options
end
- end # class AbstractAdapter
+ def batch_insertable?
+ false
+ end
+
+ private
+
+ # Instantiate an Adapter by passing it a DataMapper::Repository
+ # connection string for configuration.
+ def initialize(name, uri_or_options)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Symbol === name
+ raise ArgumentError, "+uri_or_options+ should be a Hash, a URI or a String but was #{uri_or_options.class}", caller unless [ Hash, URI, String ].any? { |k| k === uri_or_options }
+
+ @name = name
+ @uri = uri(uri_or_options)
+ @resource_naming_convention = NamingConventions::UnderscoredAndPluralized
+ @field_naming_convention = NamingConventions::Underscored
+ end
+ end # class AbstractAdapter
end # module Adapters
end # module DataMapper
View
141 lib/data_mapper/adapters/data_objects_adapter.rb
@@ -1,4 +1,6 @@
require __DIR__ + 'abstract_adapter'
+require __DIR__.parent + 'loaded_set'
+
begin
require 'fastthread'
rescue LoadError
@@ -47,12 +49,12 @@ def rollback_transaction
end
def within_transaction?
- !Thread::current["doa_#{@uri.scheme}_transaction"].nil?
+ !Thread.current["doa_#{@uri.scheme}_transaction"].nil?
end
def create_connection
if within_transaction?
- Thread::current["doa_#{@uri.scheme}_transaction"]
+ Thread.current["doa_#{@uri.scheme}_transaction"]
else
# DataObjects::Connection.new(uri) will give you back the right
# driver based on the Uri#scheme.
@@ -72,10 +74,9 @@ def create_with_returning?
# Methods dealing with a single resource object
def create(repository, resource)
properties = resource.dirty_attributes
- values = properties.map { |property| resource.instance_variable_get(property.instance_variable_name) }
sql = send(create_with_returning? ? :create_statement_with_returning : :create_statement, resource.class, properties)
-
+ values = properties.map { |property| resource.instance_variable_get(property.instance_variable_name) }
DataMapper.logger.debug { "CREATE: #{sql} PARAMETERS: #{values.inspect}" }
connection = create_connection
@@ -97,14 +98,15 @@ def create(repository, resource)
end
def read(repository, resource, key)
- properties = resource.properties(repository.name).defaults
- properties_with_indexes = Hash[*properties.zip((0...properties.length).to_a).flatten]
+ properties = resource.properties(repository.name).defaults
+ properties_with_indexes = Hash[*properties.zip((0...properties.length).to_a).flatten]
set = LoadedSet.new(repository, resource, properties_with_indexes)
- connection = create_connection
sql = read_statement(resource, key)
DataMapper.logger.debug { sql }
+
+ connection = create_connection
command = connection.create_command(sql)
command.set_types(properties.map { |property| property.primitive })
reader = command.execute_reader(*key)
@@ -120,11 +122,10 @@ def read(repository, resource, key)
def update(repository, resource)
properties = resource.dirty_attributes
- values = properties.map { |property| resource.instance_variable_get(property.instance_variable_name) }
sql = update_statement(resource.class, properties)
+ values = properties.map { |property| resource.instance_variable_get(property.instance_variable_name) }
parameters = (values + resource.key)
-
DataMapper.logger.debug { "UPDATE: #{sql} PARAMETERS: #{parameters.inspect}" }
connection = create_connection
@@ -138,10 +139,11 @@ def update(repository, resource)
end
def delete(repository, resource)
+ key = resource.class.key(name).map { |property| resource.instance_variable_get(property.instance_variable_name) }
+
connection = create_connection
command = connection.create_command(delete_statement(resource.class))
- key = resource.class.key(name).map { |property| resource.instance_variable_get(property.instance_variable_name) }
affected_rows = command.execute_non_query(*key).to_i
close_connection(connection)
@@ -151,14 +153,13 @@ def delete(repository, resource)
# Methods dealing with finding stuff by some query parameters
def read_set(repository, query)
- properties = query.fields
- properties_with_indexes = Hash[*properties.zip((0...properties.length).to_a).flatten]
+ properties = query.fields
+ properties_with_indexes = Hash[*properties.zip((0...properties.length).to_a).flatten]
set = LoadedSet.new(repository, query.model, properties_with_indexes)
sql = query_read_statement(query)
parameters = query.parameters
-
DataMapper.logger.debug { "READ_SET: #{sql} PARAMETERS: #{parameters.inspect}" }
connection = create_connection
@@ -188,25 +189,23 @@ def delete_set(repository, query)
# Database-specific method
def execute(sql, *args)
- db = create_connection
-
DataMapper.logger.debug { "EXECUTE: #{sql} PARAMETERS: #{args.inspect}" }
- command = db.create_command(sql)
+ connection = create_connection
+ command = connection.create_command(sql)
return command.execute_non_query(*args)
rescue => e
DataMapper.logger.error { e } if DataMapper.logger
raise e
ensure
- db.close if db
+ connection.close if connection
end
def query(sql, *args)
- db = create_connection
-
DataMapper.logger.debug { "QUERY: #{sql} PARAMETERS: #{args.inspect}" }
- command = db.create_command(sql)
+ connection = create_connection
+ command = connection.create_command(sql)
reader = command.execute_reader(*args)
results = []
@@ -230,11 +229,7 @@ def query(sql, *args)
raise e
ensure
reader.close if reader
- db.close if db
- end
-
- def empty_insert_sql
- "DEFAULT VALUES"
+ connection.close if connection
end
# This model is just for organization. The methods are included into the Adapter below.
@@ -331,14 +326,14 @@ def query_read_statement(query)
# allows for "foreign" properties to be qualified correctly
model_name = property.model.resource_name(property.model.repository.name)
case operator
- when :eql, :in then equality_operator(query,model_name,operator, property, qualify, value)
- when :not then inequality_operator(query,model_name,operator, property, qualify, value)
- when :like then "#{property_to_column_name(model_name, property, qualify)} LIKE ?"
- when :gt then "#{property_to_column_name(model_name, property, qualify)} > ?"
- when :gte then "#{property_to_column_name(model_name, property, qualify)} >= ?"
- when :lt then "#{property_to_column_name(model_name, property, qualify)} < ?"
- when :lte then "#{property_to_column_name(model_name, property, qualify)} <= ?"
- else raise "CAN HAS CRASH?"
+ when :eql, :in then equality_operator(query,model_name,operator, property, qualify, value)
+ when :not then inequality_operator(query,model_name,operator, property, qualify, value)
+ when :like then "#{property_to_column_name(model_name, property, qualify)} LIKE ?"
+ when :gt then "#{property_to_column_name(model_name, property, qualify)} > ?"
+ when :gte then "#{property_to_column_name(model_name, property, qualify)} >= ?"
+ when :lt then "#{property_to_column_name(model_name, property, qualify)} < ?"
+ when :lte then "#{property_to_column_name(model_name, property, qualify)} <= ?"
+ else raise "CAN HAS CRASH?"
end
end.join(') AND (') << ")"
end
@@ -346,8 +341,8 @@ def query_read_statement(query)
unless query.order.empty?
parts = []
query.order.each do |item|
- parts << item.name if item.is_a?(DataMapper::Property)
- parts << "#{item.property.name} #{item.direction}" if item.is_a?(DataMapper::Query::Direction)
+ parts << item.name if DataMapper::Property === item
+ parts << "#{item.property.name} #{item.direction}" if DataMapper::Query::Direction === item
end
sql << " ORDER BY #{parts.join(', ')}"
end
@@ -360,23 +355,23 @@ def query_read_statement(query)
def equality_operator(query, model_name, operator, property, qualify, value)
case value
- when Array then "#{property_to_column_name(model_name, property, qualify)} IN ?"
- when NilClass then "#{property_to_column_name(model_name, property, qualify)} IS NULL"
- when DataMapper::Query then
- query.merge_sub_select_conditions(operator, property, value)
- "#{property_to_column_name(model_name, property, qualify)} IN (#{query_read_statement(value)})"
- else "#{property_to_column_name(model_name, property, qualify)} = ?"
+ when Array then "#{property_to_column_name(model_name, property, qualify)} IN ?"
+ when NilClass then "#{property_to_column_name(model_name, property, qualify)} IS NULL"
+ when DataMapper::Query then
+ query.merge_sub_select_conditions(operator, property, value)
+ "#{property_to_column_name(model_name, property, qualify)} IN (#{query_read_statement(value)})"
+ else "#{property_to_column_name(model_name, property, qualify)} = ?"
end
end
def inequality_operator(query, model_name, operator, property, qualify, value)
case value
- when Array then "#{property_to_column_name(model_name, property, qualify)} NOT IN ?"
- when NilClass then "#{property_to_column_name(model_name, property, qualify)} IS NOT NULL"
- when DataMapper::Query then
- query.merge_sub_select_conditions(operator, property, value)
- "#{property_to_column_name(model_name, property, qualify)} NOT IN (#{query_read_statement(value)})"
- else "#{property_to_column_name(model_name, property, qualify)} <> ?"
+ when Array then "#{property_to_column_name(model_name, property, qualify)} NOT IN ?"
+ when NilClass then "#{property_to_column_name(model_name, property, qualify)} IS NOT NULL"
+ when DataMapper::Query then
+ query.merge_sub_select_conditions(operator, property, value)
+ "#{property_to_column_name(model_name, property, qualify)} NOT IN (#{query_read_statement(value)})"
+ else "#{property_to_column_name(model_name, property, qualify)} <> ?"
end
end
@@ -391,43 +386,49 @@ def property_to_column_name(model_name, property, qualify)
include SQL
- # Adapters requiring a RETURNING syntax for create statements
- # should overwrite this to return true.
- def syntax_returning?
- false
- end
-
- def quote_table_name(table_name)
- "\"#{table_name.gsub('"', '""')}\""
- end
-
- def quote_column_name(column_name)
- "\"#{column_name.gsub('"', '""')}\""
- end
-
def uri(uri_or_options)
- return uri_or_options if uri_or_options.is_a?(URI)
-
+ uri_or_options = URI.parse(uri_or_options) if String === uri_or_options
+ return uri_or_options if URI === uri_or_options
+
adapter = uri_or_options.delete(:adapter)
user = uri_or_options.delete(:user)
-
+
password = uri_or_options.delete(:password)
password = ":" << password.to_s if user && password
-
+
host = uri_or_options.delete(:host)
host = "@" << host.to_s if user && host
-
+
port = uri_or_options.delete(:port)
port = ":" << port.to_s if host && port
-
+
database = "/#{uri_or_options.delete(:database)}"
-
+
query = uri_or_options.to_a.map { |pair| pair.join('=') }.join('&')
query = "?" << query unless query.empty?
-
+
URI.parse("#{adapter}://#{user}#{password}#{host}#{port}#{database}#{query}")
end
- end # class DoAdapter
+ private
+
+ def empty_insert_sql
+ "DEFAULT VALUES"
+ end
+
+ # Adapters requiring a RETURNING syntax for create statements
+ # should overwrite this to return true.
+ def syntax_returning?
+ false
+ end
+
+ def quote_table_name(table_name)
+ "\"#{table_name.gsub('"', '""')}\""
+ end
+
+ def quote_column_name(column_name)
+ "\"#{column_name.gsub('"', '""')}\""
+ end
+ end # class DoAdapter
end # module Adapters
end # module DataMapper
View
2  lib/data_mapper/adapters/mysql_adapter.rb
@@ -9,6 +9,7 @@ module Adapters
# Options:
# host, user, password, database (path), socket(uri query string), port
class MysqlAdapter < DataObjectsAdapter
+ private
def quote_table_name(table_name)
"`#{table_name}`"
@@ -18,6 +19,5 @@ def quote_column_name(column_name)
"`#{column_name}`"
end
end # class MysqlAdapter
-
end # module Adapters
end # module DataMapper
View
8 lib/data_mapper/adapters/postgres_adapter.rb
@@ -3,12 +3,12 @@
module DataMapper
module Adapters
-
+
class PostgresAdapter < DataObjectsAdapter
-
+
def create_with_returning?; true; end
-
+
end # class PostgresAdapter
-
+
end # module Adapters
end # module DataMapper
View
36 lib/data_mapper/associations.rb
@@ -5,10 +5,6 @@
module DataMapper
module Associations
- def relationships
- @relationships ||= {}
- end
-
def self.extended(base)
base.send(:extend, ManyToOne)
base.send(:extend, OneToMany)
@@ -16,24 +12,34 @@ def self.extended(base)
base.send(:extend, OneToOne)
end
+ def relationships
+ @relationships ||= {}
+ end
+
def n
1.0/0
end
def has(name, cardinality, options = {})
case cardinality
- when Range then
- if cardinality.first == 0
- if cardinality.last == (1.0/0)
- one_to_many(name, options)
- else
- one_to_many(name, options.merge(:max => cardinality.last))
- end
- elsif cardinality.first == (1.0/0)
- many_to_many(name, options)
+ when Range
+ min, max = cardinality.first, cardinality.last
+ case min
+ when 0, 1 # 0..n, 1..n
+ one_to_many(name, options.merge(:min => min, :max => max))
+ when Fixnum, Bignum, n
+ case max
+ when 0, 1 # n..0, n..1
+ many_to_one(name, options.merge(:min => min, :max => max))
+ when Fixnum, Bignum, n # n..2, n..n
+ many_to_many(name, options.merge(:min => min, :max => max))
+ end
end
- when Fixnum then one_to_one(name, options)
- end
+ when 1
+ one_to_one(name, options)
+ when Fixnum, Bignum, n
+ one_to_many(name, options.merge(:min => cardinality, :max => cardinality))
+ end || raise(ArgumentError, "Cardinality #{cardinality.inspect} (#{cardinality.class}) not handled")
end
end # module Associations
end # module DataMapper
View
14 lib/data_mapper/associations/many_to_many.rb
@@ -5,14 +5,18 @@ module DataMapper
module Associations
module ManyToMany
def many_to_many(name, options = {})
- target = options[:class_name] || DataMapper::Inflection.camelize(name)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Symbol === name
+ raise ArgumentError, "+options+ should be a Hash, but was #{options.class}", caller unless Hash === options
+
+ child_model_name = DataMapper::Inflection.demodulize(self.name)
+ parent_model_name = options[:class_name] || DataMapper::Inflection.classify(name)
relationships[name] = Relationship.new(
name,
- options[:repository_name] || self.repository.name,
- DataMapper::Inflection.demodulize(self.name),
+ options[:repository_name] || repository.name,
+ child_model_name,
nil,
- target,
+ parent_model_name,
nil
)
end
@@ -24,7 +28,7 @@ def save
raise NotImplementedError
end
- end # class Instance
+ end # class Instance
end # module ManyToMany
end # module Associations
end # module DataMapper
View
54 lib/data_mapper/associations/many_to_one.rb
@@ -1,4 +1,3 @@
-#require __DIR__.parent + 'support/class'
require __DIR__.parent + 'associations'
require __DIR__ + 'relationship'
@@ -6,14 +5,18 @@ module DataMapper
module Associations
module ManyToOne
def many_to_one(name, options = {})
- target = options[:class_name] || DataMapper::Inflection.camelize(name)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Symbol === name
+ raise ArgumentError, "+options+ should be a Hash, but was #{options.class}", caller unless Hash === options
+
+ child_model_name = DataMapper::Inflection.demodulize(self.name)
+ parent_model_name = options[:class_name] || DataMapper::Inflection.classify(name)
relationships[name] = Relationship.new(
name,
options[:repository_name] || repository.name,
- DataMapper::Inflection.demodulize(self.name),
+ child_model_name,
nil,
- target,
+ parent_model_name,
nil
)
@@ -30,9 +33,10 @@ def #{name}=(value)
def #{name}_association
@#{name}_association ||= begin
- association = self.class.relationships[:#{name}].
- with_child(self, Instance) do |repository, child_rel, parent_rel, parent_res, child|
- repository.all(parent_res, parent_rel.to_query(child_rel.get(child))).first
+ relationship = self.class.relationships[:#{name}]
+
+ association = relationship.with_child(self, Instance) do |repository, child_key, parent_key, parent_model, child_resource|
+ repository.all(parent_model, parent_key.to_query(child_key.get(child_resource))).first
end
child_associations << association
@@ -41,36 +45,42 @@ def #{name}_association
end
end
EOS
+
+ relationships[name]
end
class Instance
- def initialize(relationship, child, parent_loader)
- @relationship, @child = relationship, child
- @parent_loader = parent_loader
- end
-
def parent
- @parent ||= @parent_loader.call
+ @parent_resource ||= @parent_loader.call
end
- def parent=(parent)
- @parent = parent
+ def parent=(parent_resource)
+ @parent_resource = parent_resource
- @relationship.attach_parent(@child, @parent) if @parent.nil? || ! @parent.new_record?
+ @relationship.attach_parent(@child_resource, @parent_resource) if @parent_resource.nil? || !@parent_resource.new_record?
end
def loaded?
- ! @parent.nil?
+ !defined?(@parent_resource)
end
def save
- if @parent.new_record?
- repo = repository(@relationship.repository_name)
- repo.save(@parent)
-
- @relationship.attach_parent(@child, @parent)
+ if parent.new_record?
+ repository(@relationship.repository_name).save(parent)
+ @relationship.attach_parent(@child_resource, parent)
end
end
+
+ private
+
+ def initialize(relationship, child_resource, &parent_loader)
+# raise ArgumentError, "+relationship+ should be a DataMapper::Association::Relationship, but was #{relationship.class}", caller unless Relationship === relationship
+# raise ArgumentError, "+child_resource+ should be a DataMapper::Resource, but was #{child_resource.class}", caller unless Resource === child_resource
+
+ @relationship = relationship
+ @child_resource = child_resource
+ @parent_loader = parent_loader
+ end
end # class Instance
end # module ManyToOne
end # module Associations
View
79 lib/data_mapper/associations/one_to_many.rb
@@ -7,15 +7,18 @@ module DataMapper
module Associations
module OneToMany
def one_to_many(name, options = {})
- source = options[:class_name] || DataMapper::Inflection.classify(name)
- model_name = DataMapper::Inflection.demodulize(self.name)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Symbol === name
+ raise ArgumentError, "+options+ should be a Hash, but was #{options.class}", caller unless Hash === options
+
+ child_model_name = options[:class_name] || DataMapper::Inflection.classify(name)
+ parent_model_name = DataMapper::Inflection.demodulize(self.name)
relationships[name] = Relationship.new(
- DataMapper::Inflection.underscore(model_name).to_sym,
+ DataMapper::Inflection.underscore(parent_model_name).to_sym,
options[:repository_name] || repository.name,
- source,
+ child_model_name,
nil,
- model_name,
+ parent_model_name,
nil
)
@@ -28,10 +31,11 @@ def #{name}
def #{name}_association
@#{name}_association ||= begin
- association = self.class.relationships[:#{name}].
- with_parent(self, Instance) do |repository, child_rel, parent_rel, child_res, parent|
- repository.all(child_res, child_rel.to_query(parent_rel.get(parent)))
- end
+ relationship = self.class.relationships[:#{name}]
+
+ association = relationship.with_parent(self, Instance) do |repository, child_key, parent_key, child_model, parent_resource|
+ repository.all(child_model, child_key.to_query(parent_key.get(parent_resource)))
+ end
parent_associations << association
@@ -39,6 +43,8 @@ def #{name}_association
end
end
EOS
+
+ relationships[name]
end
class Instance
@@ -46,47 +52,56 @@ class Instance
def_delegators :children, :[], :size, :length, :first, :last
- def initialize(relationship, parent, loader)
- @relationship = relationship
- @loader = loader
- @parent = parent
- @dirty_children = []
- end
-
def children
- @children ||= @loader.call
+ @children_resources ||= @children_loader.call
end
def save
- @dirty_children.each do |c|
- @relationship.attach_parent(c, @parent)
- repository(@relationship.repository_name).save(c)
+ @dirty_children.each do |child_resource|
+ @relationship.attach_parent(child_resource, @parent_resource)
+ repository(@relationship.repository_name).save(child_resource)
end
end
- def <<(child)
- (@children ||= []) << child
+ def push(*child_resources)
+ child_resources.each do |child_resource|
+ children << child_resource
- if @parent.new_record?
- @dirty_children << child
- else
- @relationship.attach_parent(child, @parent)
- repository(@relationship.repository_name).save(child)
+ if @parent_resource.new_record?
+ @dirty_children << child_resource
+ else
+ @relationship.attach_parent(child_resource, @parent_resource)
+ repository(@relationship.repository_name).save(child_resource)
+ end
end
self
end
- def delete(child)
- deleted = children.delete(child)
+ alias << push
+
+ def delete(child_resource)
+ deleted_resource = children.delete(child_resource)
begin
- @relationship.attach_parent(deleted, nil)
- repository(@relationship.repository_name).save(deleted)
+ @relationship.attach_parent(deleted_resource, nil)
+ repository(@relationship.repository_name).save(deleted_resource)
rescue
- children.push(child)
+ children << child_resource
raise
end
end
+
+ private
+
+ def initialize(relationship, parent_resource, &children_loader)
+# raise ArgumentError, "+relationship+ should be a DataMapper::Association::Relationship, but was #{relationship.class}", caller unless Relationship === relationship
+# raise ArgumentError, "+parent_resource+ should be a DataMapper::Resource, but was #{parent_resource.class}", caller unless Resource === parent_resource
+
+ @relationship = relationship
+ @parent_resource = parent_resource
+ @children_loader = children_loader
+ @dirty_children = []
+ end
end # class Instance
end # module OneToMany
end # module Associations
View
24 lib/data_mapper/associations/one_to_one.rb
@@ -4,15 +4,18 @@ module DataMapper
module Associations
module OneToOne
def one_to_one(name, options = {})
- child = options[:class_name] || DataMapper::Inflection.classify(name)
- model_name = DataMapper::Inflection.demodulize(self.name)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Symbol === name
+ raise ArgumentError, "+options+ should be a Hash, but was #{options.class}", caller unless Hash === options
+
+ child_model_name = options[:class_name] || DataMapper::Inflection.classify(name)
+ parent_model_name = DataMapper::Inflection.demodulize(self.name)
relationships[name] = Relationship.new(
- DataMapper::Inflection.underscore(model_name).to_sym,
+ DataMapper::Inflection.underscore(parent_model_name).to_sym,
options[:repository_name] || repository.name,
- child,
+ child_model_name,
nil,
- model_name,
+ parent_model_name,
nil
)
@@ -32,10 +35,11 @@ def #{name}=(value)
def #{name}_association
@#{name}_association ||= begin
- association = self.class.relationships[:#{name}].
- with_parent(self, Associations::OneToMany::Instance) do |repository, child_rel, parent_rel, child_res, parent|
- repository.all(child_res, child_rel.to_query(parent_rel.get(parent)))
- end
+ relationship = self.class.relationships[:#{name}]
+
+ association = relationship.with_parent(self, Associations::OneToMany::Instance) do |repository, child_key, parent_key, child_model, parent_resource|
+ repository.all(child_model, child_key.to_query(parent_key.get(parent_resource)))
+ end
parent_associations << association
@@ -43,6 +47,8 @@ def #{name}_association
end
end
EOS
+
+ relationships[name]
end
end # module HasOne
View
55 lib/data_mapper/associations/parent_to_child_association.rb
@@ -7,47 +7,56 @@ class ParentToChildAssociation
def_delegators :children, :[], :size, :length, :first, :last
- def initialize(relationship, parent, loader)
- @relationship = relationship
- @loader = loader
- @parent = parent
- @dirty_children = []
- end
-
def children
- @children ||= @loader.call
+ @children_resources ||= @children_loader.call
end
def save
- @dirty_children.each do |c|
- @relationship.attach_parent(c, @parent)
- repository(@relationship.repository_name).save(c)
+ @dirty_children.each do |child_resource|
+ @relationship.attach_parent(child_resource, @parent_resource)
+ repository(@relationship.repository_name).save(child_resource)
end
end
- def <<(child)
- (@children ||= []) << child
+ def push(*child_resources)
+ child_resources.each do |child_resource|
+ children << child_resource
- if @parent.new_record?
- @dirty_children << child
- else
- @relationship.attach_parent(child, @parent)
- repository(@relationship.repository_name).save(child)
+ if @parent_resource.new_record?
+ @dirty_children << child_resource
+ else
+ @relationship.attach_parent(child_resource, @parent_resource)
+ repository(@relationship.repository_name).save(child_resource)
+ end
end
self
end
- def delete(child)
- deleted = children.delete(child)
+ alias << push
+
+ def delete(child_resource)
+ deleted_resource = children.delete(child_resource)
begin
- @relationship.attach_parent(deleted, nil)
- repository(@relationship.repository_name).save(deleted)
+ @relationship.attach_parent(deleted_resource, nil)
+ repository(@relationship.repository_name).save(deleted_resource)
rescue
- children.push(child)
+ children << child_resource
raise
end
end
+
+ private
+
+ def initialize(relationship, parent_resource, &children_loader)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Relationsip === relationship
+ raise ArgumentError, "+parent_resource+ should be a Resource, but was #{parent_resource.class}", caller unless Resource === parent_resource
+
+ @relationship = relationship
+ @parent_resource = parent_resource
+ @children_loader = children_loader
+ @dirty_children = []
+ end
end # class ParentToChildAssociation
end # module Associations
end # module DataMapper
View
73 lib/data_mapper/associations/relationship.rb
@@ -4,30 +4,6 @@ class Relationship
attr_reader :name, :repository_name
- # +child_model and child_properties refers to the FK, parent_model
- # and parent_properties refer to the PK. For more information:
- # http://edocs.bea.com/kodo/docs41/full/html/jdo_overview_mapping_join.html
- # I wash my hands of it!
-
- # FIXME: should we replace child_* and parent_* arguments with two
- # Arrays of Property objects? This would allow syntax like:
- #
- # belongs_to = DataMapper::Associations::Relationship.new(
- # :manufacturer,
- # :relationship_spec,
- # Vehicle.properties.slice(:manufacturer_id)
- # Manufacturer.properties.slice(:id)
- # )
- def initialize(name, repository_name, child_model, child_properties, parent_model, parent_properties, &loader)
- @name = name
- @repository_name = repository_name
- @child_model = child_model
- @child_properties = child_properties # may be nil
- @parent_model = parent_model
- @parent_properties = parent_properties # may be nil
- @loader = loader
- end
-
def child_key
@child_key ||= begin
model_properties = child_model.properties(@repository_name)
@@ -57,15 +33,15 @@ def parent_key
end
def with_child(child_resource, association, &loader)
- association.new(self, child_resource, lambda {
- loader.call(repository(@repository_name), child_key, parent_key, parent_model, child_resource)
- })
+ association.new(self, child_resource) do
+ yield repository(@repository_name), child_key, parent_key, parent_model, child_resource
+ end
end
def with_parent(parent_resource, association, &loader)
- association.new(self, parent_resource, lambda {
- loader.call(repository(@repository_name), child_key, parent_key, child_model, parent_resource)
- })
+ association.new(self, parent_resource) do
+ yield repository(@repository_name), child_key, parent_key, child_model, parent_resource
+ end
end
def attach_parent(child, parent)
@@ -73,11 +49,44 @@ def attach_parent(child, parent)
end
def parent_model
- @parent_model.to_class
+ @parent_model_name.to_class
end
def child_model
- @child_model.to_class
+ @child_model_name.to_class
+ end
+
+ private
+
+ # +child_model_name and child_properties refers to the FK, parent_model_name
+ # and parent_properties refer to the PK. For more information:
+ # http://edocs.bea.com/kodo/docs41/full/html/jdo_overview_mapping_join.html
+ # I wash my hands of it!
+
+ # FIXME: should we replace child_* and parent_* arguments with two
+ # Arrays of Property objects? This would allow syntax like:
+ #
+ # belongs_to = DataMapper::Associations::Relationship.new(
+ # :manufacturer,
+ # :relationship_spec,
+ # Vehicle.properties.slice(:manufacturer_id)
+ # Manufacturer.properties.slice(:id)
+ # )
+ def initialize(name, repository_name, child_model_name, child_properties, parent_model_name, parent_properties, &loader)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Symbol === name
+ raise ArgumentError, "+repository_name+ must be a Symbol, but was #{repository_name.class}", caller unless Symbol === repository_name
+ raise ArgumentError, "+child_model_name+ must be a String, but was #{child_model_name.class}", caller unless String === child_model_name
+ raise ArgumentError, "+child_properties+ must be an Array or nil, but was #{child_properties.class}", caller unless Array === child_properties || child_properties.nil?
+ raise ArgumentError, "+parent_model_name+ must be a String, but was #{parent_model_name.class}", caller unless String === parent_model_name
+ raise ArgumentError, "+parent_properties+ must be an Array or nil, but was #{parent_properties.class}", caller unless Array === parent_properties || parent_properties.nil?
+
+ @name = name
+ @repository_name = repository_name
+ @child_model_name = child_model_name
+ @child_properties = child_properties # may be nil
+ @parent_model_name = parent_model_name
+ @parent_properties = parent_properties # may be nil
+ @loader = loader
end
end # class Relationship
end # module Associations
View
11 lib/data_mapper/auto_migrations.rb
@@ -4,7 +4,7 @@ def auto_migrate!
if self::subclasses.empty?
table = repository.table(self)
table.activate_associations!
-
+
table.create!(true)
else
schema = repository.schema
@@ -17,18 +17,19 @@ def auto_migrate!
columns.each do |column|
table.add_column(column.name, column.type, column.options)
end
-
+
table.activate_associations!
-
+
return table.create!(true)
end
end
-
+
private
+
def create_table(table)
raise NotImplementedError
end
-
+
def modify_table(table, columns)
raise NotImplementedError
end
View
13 lib/data_mapper/dependency_queue.rb
@@ -12,15 +12,14 @@ def add(class_name, &b)
def resolve!
@dependencies.each_pair do |class_name, callbacks|
- if Object.const_defined?(class_name)
- klass = Object.const_get(class_name)
+ next unless Object.const_defined?(class_name)
+ klass = Object.const_get(class_name)
- callbacks.each do |b|
- b.call(klass)
- end
-
- callbacks.clear
+ callbacks.each do |b|
+ b.call(klass)
end
+
+ callbacks.clear
end
end
View
28 lib/data_mapper/identity_map.rb
@@ -1,18 +1,8 @@
module DataMapper
-
+
# Tracks objects to help ensure that each object gets loaded only once.
# See: http://www.martinfowler.com/eaaCatalog/identityMap.html
class IdentityMap
-
- def initialize(second_level_cache = nil)
- @second_level_cache = second_level_cache
- @cache = if second_level_cache.nil?
- Hash.new { |h,k| h[k] = Hash.new }
- else
- Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = @second_level_cache.get(k, k2) } }
- end
- end
-
# Pass a Class and a key, and to retrieve a resource.
# If the resource isn't found, nil is returned.
def get(model, key)
@@ -22,7 +12,7 @@ def get(model, key)
# Pass a resource to add it to the IdentityMap.
# The resource must have an assigned key.
def set(resource)
- # TODO could we not cause a nasty bug by dropping nil value keys when the
+ # TODO could we not cause a nasty bug by dropping nil value keys when the
# user is using composite keys? Should we not rather raise an error if
# the value is nil?
key = resource.key
@@ -36,11 +26,21 @@ def set(resource)
def delete(model, key)
@cache[model].delete(key)
end
-
+
# Clears a particular set of classes from the IdentityMap.
def clear!(model)
@cache.delete(model)
end
-
+
+ private
+
+ def initialize(second_level_cache = nil)
+ @second_level_cache = second_level_cache
+ @cache = if second_level_cache.nil?
+ Hash.new { |h,k| h[k] = Hash.new }
+ else
+ Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = @second_level_cache.get(k, k2) } }
+ end
+ end
end # class IdentityMap
end # module DataMapper
View
4 lib/data_mapper/is/tree.rb
@@ -1,3 +1,5 @@
+# TODO: move into dm-more
+
module DataMapper
module Is
module Tree
@@ -62,7 +64,7 @@ module ClassMethods
# * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
def is_a_tree(options = {})
configuration = { :foreign_key => "parent_id" }
- configuration.update(options) if options.is_a?(Hash)
+ configuration.update(options) if Hash === options
belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order]
View
58 lib/data_mapper/loaded_set.rb
@@ -1,28 +1,7 @@
-require 'set'
-
module DataMapper
-
class LoadedSet
-
attr_reader :repository
- # +properties_with_indexes+ is a Hash of Property and values Array index pairs.
- # { Property<:id> => 1, Property<:name> => 2, Property<:notes> => 3 }
- def initialize(repository, model, properties_with_indexes)
- @repository = repository
- @model = model
- @properties_with_indexes = properties_with_indexes
- @entries = []
-
- if inheritance_property = @model.inheritance_property(@repository.name)
- @inheritance_property_index = @properties_with_indexes[inheritance_property]
- end
-
- if (@key_properties = @model.key(@repository.name)).all? { |key| @properties_with_indexes.include?(key) }
- @key_property_indexes = @properties_with_indexes.values_at(*@key_properties)
- end
- end
-
def keys
entry_keys = @entries.map { |resource| resource.key }
@@ -86,23 +65,35 @@ def first
@entries.first
end
- # FIXME: Array#uniq! is really expensive. Is there any way we can
- # avoid doing this, or at least minimize how often this method is
- # called?
def entries
@entries.uniq!
@entries.dup
end
- end # class LoadedSet
- class LazyLoadedSet < LoadedSet
+ private
- def initialize(*args, &block)
- raise "LazyLoadedSets require a materialization block. Use a LoadedSet instead." unless block_given?
- super(*args)
- @loader = block
+ # +properties_with_indexes+ is a Hash of Property and values Array index pairs.
+ # { Property<:id> => 1, Property<:name> => 2, Property<:notes> => 3 }
+ def initialize(repository, model, properties_with_indexes)
+ raise ArgumentError, "+repository+ must be a DataMapper::Repository, but was #{repository.class}", caller unless Repository === repository
+ raise ArgumentError, "+model+ is a #{model.class}, but is not a type of Resource", caller unless Resource === model
+
+ @repository = repository
+ @model = model
+ @properties_with_indexes = properties_with_indexes
+ @entries = []
+
+ if inheritance_property = @model.inheritance_property(@repository.name)
+ @inheritance_property_index = @properties_with_indexes[inheritance_property]
+ end
+
+ if (@key_properties = @model.key(@repository.name)).all? { |key| @properties_with_indexes.include?(key) }
+ @key_property_indexes = @properties_with_indexes.values_at(*@key_properties)
+ end
end
+ end # class LoadedSet
+ class LazyLoadedSet < LoadedSet
def each(&block)
entries.each { |entry| yield entry }
end
@@ -119,5 +110,12 @@ def entries
super
end
+ private
+
+ def initialize(*args, &block)
+ raise "LazyLoadedSets require a materialization block. Use a LoadedSet instead." unless block_given?
+ super(*args)
+ @loader = block
+ end
end # class LazyLoadedSet
end # module DataMapper
View
4 lib/data_mapper/logger.rb
@@ -167,7 +167,7 @@ def close
# The string message to be logged
# block<&block>
# An optional block that will be evaluated and added to the logging message after the string message.
- def <<(string = nil)
+ def push(string = nil)
message = Time.now.httpdate
message << delimiter
message << string if string
@@ -179,7 +179,7 @@ def <<(string = nil)
@buffer << message
flush # Force a flush for now until we figure out where we want to use the buffering.
end
- alias :push :<<
+ alias << push
# Generate the following logging methods for DataMapper.logger as described in the api:
# :fatal, :error, :warn, :info, :debug
View
127 lib/data_mapper/property.rb
@@ -2,6 +2,10 @@
require __DIR__ + 'type'
Dir[__DIR__ + 'types/*.rb'].each { |path| require path }
+unless defined?(DM)
+ DM = DataMapper::Types
+end
+
require 'date'
require 'time'
require 'bigdecimal'
@@ -66,7 +70,7 @@ module DataMapper
# property :body, DataMapper::Types::Text, :accessor => :protected # Both reader and writer are protected
# end
#
-# Access control is also analogous to Ruby getters, setters, and accessors, and can
+# Access control is also analogous to Ruby accessors and mutators, and can
# be declared using :reader and :writer, in addition to :accessor.
#
# class Post
@@ -216,13 +220,51 @@ class Property
attr_reader :primitive, :model, :name, :instance_variable_name, :type, :reader_visibility, :writer_visibility, :getter, :options
+ def field
+ @field ||= @options.fetch(:field, repository.adapter.field_naming_convention.call(name))
+ end
+
+ def lazy?
+ @lazy
+ end
+
+ def key?
+ @key
+ end
+
+ def serial?
+ @serial
+ end
+
+ def lock?
+ @lock
+ end
+
+ def custom?
+ @custom
+ end
+
+ def get(resource)
+ resource[@name]
+ end
+
+ def set(value, resource)
+ resource[@name] = value
+ end
+
+ def inspect
+ "#<Property:#{@model}:#{@name}>"
+ end
+
+ private
+
def initialize(model, name, type, options)
raise ArgumentError, "+model+ is a #{model.class}, but is not a type of Resource" unless Resource === model
- raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}" unless name.is_a?(Symbol)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}" unless Symbol === name
raise ArgumentError, "+type+ was #{type.class}, which is not a supported type: #{TYPES * ', '}" unless TYPES.include?(type) || (type.respond_to?(:ancestors) && type.ancestors.include?(DataMapper::Type) && TYPES.include?(type.primitive))
- if (unknown_keys = options.keys - PROPERTY_OPTIONS).any?
- raise ArgumentError, "options contained unknown keys: #{unknown_keys * ', '}"
+ if (unknown_options = options.keys - PROPERTY_OPTIONS).any?
+ raise ArgumentError, "options contained unknown keys: #{unknown_options * ', '}"
end
@model = model
@@ -231,7 +273,7 @@ def initialize(model, name, type, options)
@custom = @type.ancestors.include?(DataMapper::Type)
@options = @custom ? @type.options.merge(options) : options
@instance_variable_name = "@#{@name}"
- @getter = @type.is_a?(TrueClass) ? "#{@name}?".to_sym : @name
+ @getter = TrueClass == @type ? "#{@name}?".to_sym : @name
# TODO: This default should move to a DataMapper::Types::Text Custom-Type
# and out of Property.
@@ -242,22 +284,15 @@ def initialize(model, name, type, options)
@serial = @options.fetch(:serial, false)
@key = (@options[:key] || @serial) == true
- validate_options!
- determine_visibility!
+ determine_visibility
- create_getter!
- create_setter!
+ create_getter
+ create_setter
@model.auto_generate_validations(self) if @model.respond_to?(:auto_generate_validations)
end
- def validate_options! # :nodoc:
- @options.each_pair do |k,v|
- raise ArgumentError, "#{k.inspect} is not a supported option in DataMapper::Property::PROPERTY_OPTIONS" unless PROPERTY_OPTIONS.include?(k)
- end
- end
-
- def determine_visibility! # :nodoc:
+ def determine_visibility # :nodoc:
@reader_visibility = @options[:reader] || @options[:accessor] || :public
@writer_visibility = @options[:writer] || @options[:accessor] || :public
@writer_visibility = :protected if @options[:protected]
@@ -266,70 +301,34 @@ def determine_visibility! # :nodoc:
end
# defines the getter for the property
- def create_getter!
+ def create_getter
@model.class_eval <<-EOS, __FILE__, __LINE__
#{reader_visibility}
- def #{name}
- self[#{name.inspect}]
+ def #{@getter}
+ unless @new_record || defined?(#{@instance_variable_name})
+ if @loaded_set
+ @loaded_set.reload!(:fields => self.class.properties(self.class.repository.name).lazy_load_context(#{name.inspect}))
+ end
+ end
+ #{custom? ? "#{@type.inspect}.load(#{@instance_variable_name})" : @instance_variable_name}
end
EOS
-
- if type == TrueClass
- @model.class_eval <<-EOS, __FILE__, __LINE__
- #{reader_visibility}
- alias #{name}? #{name}
- EOS
- end
rescue SyntaxError
raise SyntaxError, name
end
# defines the setter for the property
- def create_setter!
+ def create_setter
@model.class_eval <<-EOS, __FILE__, __LINE__
#{writer_visibility}
def #{name}=(value)
- self[#{name.inspect}] = value
+ #{lock? ? "@shadow_#{name} = #{@instance_variable_name}" : ''}
+ dirty_attributes << self.class.properties(repository.name)[#{name.inspect}]
+ #{@instance_variable_name} = #{custom? ? "#{@type.inspect}.dump(value)" : 'value'}
end
EOS
rescue SyntaxError
raise SyntaxError, name
end
-
- def field
- @field ||= @options.fetch(:field, repository.adapter.field_naming_convention.call(name))
- end
-
- def lazy?
- @lazy
- end
-
- def key?
- @key
- end
-
- def serial?
- @serial
- end
-
- def lock?
- @lock
- end
-
- def custom?
- @custom
- end
-
- def get(resource)
- resource[@name]
- end
-
- def set(value, resource)
- resource[@name] = value
- end
-
- def inspect
- "#<Property:#{@model}:#{@name}>"
- end
end # class Property
end # module DataMapper
View
10 lib/data_mapper/property_set.rb
@@ -47,7 +47,7 @@ def get(resource)
end
def set(values, resource)
- if values.kind_of?(Array)
+ if Array === values
raise ArgumentError, "+values+ must have a length of #{length}, but has #{values.length}", caller if values.length != length
elsif !values.nil?
raise ArgumentError, "+values+ must be nil or an Array, but was a #{values.class}", caller
@@ -60,15 +60,15 @@ def lazy_context(name)
end
def lazy_load_context(names)
- if names.kind_of?(Array)
+ if Array === names
raise ArgumentError, "+names+ cannot be an empty Array", caller if names.empty?
- elsif !names.kind_of?(Symbol)
+ elsif !(Symbol === names)
raise ArgumentError, "+names+ must be a Symbol or an Array of Symbols, but was a #{names.class}", caller
end
result = []
- names = [ names ] if names.kind_of?(Symbol)
+ names = [ names ] if Symbol === names
names.each do |name|
contexts = property_contexts(name)
@@ -97,6 +97,8 @@ def inspect
private
def initialize(properties = [], &block)
+ raise ArgumentError, "+properties+ should be an Array, but was #{properties.class}", caller unless Array === properties
+
@entries = properties
@property_for = Hash.new do |h,k|
case k
View
99 lib/data_mapper/query.rb
@@ -17,19 +17,29 @@ def hash
private
def initialize(property, direction = :asc)
- @property, @direction = property, direction
+ raise ArgumentError, "+property+ is not a DataMapper::Property, but was #{property.class}", caller unless DataMapper::Property === property
+ raise ArgumentError, "+direction+ is not a Symbol, but was #{direction.class}", caller unless Symbol === direction
+
+ @property = property
+ @direction = direction
end
end # class Direction
class Operator
- attr_reader :value, :type, :options
+ attr_reader :property_name, :type
- def initialize(value, type, options = nil)
- @value, @type, @options = value, type, options
+ def to_sym
+ @property_name
end
- def to_sym
- @value
+ private
+
+ def initialize(property_name, type)
+ raise ArgumentError, "+property_name+ is not a Symbol, but was #{property_name.class}", caller unless Symbol === property_name
+ raise ArgumentError, "+type+ is not a Symbol, but was #{type.class}", caller unless Symbol === type
+
+ @property_name = property_name
+ @type = type
end
end # class Operator
@@ -37,35 +47,39 @@ class Path
attr_reader :relationships, :model, :property
- def initialize(relationships, model_name ,property_name=nil)
+ def initialize(relationships, model_name, property_name = nil)
+ raise ArgumentError, "+relationships+ is not an Array, but was #{relationships.class}", caller unless Array === relationships
+ raise ArgumentError, "+model_name+ is not a Symbol, but was #{model_name.class}", caller unless Symbol === model_name
+ raise ArgumentError, "+property_name+ is not a Symbol, but was #{property_name.class}", caller unless Symbol === property_name || property_name.nil?
+
@relationships = relationships
@model = DataMapper::Inflection.classify(model_name.to_s).to_class
- @property = @model.properties(@model.repository.name)[model_name] if property_name
+ @property = @model.properties(@model.repository.name)[property_name] if property_name
end
- alias_method :_method_missing, :method_missing
+ alias_method :_method_missing, :method_missing
def method_missing(method, *args)
- if @model.relationships.has_key?(method)
- relations = []
- relations.concat(@relationships)
- relations << @model.relationships[method]
- return DataMapper::Query::Path.new(relations,method)
- end
-
- if @model.properties(@model.repository.name)[method]
- @property = @model.properties(@model.repository.name)[method]
- return self
- end
-
- _method_missing(method,args)
- end
-
- # duck type the DM::Query::Path to act like a DM::Property
+ if @model.relationships.has_key?(method)
+ relations = []
+ relations.concat(@relationships)
+ relations << @model.relationships[method]
+ return DataMapper::Query::Path.new(relations,method)
+ end
+
+ if @model.properties(@model.repository.name)[method]
+ @property = @model.properties(@model.repository.name)[method]
+ return self
+ end
+
+ _method_missing(method,args)
+ end
+
+ # duck type the DM::Query::Path to act like a DM::Property
def field
- @property ? @property.field : nil
+ @property ? @property.field : nil
end
-
+
end # class Path
@@ -76,7 +90,7 @@ def field
attr_reader :model, :model_name, *OPTIONS
def update(other)
- other = self.class.new(model, other) if other.kind_of?(Hash)
+ other = self.class.new(model, other) if Hash === other
@model, @reload = other.model, other.reload
@@ -132,7 +146,8 @@ def parameters
# <DM::Query>
#
def merge_sub_select_conditions(operator, property, value)
- raise ArgumentError.new('+value+ is not a DataMapper::Query') unless value.is_a?(DataMapper::Query)
+ raise ArgumentError, "+value+ is not a DataMapper::Query, but was #{value.class}", caller unless DataMapper::Query === value
+
new_conditions = []
conditions.each do |tuple|
if tuple.length == 3 && tuple.at(0).to_s == operator.to_s && tuple.at(1) == property && tuple.at(2) == value
@@ -152,7 +167,7 @@ def merge_sub_select_conditions(operator, property, value)
def initialize(model, options = {})
validate_model(model)
- validate_options(options)
+ validate_options(options)
@repository = model.repository
repository_name = @repository.name
@@ -206,13 +221,13 @@ def initialize_copy(original)
# validate the model
def validate_model(model)
- raise ArgumentError, "model must be a Class, but is #{model.class}" unless model.kind_of?(Class)
+ raise ArgumentError, "model must be a Class, but is #{model.class}" unless Class === Class
raise ArgumentError, 'model must include DataMapper::Resource' unless DataMapper::Resource === model
end
# validate the options
def validate_options(options)
- raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
+ raise ArgumentError, 'options must be a Hash' unless Hash === options
# validate the reload option
if options.has_key?(:reload) && options[:reload] != true && options[:reload] != false
@@ -222,7 +237,7 @@ def validate_options(options)
# validate the offset and limit options
([ :offset, :limit ] & options.keys).each do |attribute|
value = options[attribute]
- raise ArgumentError, ":#{attribute} must be an Integer, but was #{value.class}" unless value.kind_of?(Integer)
+ raise ArgumentError, ":#{attribute} must be an Integer, but was #{value.class}" unless Integer === value
end
raise ArgumentError, ':offset must be greater than or equal to 0' if options.has_key?(:offset) && !(options[:offset] >= 0)
raise ArgumentError, ':limit must be greater than or equal to 1' if options.has_key?(:limit) && !(options[:limit] >= 1)
@@ -230,17 +245,17 @@ def validate_options(options)
# validate the order, fields, links, includes and conditions options
([ :order, :fields, :links, :includes, :conditions ] & options.keys).each do |attribute|
value = options[attribute]
- raise ArgumentError, ":#{attribute} must be an Array, but was #{value.class}" unless value.kind_of?(Array)
+ raise ArgumentError, ":#{attribute} must be an Array, but was #{value.class}" unless Array === value
raise ArgumentError, ":#{attribute} cannot be an empty Array" unless value.any?
- end
+ end
end
# TODO: spec this
# validate other DM::Query or Hash object
def validate_other(other)
- if other.kind_of?(self.class)
+ if self.class === other
raise ArgumentError, "other #{self.class} must belong to the same repository" unless other.model.repository == @model.repository
- elsif !other.kind_of?(Hash)
+ elsif !(Hash === other)
raise ArgumentError, "other must be a #{self.class} or Hash, but was a #{other.class}"
end
end
@@ -311,7 +326,7 @@ def normalize_links
when DataMapper::Associations::Relationship
link
when Symbol, String
- link = link.to_sym if link.is_a?(String)
+ link = link.to_sym if String === link
raise ArgumentError, "Link #{link}. No such relationship" unless model.relationships.has_key?(link)
model.relationships[link]
else
@@ -330,25 +345,25 @@ def normalize_includes
# validate that all the links or includes are present for the given DM::Query::Path
#
def validate_query_path_links(path)
- path.relationships.map do |relationship|
+ path.relationships.map do |relationship|
@links << relationship unless (@links.include?(relationship) || @includes.include?(relationship))
end
end
def append_condition(property, value)
operator = :eql
-
+
property = case property
when DataMapper::Property
property
when DataMapper::Query::Path
- validate_query_path_links(property)
+ validate_query_path_links(property)
property
when Operator
operator = property.type
@properties[property.to_sym]
when Symbol, String
- @properties[property]
+ @properties[property]
else
raise ArgumentError, "Condition type #{property.inspect} not supported"
end
View
128 lib/data_mapper/repository.rb
@@ -1,89 +1,9 @@
-require 'uri'
require __DIR__ + 'support/errors'
-require __DIR__ + 'logger'
-require __DIR__ + 'adapters/abstract_adapter'
require __DIR__ + 'identity_map'
-require __DIR__ + 'naming_conventions'
-
-# Delegates to DataMapper::repository.
-# Will not overwrite if a method of the same name is pre-defined.
-module Kernel
- def repository(name = :default)
- unless block_given?
- begin
- DataMapper::Repository.context.last || DataMapper::Repository.new(name)
- #rescue NoMethodError
- # raise RepositoryNotSetupError, "#{name.inspect} repository not set up."
- end
- else
- begin
- return yield(DataMapper::Repository.context.push(DataMapper::Repository.new(name)))
- ensure
- # current = DataMapper::Repository.context.last
- # current.flush! if current.adapter.auto_flush?
- DataMapper::Repository.context.pop
- end
- end
- end
-end # module Kernel
+require __DIR__ + 'scope'
module DataMapper
-
- def self.setup(name, uri_or_options)
- case uri_or_options
- when Hash
- adapter_name = uri_or_options[:adapter]
- else
- uri_or_options = uri_or_options.is_a?(String) ? URI.parse(uri_or_options) : uri_or_options
- raise ArgumentError, "'uri' must be a URI or String" unless uri_or_options.is_a?(URI)
- adapter_name = uri_or_options.scheme
- end
-
- unless Adapters::const_defined?(DataMapper::Inflection.classify(adapter_name) + "Adapter")
- begin
- require __DIR__ + "adapters/#{DataMapper::Inflection.underscore(adapter_name)}_adapter"
- rescue LoadError
- require "#{DataMapper::Inflection.underscore(adapter_name)}_adapter"
- end
- end
-
- raise ArgumentError, "'name' must be a Symbol" unless name.is_a?(Symbol)
-
- Repository.adapters[name] = Adapters::
- const_get(DataMapper::Inflection.classify(adapter_name) + "Adapter").new(name, uri_or_options)
- end
-
- # ===Block Syntax:
- # Pushes the named repository onto the context-stack,
- # yields a new session, and pops the context-stack.
- #
- # results = DataMapper.repository(:second_database) do |current_context|
- # ...
- # end
- #
- # ===Non-Block Syntax:
- # Returns the current session, or if there is none,
- # a new Session.
- #
- # current_repository = DataMapper.repository
- def self.repository(name = :default) # :yields: current_context
- unless block_given?
- begin
- Repository.context.last || Repository.new(name)
- #rescue NoMethodError
- # raise RepositoryNotSetupError, "#{name.inspect} repository not set up."
- end
- else
- begin
- return yield(Repository.context.push(Repository.new(name)))
- ensure
- Repository.context.pop
- end
- end
- end
-
class Repository
-
@adapters = {}
def self.adapters
@@ -91,35 +11,39 @@ def self.adapters
end
def self.context
- Thread::current[:repository_contexts] ||= []
+ Thread.current[:repository_contexts] ||= []
end
attr_reader :name, :adapter
- def initialize(name)
- @name = name
- @adapter = self.class.adapters[name]
- @identity_map = IdentityMap.new
- end
-
- def identity_map_get(resource, key)
- @identity_map.get(resource, key)
+ def identity_map_get(model, key)
+ @identity_map.get(model, key)
end
def identity_map_set(resource)
@identity_map.set(resource)
end
- def get(resource, key)
- @identity_map.get(resource, key) || @adapter.read(self, resource, key)
+ def get(model, key)
+ @identity_map.get(model, key) || @adapter.read(self, model, key)
end
- def first(resource, options)
- @adapter.read_set(self, Query.new(resource, options.merge(:limit => 1))).first
+ def first(model, options)
+ query = if current_scope = model.send(:current_scope)
+ current_scope.merge(options.merge(:limit => 1))
+ else
+ Query.new(model, options.merge(:limit => 1))
+ end
+ @adapter.read_set(self, query).first
end
- def all(resource, options)
- @adapter.read_set(self, Query.new(resource, options)).entries
+ def all(model, options)
+ query = if current_scope = model.send(:current_scope)
+ current_scope.merge(options)
+ else
+ Query.new(model, options)
+ end
+ @adapter.read_set(self, query).entries
end
def save(resource)
@@ -161,5 +85,15 @@ def destroy(resource)
end
end
+ private
+
+ def initialize(name)
+ raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}", caller unless Symbol === name
+
+ @name = name
+ @adapter = self.class.adapters[name]
+ @identity_map = IdentityMap.new
+ end
+
end # class Repository
-end # module DataMapper
+end # module DataMapper
View
94 lib/data_mapper/resource.rb
@@ -1,8 +1,8 @@
require __DIR__ + 'support/string'
require __DIR__ + 'property_set'
require __DIR__ + 'property'
-require __DIR__ + 'repository'
require __DIR__ + 'hook'
+require __DIR__ + 'scope'
require __DIR__ + 'associations'
module DataMapper
@@ -16,6 +16,7 @@ def self.included(base)
base.send(:extend, ClassMethods)
base.send(:extend, DataMapper::Hook::ClassMethods)
base.send(:include, DataMapper::Hook)
+ base.send(:extend, DataMapper::Scope::ClassMethods)
base.instance_variable_set(:@resource_names, Hash.new { |h,k| h[k] = repository(k).adapter.resource_naming_convention.call(base.name) })
base.instance_variable_set(:@properties, Hash.new { |h,k| h[k] = (k == :default ? PropertySet.new : h[:default].dup) })
@@ -31,6 +32,10 @@ def self.dependencies
@dependencies
end
+ def self.===(other)
+ Module === other && other.ancestors.include?(Resource)
+ end
+
# +---------------
# Instance methods
@@ -45,7 +50,7 @@ def [](name)
end
value = instance_variable_get(ivar_name)
- property.custom? && property.type.respond_to?(:load) ? property.type.load(value) : value
+ property.custom? ? property.type.load(value) : value
end
def []=(name, value)
@@ -57,7 +62,7 @@ def []=(name, value)
end
dirty_attributes << property
- instance_variable_set(ivar_name, (property.custom? && property.type.respond_to?(:dump) ? property.type.dump(value) : value))
+ instance_variable_set(ivar_name, property.custom? ? property.type.dump(value) : value)
end
def repository
@@ -123,30 +128,6 @@ def reload!
@loaded_set.reload!(:fields => loaded_attributes.keys)
end