diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..6c4af1f --- /dev/null +++ b/.envrc @@ -0,0 +1,7 @@ +# A direnv config file: https://github.com/direnv/direnv + +if [ "$(type -t direnv_load)" = 'function' ]; then + # Whatever you want only direnv to execute + PATH_add bin +fi + diff --git a/.gitignore b/.gitignore index 21c9ca8..e670bb5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ coverage rdoc pkg -log/test.log \ No newline at end of file +log/test.log +.rspec.status +.rake_t_cache \ No newline at end of file diff --git a/.tm_properties b/.tm_properties new file mode 100644 index 0000000..cbd3fa3 --- /dev/null +++ b/.tm_properties @@ -0,0 +1 @@ +excludeDirectories = "{coverage,log,vendor,arena,}" diff --git a/README.md b/README.md index 5dbec24..5b60c5d 100644 --- a/README.md +++ b/README.md @@ -50,15 +50,11 @@ gives you the following options: Note: whenever you run a query `simple-sql` takes care of sending query parameters over the wire properly. That means that you use placeholders `$1`, `$2`, etc. to use these inside your queries; the following is a correct example: -```ruby -Simple::SQL.all "SELECT * FROM users WHERE email=$1", "foo@bar.local" -``` + Simple::SQL.all "SELECT * FROM users WHERE email=$1", "foo@bar.local" Also note that it is not possible to use an array as the argument for the `IN(?)` SQL construct. Instead you want to use `ANY`, for example: -```ruby -Simple::SQL.all "SELECT * FROM users WHERE id = ANY($1)", [1,2,3] -``` + Simple::SQL.all "SELECT * FROM users WHERE id = ANY($1)", [1,2,3] ### Simple::SQL.all: Fetching all results of a query @@ -71,18 +67,14 @@ Otherwise it returns an array of arrays. Examples: -```ruby -Simple::SQL.all("SELECT id FROM users") # returns an array of id values, but -Simple::SQL.all("SELECT id, email FROM users") # returns an array of arrays `[ , ]`. -``` + Simple::SQL.all("SELECT id FROM users") # returns an array of id values, but + Simple::SQL.all("SELECT id, email FROM users") # returns an array of arrays `[ , ]`. If a block is passed to SQL.all, each row is yielded into the block: -```ruby -Simple::SQL.all "SELECT id, email FROM users" do |id, email| - # do something -end -``` + Simple::SQL.all "SELECT id, email FROM users" do |id, email| + # do something + end ### Simple::SQL.ask: getting the first result @@ -94,10 +86,8 @@ If the SQL query returns rows with one column, this method returns the column va Examples: -```ruby -Simple::SQL.ask "SELECT id FROM users WHERE email=$1", "foo@local" # returns a number (or `nil`) and -Simple::SQL.ask "SELECT id, email FROM users WHERE email=$?", "foo@local" # returns an array `[ , ]` (or `nil`) -``` + Simple::SQL.ask "SELECT id FROM users WHERE email=$1", "foo@local" # returns a number (or `nil`) and + Simple::SQL.ask "SELECT id, email FROM users WHERE email=$?", "foo@local" # returns an array `[ , ]` (or `nil`) ### Simple::SQL.ask/Simple::SQL.all: fetching hashes @@ -111,15 +101,15 @@ If you want the returned record to be in a structure which is not a Hash, you ca the `into: ` option. The following would return an array of up to two `OpenStruct` objects: - sql = "SELECT id, email FROM users WHERE id = ANY($1) LIMIT 1", + sql = "SELECT id, email FROM users WHERE id = ANY($1) LIMIT 1" Simple::SQL.all sql, [1,2,3], into: OpenStruct -This supports all target types that take a contructor which acceps Hash arguments. +This supports all target types that take a constructor which accepts Hash arguments. It also supports a :struct argument, in which case simple-sql creates uses a Struct-class. Struct classes are reused when possible, and are maintained by Simple::SQL. - sql = "SELECT id, email FROM users WHERE id = ANY($1) LIMIT 1", + sql = "SELECT id, email FROM users WHERE id = ANY($1) LIMIT 1" Simple::SQL.all sql, [1,2,3], into: :struct ### Transaction support diff --git a/Rakefile b/Rakefile index 9ab821b..810565f 100644 --- a/Rakefile +++ b/Rakefile @@ -10,3 +10,8 @@ task default: "test:prepare_db" do sh "USE_ACTIVE_RECORD=1 rspec" sh "rubocop -D" end + +task :fastspec do + sh "SKIP_SIMPLE_COV=1 rspec --only-failures" + sh "rspec" +end diff --git a/lib/simple/sql/connection_adapter.rb b/lib/simple/sql/connection_adapter.rb index fc50349..ef17ee2 100644 --- a/lib/simple/sql/connection_adapter.rb +++ b/lib/simple/sql/connection_adapter.rb @@ -64,10 +64,9 @@ def ask(sql, *args, into: nil) def add_page_info(scope, results) raise ArgumentError, "expect Array but get a #{results.class.name}" unless results.is_a?(Array) - raise ArgumentError, "per must be > 0" unless scope.per > 0 # optimization: add empty case (page <= 1 && results.empty?) - if scope.page <= 1 && results.empty? + if scope.page <= 1 && scope.per > 0 && results.empty? Scope::PageInfo.attach(results, total_count: 0, per: scope.per, page: 1) else sql = "SELECT COUNT(*) FROM (#{scope.order_by(nil).to_sql(pagination: false)}) simple_sql_count" @@ -80,6 +79,8 @@ def exec_logged(sql_or_scope, *args) if sql_or_scope.is_a?(Scope) raise ArgumentError, "You cannot call .all with a scope and additional arguments" unless args.empty? + return [] if sql_or_scope.per == 0 + sql = sql_or_scope.to_sql args = sql_or_scope.args else @@ -92,6 +93,8 @@ def exec_logged(sql_or_scope, *args) end def enumerate(result, into:, &block) + return result if result.is_a?(Array) && result.empty? + decoder = Decoder.new(self, result, into: into) if block diff --git a/lib/simple/sql/scope.rb b/lib/simple/sql/scope.rb index bb37238..e24bca9 100644 --- a/lib/simple/sql/scope.rb +++ b/lib/simple/sql/scope.rb @@ -52,6 +52,17 @@ def where(sql_fragment, arg = :__dummy__no__arg, placeholder: "?") duplicate.send(:where!, sql_fragment, arg, placeholder: placeholder) end + # Set pagination + def paginate(per:, page: 1) + duplicate.send(:paginate!, per: per, page: page) + end + + def order_by(sql_fragment) + duplicate.send(:order_by!, sql_fragment) + end + + private + def where!(sql_fragment, arg = :__dummy__no__arg, placeholder: "?") if arg == :__dummy__no__arg @filters << sql_fragment @@ -63,11 +74,6 @@ def where!(sql_fragment, arg = :__dummy__no__arg, placeholder: "?") self end - # Set pagination - def paginate(per:, page:) - duplicate.send(:paginate!, per: per, page: page) - end - def paginate!(per:, page:) @per = per @page = page @@ -81,10 +87,6 @@ def order_by!(sql_fragment) self end - def order_by(sql_fragment) - duplicate.order_by!(sql_fragment) - end - public # Is this a paginated scope? @@ -124,7 +126,7 @@ module PageInfo def self.attach(results, total_count:, per:, page:) results.extend(self) results.instance_variable_set :@total_count, total_count - results.instance_variable_set :@total_pages, (total_count + (per - 1)) / per + results.instance_variable_set :@total_pages, per > 0 ? (total_count + (per - 1)) / per : -1 results.instance_variable_set :@current_page, page results end diff --git a/scripts/watch b/scripts/watch index 8e54520..af1cf81 100755 --- a/scripts/watch +++ b/scripts/watch @@ -1,2 +1,2 @@ #!/bin/bash -watchr lib,spec rspec +watchr lib,spec rake fastspec diff --git a/spec/simple/sql_scope_spec.rb b/spec/simple/sql_scope_spec.rb index 172e61b..13f5c56 100644 --- a/spec/simple/sql_scope_spec.rb +++ b/spec/simple/sql_scope_spec.rb @@ -140,6 +140,18 @@ def expects(expected_result, sql, *args) end end + context "with per=0" do + let(:result) { SQL.all(scope.paginate(per: 0)) } + + it "returns an empty result set" do + expect(result).to eq([]) + end + + it "adds total_count info to the .all return value" do + expect(result.total_count).to eq(2) + end + end + context "with per=2" do it "returns an empty array after the last page" do result = SQL.all(scope.paginate(per: 2, page: 2)) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f5e051b..5791026 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,6 +23,8 @@ ActiveRecord::Base.logger.level = Logger::INFO RSpec.configure do |config| + config.example_status_persistence_file_path = ".rspec.status" + config.run_all_when_everything_filtered = true config.filter_run focus: (ENV["CI"] != "true") config.expect_with(:rspec) { |c| c.syntax = :expect } diff --git a/spec/support/004_simplecov.rb b/spec/support/004_simplecov.rb index 122a80c..8b1507e 100644 --- a/spec/support/004_simplecov.rb +++ b/spec/support/004_simplecov.rb @@ -1,3 +1,5 @@ +unless ENV['SKIP_SIMPLE_COV'] + require "simplecov" # make sure multiple runs result in multiple result set. SimpleCov will take @@ -12,3 +14,5 @@ minimum_coverage 90 end + +end