dm-ar-finders 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ === 0.10.0 / 2009-10-15
2
+
3
+ * Updated to work with dm-core 0.10.0
4
+
1
5
  === 0.9.11 / 2009-03-29
2
6
 
3
7
  * No changes this version
data/Manifest.txt CHANGED
@@ -1,7 +1,7 @@
1
- History.txt
1
+ History.rdoc
2
2
  LICENSE
3
3
  Manifest.txt
4
- README.txt
4
+ README.rdoc
5
5
  Rakefile
6
6
  TODO
7
7
  lib/dm-ar-finders.rb
File without changes
data/Rakefile CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'pathname'
2
- require 'rubygems'
3
2
 
4
3
  ROOT = Pathname(__FILE__).dirname.expand_path
5
4
  JRUBY = RUBY_PLATFORM =~ /java/
@@ -14,10 +13,10 @@ GEM_NAME = 'dm-ar-finders'
14
13
  GEM_VERSION = DataMapper::ARFinders::VERSION
15
14
  GEM_DEPENDENCIES = [['dm-core', GEM_VERSION]]
16
15
  GEM_CLEAN = %w[ log pkg coverage ]
17
- GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO History.txt ] }
16
+ GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.rdoc LICENSE TODO History.rdoc ] }
18
17
 
19
18
  PROJECT_NAME = 'datamapper'
20
- PROJECT_URL = "http://github.com/sam/dm-more/tree/master/#{GEM_NAME}"
19
+ PROJECT_URL = "http://github.com/datamapper/dm-more/tree/master/#{GEM_NAME}"
21
20
  PROJECT_DESCRIPTION = PROJECT_SUMMARY = 'DataMapper plugin providing ActiveRecord-style finders'
22
21
 
23
22
  [ ROOT, ROOT.parent ].each do |dir|
@@ -1,5 +1,5 @@
1
1
  module DataMapper
2
2
  module ARFinders
3
- VERSION = '0.9.11'
3
+ VERSION = '0.10.0'.freeze
4
4
  end
5
5
  end
data/lib/dm-ar-finders.rb CHANGED
@@ -1,24 +1,123 @@
1
- require 'rubygems'
2
- gem 'dm-core', '0.9.11'
3
- require 'dm-core'
4
-
5
1
  module DataMapper
6
2
  module Model
7
- def find_or_create(search_attributes, create_attributes = {})
8
- first(search_attributes) || create(search_attributes.merge(create_attributes))
3
+ # Find resources by providing your own SQL query or DataMapper::Query
4
+ # instance.
5
+ #
6
+ # @param [Array] sql_or_query
7
+ # An array whose first element is an SQL query, and the other
8
+ # elements are bind values for the query.
9
+ # @param [Hash] options
10
+ # A hash containing extra options.
11
+ #
12
+ # @overload find_by_sql(string_query, options = {})
13
+ # @param [String] sql_or_query
14
+ # A string containing an SQL query to execute.
15
+ # @param [Hash] options
16
+ # A hash containing extra options.
17
+ #
18
+ # @overload find_by_sql(dm_query, options = {})
19
+ # @param [DataMapper::Query] sql_or_query
20
+ # A DataMapper::Query instance to be used to generate an SQL query.
21
+ # @param [Hash] options
22
+ # A hash containing extra options.
23
+ #
24
+ # @option options [true, false] :reload (false)
25
+ # Whether to reload any matching resources which are already loaded.
26
+ # @option options [Symbol, Array, DataMapper::Property, DataMapper::PropertySet] :properties
27
+ # Specific properties to be loaded. May be a single symbol, a Property
28
+ # instance, an array of Properties, or a PropertySet.
29
+ # @option options [Symbol] :repository
30
+ # The repository to query. Uses the model default if none is specified.
31
+ #
32
+ # @return [DataMapper::Collection]
33
+ # A collection containing any records which matched your query.
34
+ #
35
+ # @raise [ArgumentError]
36
+ #
37
+ # @example Query with bind values
38
+ # MyClass.find_by_sql(["SELECT id FROM my_classes WHERE county = ?",
39
+ # selected_county])
40
+ #
41
+ # @example String query
42
+ # MyClass.find_by_sql("SELECT id FROM my_classes LIMIT 1")
43
+ #
44
+ # @example Query with properties option
45
+ # MyClass.find_by_sql("SELECT id FROM my_classes LIMIT 1",
46
+ # :properties => [:id, :name])
47
+ #
48
+ # @example Query with repository
49
+ # MyClass.find_by_sql(["SELECT id FROM my_classes WHERE county = ?",
50
+ # selected_county], :properties => MyClass.property[:name],
51
+ # :repository => :county_repo)
52
+ #
53
+ # @api public
54
+ def find_by_sql(sql_or_query, options = {})
55
+ # Figure out what the user passed in.
56
+ case sql_or_query
57
+ when Array
58
+ sql, *bind_values = sql_or_query
59
+ when String
60
+ sql, bind_values = sql_or_query, []
61
+ when DataMapper::Query
62
+ sql, bind_values = repository.adapter.send(:select_statement, query)
63
+ else
64
+ raise ArgumentError, '#find_by_sql requires a query of some kind to work'
65
+ end
66
+
67
+ # Sort out the options.
68
+ repository = repository(options.fetch(:repository, default_repository_name))
69
+
70
+ if options.key?(:properties)
71
+ if options[:properties].kind_of?(DataMapper::PropertySet)
72
+ properties = Array(options[:properties])
73
+ else
74
+ # Normalize properties into PropertySet[Property].
75
+ properties = Array(options[:properties]).map! do |prop|
76
+ prop.kind_of?(Symbol) ? self.properties[prop] : prop
77
+ end
78
+
79
+ properties = DataMapper::PropertySet.new(properties)
80
+ end
81
+ else
82
+ properties = self.properties(repository.name)
83
+ end
84
+
85
+ unless repository.adapter.kind_of?(Adapters::DataObjectsAdapter)
86
+ raise '#find_by_sql only available for Repositories served by a DataObjectsAdapter'
87
+ end
88
+
89
+ records = []
90
+
91
+ repository.adapter.send(:with_connection) do |connection|
92
+ reader = connection.create_command(sql).execute_reader(*bind_values)
93
+ fields = properties.values_at(*reader.fields).compact
94
+
95
+ begin
96
+ while reader.next!
97
+ records << fields.zip(reader.values).to_hash
98
+ end
99
+ ensure
100
+ reader.close
101
+ end
102
+ end
103
+
104
+ query = Query.new(repository, self,
105
+ :fields => properties, :reload => options.fetch(:reload, false))
106
+
107
+ Collection.new(query, query.model.load(records, query))
9
108
  end
10
109
 
110
+ alias find_or_create first_or_create
111
+ alias find_or_initialize first_or_new
112
+
11
113
  private
12
114
 
13
115
  def method_missing_with_find_by(method, *args, &block)
14
116
  if match = matches_dynamic_finder?(method)
15
- finder = determine_finder(match)
117
+ finder = determine_finder(match)
16
118
  attribute_names = extract_attribute_names_from_match(match)
17
119
 
18
- conditions = {}
19
- attribute_names.each {|key| conditions[key] = args.shift}
20
-
21
- send(finder, conditions)
120
+ send(finder, attribute_names.zip(args).to_hash)
22
121
  else
23
122
  method_missing_without_find_by(method, *args, &block)
24
123
  end
@@ -39,4 +138,4 @@ module DataMapper
39
138
  match.captures.last.split('_and_')
40
139
  end
41
140
  end
42
- end
141
+ end # module Model
@@ -1,16 +1,15 @@
1
- require 'pathname'
2
- require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
1
+ require 'spec_helper'
3
2
 
4
3
  if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
5
4
  describe "DataMapper::Resource" do
6
5
  after do
7
- repository(:default).adapter.execute('DELETE from green_smoothies');
6
+ DataMapper.repository(:default).adapter.execute('DELETE from green_smoothies');
8
7
  end
9
8
 
10
9
  before(:all) do
11
10
  class ::GreenSmoothie
12
11
  include DataMapper::Resource
13
- property :id, Integer, :serial => true
12
+ property :id, Serial
14
13
  property :name, String
15
14
 
16
15
  auto_migrate!(:default)
@@ -18,7 +17,7 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
18
17
  end
19
18
 
20
19
  it "should find/create using find_or_create" do
21
- repository(:default) do
20
+ DataMapper.repository(:default) do
22
21
  green_smoothie = GreenSmoothie.new(:name => 'Banana')
23
22
  green_smoothie.save
24
23
  GreenSmoothie.find_or_create({:name => 'Banana'}).id.should eql(green_smoothie.id)
@@ -27,14 +26,14 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
27
26
  end
28
27
 
29
28
  it "should use find_by and use the name attribute to find a record" do
30
- repository(:default) do
29
+ DataMapper.repository(:default) do
31
30
  green_smoothie = GreenSmoothie.create({:name => 'Banana'})
32
31
  green_smoothie.should == GreenSmoothie.find_by_name('Banana')
33
32
  end
34
33
  end
35
34
 
36
35
  it "should use find_all_by to find records using an attribute" do
37
- repository(:default) do
36
+ DataMapper.repository(:default) do
38
37
  green_smoothie = GreenSmoothie.create({:name => 'Banana'})
39
38
  green_smoothie2 = GreenSmoothie.create({:name => 'Banana'})
40
39
  found_records = GreenSmoothie.find_all_by_name('Banana')
@@ -44,5 +43,191 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
44
43
  end
45
44
  end
46
45
  end
46
+
47
+ ###
48
+
49
+ describe '#find_by_sql' do
50
+ before(:each) do
51
+ DataMapper.repository(:default) do
52
+ @resource = GreenSmoothie.create({:name => 'Banana'})
53
+ end
54
+ end
55
+
56
+ it 'should find the resource when given a string' do
57
+ DataMapper.repository(:default) do
58
+ found = GreenSmoothie.find_by_sql <<-SQL
59
+ SELECT id, name FROM green_smoothies LIMIT 1
60
+ SQL
61
+
62
+ found.should_not be_empty
63
+ found.first.should == @resource
64
+ end
65
+ end
66
+
67
+ it 'should find the resource when given an array containing SQL and bind values' do
68
+ DataMapper.repository(:default) do
69
+ found = GreenSmoothie.find_by_sql [<<-SQL, @resource.id]
70
+ SELECT id, name FROM green_smoothies WHERE id = ?
71
+ SQL
72
+
73
+ found.should_not be_empty
74
+ found.first.should == @resource
75
+ end
76
+ end
77
+
78
+ it 'should return an empty collection when nothing is found' do
79
+ DataMapper.repository(:default) do
80
+ found = GreenSmoothie.find_by_sql [<<-SQL, 0]
81
+ SELECT id, name FROM green_smoothies WHERE id = ?
82
+ SQL
83
+
84
+ found.should be_kind_of(DataMapper::Collection)
85
+ found.should be_empty
86
+ end
87
+ end
88
+
89
+ it 'should raise an error if no SQL string or Query is given' do
90
+ DataMapper.repository(:default) do
91
+ lambda { GreenSmoothie.find_by_sql nil }.should raise_error(ArgumentError, /requires a query/)
92
+ end
93
+ end
94
+
95
+ it 'should raise an error if an unacceptable argument is given' do
96
+ DataMapper.repository(:default) do
97
+ lambda { GreenSmoothie.find_by_sql :go }.should raise_error(ArgumentError)
98
+ end
99
+ end
100
+
101
+ it 'should accept a Query instance' do
102
+ query = GreenSmoothie.find_by_sql([<<-SQL, @resource.id]).query
103
+ SELECT id, name FROM green_smoothies WHERE id = ?
104
+ SQL
105
+
106
+ found = GreenSmoothie.find_by_sql(query)
107
+ found.should_not be_empty
108
+ found.first.should == @resource
109
+ end
110
+
111
+ # Options.
112
+
113
+ describe ':repository option' do
114
+ it 'should use repository identified by the given symbol' do
115
+ found = GreenSmoothie.find_by_sql <<-SQL, :repository => ENV['ADAPTER'].intern
116
+ SELECT id, name FROM green_smoothies LIMIT 1
117
+ SQL
118
+
119
+ found.repository.should == DataMapper.repository(ENV['ADAPTER'].intern)
120
+ end
121
+
122
+ it 'should use the default repository if no repository option is specified' do
123
+ found = GreenSmoothie.find_by_sql <<-SQL
124
+ SELECT id, name FROM green_smoothies LIMIT 1
125
+ SQL
126
+
127
+ found.repository.should == DataMapper.repository(:default)
128
+ end
129
+ end
130
+
131
+ describe ':reload option' do
132
+ it 'should reload existing resources in the identity map if given true' do
133
+ found = GreenSmoothie.find_by_sql <<-SQL, :reload => true
134
+ SELECT id, name FROM green_smoothies LIMIT 1
135
+ SQL
136
+
137
+ found.query.reload?.should be_true
138
+ end
139
+
140
+ it 'should not reload existing resources in the identity map if given false' do
141
+ found = GreenSmoothie.find_by_sql <<-SQL, :reload => false
142
+ SELECT id, name FROM green_smoothies LIMIT 1
143
+ SQL
144
+
145
+ found.query.reload?.should be_false
146
+ end
147
+
148
+ it 'should default to false' do
149
+ found = GreenSmoothie.find_by_sql <<-SQL
150
+ SELECT id, name FROM green_smoothies LIMIT 1
151
+ SQL
152
+
153
+ found.query.reload?.should be_false
154
+ end
155
+ end
156
+
157
+ describe ':properties option' do
158
+ it 'should accept an array of symbols' do
159
+ found = GreenSmoothie.find_by_sql <<-SQL, :properties => [:id, :name]
160
+ SELECT id, name FROM green_smoothies LIMIT 1
161
+ SQL
162
+
163
+ properties = found.first.loaded_properties
164
+ properties.should have(2).properties
165
+ properties.should include(GreenSmoothie.properties[:id])
166
+ properties.should include(GreenSmoothie.properties[:name])
167
+ end
168
+
169
+ it 'should accept a single Symbol' do
170
+ found = GreenSmoothie.find_by_sql <<-SQL, :properties => :id
171
+ SELECT id, name FROM green_smoothies LIMIT 1
172
+ SQL
173
+
174
+ properties = found.first.loaded_properties
175
+ properties.should have(1).properties
176
+ properties.should include(GreenSmoothie.properties[:id])
177
+ end
178
+
179
+ it 'should accept a PropertySet' do
180
+ found = GreenSmoothie.find_by_sql <<-SQL, :properties => GreenSmoothie.properties
181
+ SELECT id, name FROM green_smoothies LIMIT 1
182
+ SQL
183
+
184
+ properties = found.first.loaded_properties
185
+ properties.should have(2).properties
186
+ properties.should include(GreenSmoothie.properties[:id])
187
+ properties.should include(GreenSmoothie.properties[:name])
188
+ end
189
+
190
+ it 'should accept a single property' do
191
+ found = GreenSmoothie.find_by_sql <<-SQL, :properties => GreenSmoothie.properties[:id]
192
+ SELECT id, name FROM green_smoothies LIMIT 1
193
+ SQL
194
+
195
+ properties = found.first.loaded_properties
196
+ properties.should have(1).property
197
+ properties.first.should == GreenSmoothie.properties[:id]
198
+ end
199
+
200
+ it 'should accept an array of Properties' do
201
+ found = GreenSmoothie.find_by_sql <<-SQL, :properties => GreenSmoothie.properties.to_a
202
+ SELECT id, name FROM green_smoothies LIMIT 1
203
+ SQL
204
+
205
+ properties = found.first.loaded_properties
206
+ properties.should have(2).properties
207
+ properties.should include(GreenSmoothie.properties[:id])
208
+ properties.should include(GreenSmoothie.properties[:name])
209
+ end
210
+
211
+ it 'should use the given properties in preference over those in the SQL query' do
212
+ found = GreenSmoothie.find_by_sql <<-SQL, :properties => GreenSmoothie.properties
213
+ SELECT id, name FROM green_smoothies LIMIT 1
214
+ SQL
215
+
216
+ properties = found.first.loaded_properties
217
+ properties.should have(2).properties
218
+ properties.should include(GreenSmoothie.properties[:id])
219
+ properties.should include(GreenSmoothie.properties[:name])
220
+ end
221
+
222
+ it 'should use the default properties if none are specified' do
223
+ found = GreenSmoothie.find_by_sql <<-SQL
224
+ SELECT id, name FROM green_smoothies LIMIT 1
225
+ SQL
226
+
227
+ found.first.loaded_properties.should == GreenSmoothie.properties
228
+ end
229
+ end
230
+ end # find_by_sql
231
+
47
232
  end
48
233
  end
data/spec/spec.opts CHANGED
@@ -1 +1,2 @@
1
1
  --colour
2
+ --loadby random
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,14 @@
1
- require 'pathname'
2
1
  require 'rubygems'
3
2
 
4
- require Pathname(__FILE__).dirname.expand_path.parent + 'lib/dm-ar-finders'
3
+ # use local dm-core if running from a typical dev checkout.
4
+ lib = File.join('..', '..', 'dm-core', 'lib')
5
+ $LOAD_PATH.unshift(lib) if File.directory?(lib)
6
+ require 'dm-core'
7
+
8
+ # Support running specs with 'rake spec' and 'spec'
9
+ $LOAD_PATH.unshift('lib') unless $LOAD_PATH.include?('lib')
10
+
11
+ require 'dm-ar-finders'
5
12
 
6
13
  def load_driver(name, default_uri)
7
14
  return false if ENV['ADAPTER'] != name.to_s
@@ -9,10 +16,6 @@ def load_driver(name, default_uri)
9
16
  begin
10
17
  DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
11
18
  DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
12
-
13
- FileUtils.touch LOG_PATH
14
- DataMapper::Logger.new(LOG_PATH, :debug)
15
- at_exit { DataMapper.logger.close }
16
19
  true
17
20
  rescue LoadError => e
18
21
  warn "Could not load do_#{name}: #{e}"
@@ -21,7 +24,7 @@ def load_driver(name, default_uri)
21
24
  end
22
25
 
23
26
  ENV['ADAPTER'] ||= 'sqlite3'
24
- LOG_PATH = Pathname(__FILE__).dirname.expand_path.to_s + '/sql.log'
27
+
25
28
  HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
26
29
  HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
27
30
  HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
data/tasks/install.rb CHANGED
@@ -4,7 +4,7 @@ end
4
4
 
5
5
  desc "Install #{GEM_NAME} #{GEM_VERSION}"
6
6
  task :install => [ :package ] do
7
- sudo_gem "install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
7
+ sudo_gem "install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
8
8
  end
9
9
 
10
10
  desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
data/tasks/spec.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  begin
2
- gem 'rspec', '~>1.2'
3
- require 'spec'
4
2
  require 'spec/rake/spectask'
5
3
 
6
4
  task :default => [ :spec ]
@@ -8,16 +6,18 @@ begin
8
6
  desc 'Run specifications'
9
7
  Spec::Rake::SpecTask.new(:spec) do |t|
10
8
  t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
11
- t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s).map { |f| f.to_s }
9
+ t.libs << 'lib' << 'spec' # needed for CI rake spec task, duplicated in spec_helper
12
10
 
13
11
  begin
14
- gem 'rcov', '~>0.8'
12
+ require 'rcov'
15
13
  t.rcov = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
16
14
  t.rcov_opts << '--exclude' << 'spec'
17
15
  t.rcov_opts << '--text-summary'
18
16
  t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
19
17
  rescue LoadError
20
18
  # rcov not installed
19
+ rescue SyntaxError
20
+ # rcov syntax invalid
21
21
  end
22
22
  end
23
23
  rescue LoadError
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-ar-finders
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.11
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John W Higgins
@@ -9,19 +9,10 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-29 00:00:00 -07:00
12
+ date: 2009-09-16 00:00:00 -07:00
13
13
  default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: dm-core
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - "="
22
- - !ruby/object:Gem::Version
23
- version: 0.9.11
24
- version:
14
+ dependencies: []
15
+
25
16
  description: DataMapper plugin providing ActiveRecord-style finders
26
17
  email:
27
18
  - john [a] wishVPS [d] com
@@ -30,15 +21,15 @@ executables: []
30
21
  extensions: []
31
22
 
32
23
  extra_rdoc_files:
33
- - README.txt
24
+ - README.rdoc
34
25
  - LICENSE
35
26
  - TODO
36
- - History.txt
27
+ - History.rdoc
37
28
  files:
38
- - History.txt
29
+ - History.rdoc
39
30
  - LICENSE
40
31
  - Manifest.txt
41
- - README.txt
32
+ - README.rdoc
42
33
  - Rakefile
43
34
  - TODO
44
35
  - lib/dm-ar-finders.rb
@@ -49,11 +40,13 @@ files:
49
40
  - tasks/install.rb
50
41
  - tasks/spec.rb
51
42
  has_rdoc: true
52
- homepage: http://github.com/sam/dm-more/tree/master/dm-ar-finders
43
+ homepage: http://github.com/datamapper/dm-more/tree/master/dm-ar-finders
44
+ licenses: []
45
+
53
46
  post_install_message:
54
47
  rdoc_options:
55
48
  - --main
56
- - README.txt
49
+ - README.rdoc
57
50
  require_paths:
58
51
  - lib
59
52
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -71,9 +64,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
64
  requirements: []
72
65
 
73
66
  rubyforge_project: datamapper
74
- rubygems_version: 1.3.1
67
+ rubygems_version: 1.3.5
75
68
  signing_key:
76
- specification_version: 2
69
+ specification_version: 3
77
70
  summary: DataMapper plugin providing ActiveRecord-style finders
78
71
  test_files: []
79
72