dm-ar-finders 0.9.11 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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