Permalink
Browse files

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

* 'master' of git@github.com:sam/dm-core:
  Associations may now be added inside a repository do ... end block
  Fixed repository(...) do ... end inside a Resource class declaration.
  Made Resource::ClassMethods#find_by_sql slightly more practically useful
  Added Resource::ClassMethods#find_by_sql.
  Added better documentation for Resource.including_classes.
  Enabled DataMapper::Resource to know what classes have included it.
  Resolved ticket #210.
  Resolved ticket #212.
  Added specs for Property type casting
  Fixed failing specs
  Fixes stupid thing I did.
  Assorted fixes.
  Added documentation for the new property parameter
  Added support to pass property information own to custom types in the dump/load
  • Loading branch information...
2 parents cee40c0 + c5bbdd0 commit 5a3189ce02a26318923075bad789f6adc3be5e89 @wycats wycats committed Apr 19, 2008
@@ -9,6 +9,77 @@
module DataMapper
+ module Resource
+
+ module ClassMethods
+ #
+ # Find instances by manually providing SQL
+ #
+ # ==== Parameters
+ # <String>:: An SQL query to execute
+ # <Array>:: An Array containing a String (being the SQL query to execute) and the parameters to the query.
+ # example: ["SELECT name FROM users WHERE id = ?", id]
+ # <DataMapper::Query>:: A prepared Query to execute.
+ # <Hash>:: An options hash.
+ #
+ # A String, Array or Query is required.
+ #
+ # ==== Options (the options hash)
+ # :repository<Symbol>:: The name of the repository to execute the query in. Defaults to self.default_repository_name.
+ # :reload<Boolean>:: Whether to reload any instances found that allready exist in the identity map. Defaults to false.
+ # :properties<Array>:: The Properties of the instance that the query loads. Must contain DataMapper::Properties. Defaults to self.properties.
+ #
+ # ==== Returns
+ # LoadedSet:: The instance matched by the query.
+ #
+ # ==== Example
+ # MyClass.find_by_sql(["SELECT id FROM my_classes WHERE county = ?", selected_county], :properties => MyClass.property[:id], :repository => :county_repo)
+ #
+ # -
+ # @public
+ def find_by_sql(*args)
+ sql = nil
+ query = nil
+ params = []
+ properties = nil
+ do_reload = false
+ repository_name = default_repository_name
+ args.each do |arg|
+ if arg.is_a?(String)
+ sql = arg
+ elsif arg.is_a?(Array)
+ sql = arg.first
+ params = arg[1..-1]
+ elsif arg.is_a?(DataMapper::Query)
+ query = arg
+ elsif arg.is_a?(Hash)
+ repository_name = arg.delete(:repository) if arg.include?(:repository)
+ properties = Array(arg.delete(:properties)) if arg.include?(:properties)
+ do_reload = arg.delete(:reload) if arg.include?(:reload)
+ raise "unknown options to #find_by_sql: #{arg.inspect}" unless arg.empty?
+ end
+ end
+
+ the_repository = repository(repository_name)
+ raise "#find_by_sql only available for Repositories served by a DataObjectsAdapter" unless the_repository.adapter.is_a?(DataMapper::Adapters::DataObjectsAdapter)
+
+ if query
+ sql = the_repository.adapter.query_read_statement(query)
+ params = query.fields
+ end
+
+ raise "#find_by_sql requires a query of some kind to work" unless sql
+
+ properties ||= self.properties
+
+ DataMapper.logger.debug { "FIND_BY_SQL: #{sql} PARAMETERS: #{params.inspect}" }
+
+ repository.adapter.read_set_with_sql(repository, self, properties, sql, params, do_reload)
+ end
+ end
+
+ end
+
module Adapters
# You must inherit from the DoAdapter, and implement the
@@ -122,20 +193,24 @@ def read(repository, resource, key)
def update(repository, resource)
properties = resource.dirty_attributes
-
- 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
- command = connection.create_command(sql)
-
- affected_rows = command.execute_non_query(*parameters).to_i
-
- close_connection(connection)
-
- affected_rows == 1
+
+ if properties.empty?
+ return false
+ else
+ 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
+ command = connection.create_command(sql)
+
+ affected_rows = command.execute_non_query(*parameters).to_i
+
+ close_connection(connection)
+
+ affected_rows == 1
+ end
end
def delete(repository, resource)
@@ -151,15 +226,24 @@ def delete(repository, resource)
affected_rows == 1
end
- # Methods dealing with finding stuff by some query parameters
- def read_set(repository, query)
- properties = query.fields
-
+ #
+ # used by find_by_sql and read_set
+ #
+ # ==== Parameters
+ # repository<DataMapper::Repository>:: The repository to read from.
+ # model<Object>:: The class of the instances to read.
+ # properties<Array>:: The properties to read. Must contain Symbols, Strings or DM::Properties.
+ # sql<String>:: The query to execute.
+ # parameters<Array>:: The conditions to the query.
+ # do_reload<Boolean>:: Whether to reload objects already found in the identity map.
+ #
+ # ==== Returns
+ # LoadedSet:: A set of the found instances.
+ #
+ def read_set_with_sql(repository, model, properties, sql, parameters, do_reload)
properties_with_indexes = Hash[*properties.zip((0...properties.length).to_a).flatten]
- set = LoadedSet.new(repository, query.model, properties_with_indexes)
+ set = LoadedSet.new(repository, model, properties_with_indexes)
- sql = query_read_statement(query)
- parameters = query.parameters
DataMapper.logger.debug { "READ_SET: #{sql} PARAMETERS: #{parameters.inspect}" }
connection = create_connection
@@ -169,7 +253,7 @@ def read_set(repository, query)
reader = command.execute_reader(*parameters)
while(reader.next!)
- set.add(reader.values, query.reload?)
+ set.add(reader.values, do_reload)
end
reader.close
@@ -183,6 +267,16 @@ def read_set(repository, query)
set
end
+ # Methods dealing with finding stuff by some query parameters
+ def read_set(repository, query)
+ read_set_with_sql(repository,
+ query.model,
+ query.fields,
+ query_read_statement(query),
+ query.parameters,
+ query.reload?)
+ end
+
def delete_set(repository, query)
raise NotImplementedError
end
@@ -13,7 +13,7 @@ def many_to_one(name, options = {})
relationships[name] = Relationship.new(
name,
- options[:repository_name] || repository.name,
+ repository.name,
child_model_name,
nil,
parent_model_name,
@@ -15,7 +15,7 @@ def one_to_many(name, options = {})
relationships[name] = Relationship.new(
DataMapper::Inflection.underscore(parent_model_name).to_sym,
- options[:repository_name] || repository.name,
+ repository.name,
child_model_name,
nil,
parent_model_name,
@@ -12,7 +12,7 @@ def one_to_one(name, options = {})
relationships[name] = Relationship.new(
DataMapper::Inflection.underscore(parent_model_name).to_sym,
- options[:repository_name] || repository.name,
+ repository.name,
child_model_name,
nil,
parent_model_name,
View
@@ -8,8 +8,16 @@ module ClassMethods
def before(target_method, method_sym = nil, &block)
install_hook :before, target_method, method_sym, &block
end
+
+ def after(target_method, method_sym = nil, &block)
+ install_hook :after, target_method, method_sym, &block
+ end
def install_hook(type, name, method_sym = nil, &block)
+ raise ArgumentError.new("You need to pass 2 arguments to \"#{type}\".") if ! block_given? and method_sym.nil?
+ raise ArgumentError.new("target_method should be a symbol") unless name.is_a?(Symbol)
+ raise ArgumentError.new("method_sym should be a symbol") if method_sym && ! method_sym.is_a?(Symbol)
+
(hooks[name][type] ||= []) << if block
new_meth_name = "__hooks_#{type}_#{quote_method(name)}_#{hooks[name][type].length}".to_sym
define_method new_meth_name, block
@@ -21,14 +29,16 @@ def install_hook(type, name, method_sym = nil, &block)
class_eval define_advised_method(name), __FILE__, __LINE__
end
+ # FIXME Return the method value
def define_advised_method(name)
args = args_for(hooks[name][:old_method] ||= instance_method(name))
<<-EOD
def #{name}(#{args})
#{inline_hooks(name, :before, args)}
- #{inline_call(name, args)}
+ retval = #{inline_call(name, args)}
#{inline_hooks(name, :after, args)}
+ retval
end
EOD
end
@@ -80,11 +90,7 @@ def hooks
end
def quote_method(name)
- name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_')
- end
-
- def after(target_method, method_sym = nil, &block)
- install_hook :after, target_method, method_sym, &block
+ name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_')
end
end
end # module Hook
@@ -110,7 +110,7 @@ module DataMapper
# property :body, DataMapper::Types::Text # Is lazily loaded by default
# end
#
-# If you want to over-ride the lazy loading on any field you can set it to a
+# If you want to over-ride the lazy loading on any field you can set it to a
# context or false to disable it with the :lazy option. Contexts allow multipule
# lazy properties to be loaded at one time. If you set :lazy to true, it is placed
# in the :default context
@@ -230,6 +230,18 @@ def field
@field ||= @options.fetch(:field, repository.adapter.field_naming_convention.call(name))
end
+ def hash
+ return @model.hash + @name.hash
+ end
+
+ def eql?(o)
+ if o.is_a?(Property)
+ return o.model == @model && o.name == @name
+ else
+ return false
+ end
+ end
+
def lazy?
@lazy
end
@@ -258,33 +270,29 @@ def set(value, resource)
resource[@name] = value
end
- def inspect
- "#<Property:#{@model}:#{@name}>"
- end
-
def typecast(value)
- if type == TrueClass
- value == true || value == "true"
- elsif type == String
- value.to_s
- elsif [Float, Fixnum, BigDecimal].include?(type)
- value.to_f
- elsif type == DateTime
- Time.parse(value)
- elsif type == Date
- Date.parse(value)
- elsif type == Class
- Object.recursive_const_get(value)
- else
- value
+ return value if type === value || value.nil?
+
+ if type == TrueClass then true == value || 'true' == value
+ elsif type == String then value.to_s
+ elsif type == Float then value.to_f
+ elsif type == Fixnum then value.to_i
+ elsif type == BigDecimal then BigDecimal.new(value.to_s)
+ elsif type == DateTime then DateTime.parse(value.to_s)
+ elsif type == Date then Date.parse(value.to_s)
+ elsif type == Class then Object.recursive_const_get(value)
end
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 Symbol === name
+ 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 Symbol === name
raise ArgumentError, "+type+ was #{type.inspect}, 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_options = options.keys - PROPERTY_OPTIONS).any?
@@ -329,30 +337,19 @@ def create_getter
@model.class_eval <<-EOS, __FILE__, __LINE__
#{reader_visibility}
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}
+ self[#{name.inspect}]
end
EOS
- rescue SyntaxError
- raise SyntaxError, name
end
# defines the setter for the property
def create_setter
@model.class_eval <<-EOS, __FILE__, __LINE__
#{writer_visibility}
def #{name}=(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'}
+ self[#{name.inspect}] = value
end
EOS
- rescue SyntaxError
- raise SyntaxError, name
end
end # class Property
end # module DataMapper
Oops, something went wrong.

0 comments on commit 5a3189c

Please sign in to comment.