Skip to content

Commit

Permalink
Merge branch 'master' of git@github.com:sam/dm-core
Browse files Browse the repository at this point in the history
  • Loading branch information
david committed Apr 18, 2008
2 parents 0c89e91 + 557fb65 commit 4b63dcb
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 11 deletions.
106 changes: 98 additions & 8 deletions lib/data_mapper/adapters/data_objects_adapter.rb
Expand Up @@ -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
Expand Down Expand Up @@ -155,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
Expand All @@ -173,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
Expand All @@ -187,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
Expand Down
7 changes: 7 additions & 0 deletions lib/data_mapper/resource.rb
Expand Up @@ -23,6 +23,13 @@ def self.included(base)
@@including_classes << base
end

# Return all classes that include the DataMapper::Resource module
#
# ==== Returns
# Set:: A Set containing the including classes
#
# -
# @public
def self.including_classes
@@including_classes
end
Expand Down
82 changes: 82 additions & 0 deletions spec/unit/adapters/data_objects_adapter_spec.rb
Expand Up @@ -12,6 +12,88 @@

it_should_behave_like 'a DataMapper Adapter'

describe "#find_by_sql" do

before do
class Plupp
include DataMapper::Resource
property :id, Fixnum, :key => true
property :name, String
end
end

it "should be added to DataMapper::Resource::ClassMethods" do
DataMapper::Resource::ClassMethods.instance_methods.include?("find_by_sql").should == true
Plupp.methods.include?("find_by_sql").should == true
end

describe "when called" do

before do
@reader = mock("reader")
@reader.stub!(:next!).and_return(false)
@reader.stub!(:close)
@connection = mock("connection")
@connection.stub!(:close)
@command = mock("command")
@adapter = Plupp.repository.adapter
@repository = Plupp.repository
@repository.stub!(:adapter).and_return(@adapter)
@adapter.stub!(:create_connection).and_return(@connection)
@adapter.should_receive(:is_a?).any_number_of_times.with(DataMapper::Adapters::DataObjectsAdapter).and_return(true)
end

it "should accept a single String argument with or without options hash" do
@connection.should_receive(:create_command).twice.with("SELECT * FROM plupps").and_return(@command)
@command.should_receive(:set_types).twice.with([Fixnum, String])
@command.should_receive(:execute_reader).twice.and_return(@reader)
Plupp.should_receive(:repository).any_number_of_times.and_return(@repository)
Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
Plupp.find_by_sql("SELECT * FROM plupps")
Plupp.find_by_sql("SELECT * FROM plupps", :repository => :plupp_repo)
end

it "should accept an Array argument with or without options hash" do
@connection.should_receive(:create_command).twice.with("SELECT * FROM plupps WHERE plur = ?").and_return(@command)
@command.should_receive(:set_types).twice.with([Fixnum, String])
@command.should_receive(:execute_reader).twice.with("my pretty plur").and_return(@reader)
Plupp.should_receive(:repository).any_number_of_times.and_return(@repository)
Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
Plupp.find_by_sql(["SELECT * FROM plupps WHERE plur = ?", "my pretty plur"])
Plupp.find_by_sql(["SELECT * FROM plupps WHERE plur = ?", "my pretty plur"], :repository => :plupp_repo)
end

it "should accept a Query argument with or without options hash" do
@connection.should_receive(:create_command).twice.with("SELECT \"name\" FROM \"plupps\" WHERE (\"name\" = ?)").and_return(@command)
@command.should_receive(:set_types).twice.with([Fixnum, String])
@command.should_receive(:execute_reader).twice.with(Plupp.properties[:name]).and_return(@reader)
Plupp.should_receive(:repository).any_number_of_times.and_return(@repository)
Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
Plupp.find_by_sql(DataMapper::Query.new(Plupp, "name" => "my pretty plur", :fields => ["name"]))
Plupp.find_by_sql(DataMapper::Query.new(Plupp, "name" => "my pretty plur", :fields => ["name"]), :repository => :plupp_repo)
end

it "requires a Repository that is a DataObjectsRepository to work" do
non_do_adapter = mock("non do adapter")
non_do_repo = mock("non do repo")
non_do_repo.stub!(:adapter).and_return(non_do_adapter)
Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(non_do_repo)
Proc.new do
Plupp.find_by_sql(:repository => :plupp_repo)
end.should raise_error(Exception, /DataObjectsAdapter/)
end

it "requires some kind of query to work at all" do
Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
Proc.new do
Plupp.find_by_sql(:repository => :plupp_repo)
end.should raise_error(Exception, /requires a query/)
end

end

end

describe '#execute' do
before do
@mock_command = mock('Command', :execute_non_query => nil)
Expand Down
5 changes: 2 additions & 3 deletions spec/unit/resource_spec.rb
Expand Up @@ -43,10 +43,9 @@ class Moon
end

it "should track the classes that include it" do
DataMapper::Resource.including_classes.should == Set.new([Vehicle, Article, Manufacturer, Planet, Supplier, Comment])
DataMapper::Resource.including_classes.clear
Moon.class_eval do include(DataMapper::Resource) end
DataMapper::Resource.including_classes.size.should == 7
DataMapper::Resource.including_classes.should == Set.new([Vehicle, Article, Manufacturer, Planet, Supplier, Comment, Moon])
DataMapper::Resource.including_classes.should == Set.new([Moon])
end

it "should return an instance of the created object" do
Expand Down

0 comments on commit 4b63dcb

Please sign in to comment.