diff --git a/README.md b/README.md index 2d40914d..cc3a050d 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,6 @@ Like views, it's possible to use N1QL to process some requests used for filterin ```ruby class Comment < CouchbaseOrm::Base attribute :author, :body, type: String - n1ql :all # => emits :id and will return all comments n1ql :by_author, emit_key: :author # Generates two functions: diff --git a/lib/couchbase-orm/base.rb b/lib/couchbase-orm/base.rb index ea298952..91e6ed29 100644 --- a/lib/couchbase-orm/base.rb +++ b/lib/couchbase-orm/base.rb @@ -16,6 +16,7 @@ require 'couchbase-orm/persistence' require 'couchbase-orm/associations' require 'couchbase-orm/types' +require 'couchbase-orm/relation' require 'couchbase-orm/proxies/bucket_proxy' require 'couchbase-orm/proxies/collection_proxy' require 'couchbase-orm/utilities/join' @@ -23,6 +24,7 @@ require 'couchbase-orm/utilities/index' require 'couchbase-orm/utilities/has_many' require 'couchbase-orm/utilities/ensure_unique' +require 'couchbase-orm/utilities/query_helper' module CouchbaseOrm @@ -46,26 +48,22 @@ def column_names # can't be an alias for now attribute_names end - if ActiveModel::VERSION::MAJOR < 6 - def attribute_names - attribute_types.keys - end + def abstract_class? + false + end - def abstract_class? - false - end + def connected? + true + end - def connected? - true - end + def table_exists? + true + end - def table_exists? - true + if ActiveModel::VERSION::MAJOR < 6 + def attribute_names + attribute_types.keys end - - # def partial_writes? - # partial_updates? && partial_inserts? - # end end end @@ -119,7 +117,9 @@ class Base include ::ActiveRecord::Timestamp # must be included after Persistence include Associations include Views + include QueryHelper include N1ql + include Relation extend Join extend Enum @@ -247,7 +247,6 @@ def [](key) end def []=(key, value) - CouchbaseOrm.logger.debug { "Set attribute #{key} to #{value}" } send(:"#{key}=", value) end diff --git a/lib/couchbase-orm/n1ql.rb b/lib/couchbase-orm/n1ql.rb index 4befc5d5..29a5640b 100644 --- a/lib/couchbase-orm/n1ql.rb +++ b/lib/couchbase-orm/n1ql.rb @@ -27,7 +27,6 @@ module ClassMethods # # @example Define some N1QL queries for a model # class Post < CouchbaseOrm::Base - # n1ql :all # n1ql :by_rating, emit_key: :rating # end # @@ -36,6 +35,8 @@ module ClassMethods # end # TODO: add range keys [:startkey, :endkey] def n1ql(name, query_fn: nil, emit_key: [], custom_order: nil, **options) + raise ArgumentError, "#{self} already respond_to? #{name}" if self.respond_to?(name) + emit_key = Array.wrap(emit_key) emit_key.each do |key| raise "unknown emit_key attribute for n1ql :#{name}, emit_key: :#{key}" if key && !attribute_names.include?(key.to_s) @@ -82,41 +83,7 @@ def index_n1ql(attr, validate: true, find_method: nil, n1ql_method: nil) def convert_values(keys, values) raise ArgumentError, "Empty keys but values are present, can't type cast" if keys.empty? && Array.wrap(values).any? keys.zip(Array.wrap(values)).map do |key, value_before_type_cast| - # cast value to type - value = if value_before_type_cast.is_a?(Array) - value_before_type_cast.map do |v| - attribute_types[key.to_s].serialize(attribute_types[key.to_s].cast(v)) - end - else - attribute_types[key.to_s].serialize(attribute_types[key.to_s].cast(value_before_type_cast)) - end - - CouchbaseOrm.logger.debug { "convert_values: #{key} => #{value_before_type_cast.inspect} => #{value.inspect} #{value.class} #{attribute_types[key.to_s]}" } - - value - end - end - - def quote(value) - if value.is_a? String - "'#{N1ql.sanitize(value)}'" - elsif value.is_a? Array - "[#{value.map{|v|quote(v)}.join(', ')}]" - elsif value.nil? - nil - else - N1ql.sanitize(value).to_s - end - end - - def build_match(key, value) - case - when value.nil? - "#{key} IS NOT VALUED" - when value.is_a?(Array) - "#{key} IN #{quote(value)}" - else - "#{key} = #{quote(value)}" + serialize_value(key, value_before_type_cast) end end diff --git a/lib/couchbase-orm/persistence.rb b/lib/couchbase-orm/persistence.rb index 10219c08..29f72552 100644 --- a/lib/couchbase-orm/persistence.rb +++ b/lib/couchbase-orm/persistence.rb @@ -90,7 +90,6 @@ def save(**options) # By default, #save! always runs validations. If any of them fail # CouchbaseOrm::Error::RecordInvalid gets raised, and the record won't be saved. def save!(**options) - CouchbaseOrm.logger.debug { "Will save! : #{id} -> #{attributes.to_s.truncate(200)}" } self.class.fail_validate!(self) unless self.save(**options) self end diff --git a/lib/couchbase-orm/proxies/results_proxy.rb b/lib/couchbase-orm/proxies/results_proxy.rb index 5d8ecc73..27d2897c 100644 --- a/lib/couchbase-orm/proxies/results_proxy.rb +++ b/lib/couchbase-orm/proxies/results_proxy.rb @@ -4,6 +4,8 @@ module CouchbaseOrm class ResultsProxy def initialize(proxyfied) @proxyfied = proxyfied + + raise ArgumentError, "Proxyfied object must respond to :to_a" unless @proxyfied.respond_to?(:to_a) proxyfied.public_methods.each do |method| next if self.public_methods.include?(method) @@ -13,7 +15,7 @@ def initialize(proxyfied) end end end - + def method_missing(m, *args, &block) @proxyfied.to_a.send(m, *args, &block) end diff --git a/lib/couchbase-orm/relation.rb b/lib/couchbase-orm/relation.rb new file mode 100644 index 00000000..c595c6ca --- /dev/null +++ b/lib/couchbase-orm/relation.rb @@ -0,0 +1,142 @@ +module CouchbaseOrm + module Relation + extend ActiveSupport::Concern + + class CouchbaseOrm_Relation + def initialize(model:, where: where = nil, order: order = nil, limit: limit = nil, _not: _not = false) + CouchbaseOrm::logger.debug "CouchbaseOrm_Relation init: #{model} where:#{where.inspect} not:#{_not.inspect} order:#{order.inspect} limit: #{limit}" + @model = model + @limit = limit + @where = [] + @order = {} + @order = merge_order(**order) if order + @where = merge_where(where, _not) if where + CouchbaseOrm::logger.debug "- #{to_s}" + end + + def to_s + "CouchbaseOrm_Relation: #{@model} where:#{@where.inspect} order:#{@order.inspect} limit: #{@limit}" + end + + def to_n1ql + bucket_name = @model.bucket.name + where = build_where + order = build_order + limit = build_limit + "select raw meta().id from `#{bucket_name}` where #{where} order by #{order} #{limit}" + end + + def query + CouchbaseOrm::logger.debug("Query: #{self}") + n1ql_query = to_n1ql + result = @model.cluster.query(n1ql_query, Couchbase::Options::Query.new(scan_consistency: :request_plus)) + CouchbaseOrm.logger.debug { "Relation query: #{n1ql_query} return #{result.rows.to_a.length} rows" } + N1qlProxy.new(result) + end + + def ids + query.to_a + end + + def count + query.count + end + + def to_ary + query.results { |res| @model.find(res) }.to_ary + end + + alias :to_a :to_ary + + delegate :each, :map, :collect, :to => :to_ary + + def delete_all + CouchbaseOrm::logger.debug{ "Delete all: #{self}" } + ids = query.to_a + CouchbaseOrm::Connection.bucket.default_collection.remove_multi(ids) unless ids.empty? + end + + def where(**conds) + CouchbaseOrm_Relation.new(**initializer_arguments.merge(where: merge_where(conds))) + end + + def not(**conds) + CouchbaseOrm_Relation.new(**initializer_arguments.merge(where: merge_where(conds, _not: true))) + end + + def order(*lorder, **horder) + CouchbaseOrm_Relation.new(**initializer_arguments.merge(order: merge_order(*lorder, **horder))) + end + + def limit(limit) + CouchbaseOrm_Relation.new(**initializer_arguments.merge(limit: limit)) + end + + def all + CouchbaseOrm_Relation.new(**initializer_arguments) + end + + private + + def build_limit + @limit ? "limit #{@limit}" : "" + end + + def initializer_arguments + { model: @model, order: @order, where: @where, limit: @limit } + end + + def merge_order(*lorder, **horder) + raise ArgumentError, "invalid order passed by list: #{lorder.inspect}, must be symbols" unless lorder.all? { |o| o.is_a? Symbol } + raise ArgumentError, "Invalid order passed by hash: #{horder.inspect}, must be symbol -> :asc|:desc" unless horder.all? { |k, v| k.is_a?(Symbol) && [:asc, :desc].include?(v) } + @order + .merge(Array.wrap(lorder).map{ |o| [o, :asc] }.to_h) + .merge(horder) + end + + def merge_where(conds, _not = false) + @where + (_not ? conds.to_a.map{|k,v|[k,v,:not]} : conds.to_a) + end + + def build_order + order = @order.map do |key, value| + "#{key} #{value}" + end.join(", ") + order.empty? ? "meta().id" : order + end + + def build_where + ([[:type, @model.design_document]] + @where).map do |key, value, opt| + opt == :not ? + @model.build_not_match(key, value) : + @model.build_match(key, value) + end.join(" AND ") + end + end + + module ClassMethods + def where(**conds) + CouchbaseOrm_Relation.new(model: self, where: conds) + end + + def not(**conds) + CouchbaseOrm_Relation.new(model: self, where: conds, _not: true) + end + + def order(*ordersl, **ordersh) + order = ordersh.reverse_merge(ordersl.map{ |o| [o, :asc] }.to_h) + CouchbaseOrm_Relation.new(model: self, order: order) + end + + def limit(limit) + CouchbaseOrm_Relation.new(model: self, limit: limit) + end + + def all + CouchbaseOrm_Relation.new(model: self) + end + + delegate :ids, :delete_all, :count, to: :all + end + end +end diff --git a/lib/couchbase-orm/utilities/query_helper.rb b/lib/couchbase-orm/utilities/query_helper.rb new file mode 100644 index 00000000..7b5a159b --- /dev/null +++ b/lib/couchbase-orm/utilities/query_helper.rb @@ -0,0 +1,59 @@ +module CouchbaseOrm + module QueryHelper + extend ActiveSupport::Concern + + module ClassMethods + + def build_match(key, value) + case + when value.nil? + "#{key} IS NOT VALUED" + when value.is_a?(Array) && value.include?(nil) + "(#{build_match(key, nil)} OR #{build_match(key, value.compact)})" + when value.is_a?(Array) + "#{key} IN #{quote(value)}" + else + "#{key} = #{quote(value)}" + end + end + + def build_not_match(key, value) + case + when value.nil? + "#{key} IS VALUED" + when value.is_a?(Array) && value.include?(nil) + "(#{build_not_match(key, nil)} AND #{build_not_match(key, value.compact)})" + when value.is_a?(Array) + "#{key} NOT IN #{quote(value)}" + else + "#{key} != #{quote(value)}" + end + end + + def serialize_value(key, value_before_type_cast) + value = + if value_before_type_cast.is_a?(Array) + value_before_type_cast.map do |v| + attribute_types[key.to_s].serialize(attribute_types[key.to_s].cast(v)) + end + else + attribute_types[key.to_s].serialize(attribute_types[key.to_s].cast(value_before_type_cast)) + end + CouchbaseOrm.logger.debug { "convert_values: #{key} => #{value_before_type_cast.inspect} => #{value.inspect} #{value.class} #{attribute_types[key.to_s]}" } + value + end + + def quote(value) + if value.is_a? String + "'#{N1ql.sanitize(value)}'" + elsif value.is_a? Array + "[#{value.map{|v|quote(v)}.join(', ')}]" + elsif value.nil? + nil + else + N1ql.sanitize(value).to_s + end + end + end + end +end diff --git a/lib/couchbase-orm/views.rb b/lib/couchbase-orm/views.rb index 98399954..b2e55493 100644 --- a/lib/couchbase-orm/views.rb +++ b/lib/couchbase-orm/views.rb @@ -23,6 +23,8 @@ module ClassMethods # # ... # end def view(name, map: nil, emit_key: nil, reduce: nil, **options) + raise ArgumentError, "#{self} already respond_to? #{name}" if self.respond_to?(name) + if emit_key.class == Array emit_key.each do |key| raise "unknown emit_key attribute for view :#{name}, emit_key: :#{key}" if key && !attribute_names.include?(key.to_s) diff --git a/spec/has_many_spec.rb b/spec/has_many_spec.rb index ccf47d5f..68dbc417 100644 --- a/spec/has_many_spec.rb +++ b/spec/has_many_spec.rb @@ -13,15 +13,15 @@ @object_test_class.ensure_design_document! @object_rating_test_class.ensure_design_document! - @rating_test_class.all.each(&:destroy) - @object_test_class.all.each(&:destroy) - @object_rating_test_class.all.each(&:destroy) + @rating_test_class.delete_all + @object_test_class.delete_all + @object_rating_test_class.delete_all end after :each do - @rating_test_class.all.each(&:destroy) - @object_test_class.all.each(&:destroy) - @object_rating_test_class.all.each(&:destroy) + @rating_test_class.delete_all + @object_test_class.delete_all + @object_rating_test_class.delete_all end it "should return matching results" do @@ -42,7 +42,7 @@ first.destroy expect { @rating_test_class.find rate.id }.to raise_error(Couchbase::Error::DocumentNotFound) - expect(@rating_test_class.all.count).to be(1) + expect(@rating_test_class.send(:"#{@context}_all").count).to be(1) end it "should work through a join model" do @@ -79,7 +79,7 @@ context 'with view' do class ObjectRatingViewTest < CouchbaseOrm::Base join :object_view_test, :rating_view_test - view :all + view :view_all end class RatingViewTest < CouchbaseOrm::Base @@ -87,14 +87,14 @@ class RatingViewTest < CouchbaseOrm::Base belongs_to :object_view_test has_many :object_view_tests, through: :object_rating_view_test - view :all + view :view_all end class ObjectViewTest < CouchbaseOrm::Base attribute :name, type: String has_many :rating_view_tests, dependent: :destroy - view :all + view :view_all end include_examples("has_many example", context: :view) @@ -103,7 +103,8 @@ class ObjectViewTest < CouchbaseOrm::Base context 'with n1ql' do class ObjectRatingN1qlTest < CouchbaseOrm::Base join :object_n1ql_test, :rating_n1ql_test - n1ql :all + + n1ql :n1ql_all end class RatingN1qlTest < CouchbaseOrm::Base @@ -111,13 +112,16 @@ class RatingN1qlTest < CouchbaseOrm::Base belongs_to :object_n1ql_test has_many :object_n1ql_tests, through: :object_rating_n1ql_test, type: :n1ql - n1ql :all + + n1ql :n1ql_all end class ObjectN1qlTest < CouchbaseOrm::Base attribute :name, type: String + has_many :rating_n1ql_tests, dependent: :destroy, type: :n1ql - n1ql :all + + n1ql :n1ql_all end include_examples("has_many example", context: :n1ql) diff --git a/spec/index_spec.rb b/spec/index_spec.rb index a3698e49..c9d0a26a 100644 --- a/spec/index_spec.rb +++ b/spec/index_spec.rb @@ -4,7 +4,6 @@ class IndexTest < CouchbaseOrm::Base - n1ql :all attribute :email, type: String attribute :name, type: String, default: :joe ensure_unique :email, presence: false diff --git a/spec/n1ql_spec.rb b/spec/n1ql_spec.rb index 7b5e3aed..f5ba9971 100644 --- a/spec/n1ql_spec.rb +++ b/spec/n1ql_spec.rb @@ -7,16 +7,13 @@ class N1QLTest < CouchbaseOrm::Base attribute :lastname, type: String enum rating: [:awesome, :good, :okay, :bad], default: :okay - n1ql :all - n1ql :by_custom_rating, emit_key: [:name, :rating], query_fn: proc { |bucket, _values| - cluster.query("SELECT raw meta().id FROM `#{bucket.name}` WHERE rating IN [1, 2] ORDER BY name ASC") + n1ql :by_custom_rating, emit_key: [:rating], query_fn: proc { |bucket, _values, options| + cluster.query("SELECT raw meta().id FROM `#{bucket.name}` WHERE type = 'n1_ql_test' AND rating IN [1, 2] ORDER BY name ASC", options) } n1ql :by_name, emit_key: [:name] n1ql :by_lastname, emit_key: [:lastname] - n1ql :by_rating, emit_key: :rating - n1ql :by_custom_rating, query_fn: proc { |bucket, _values, options| - cluster.query("SELECT raw meta().id FROM `#{bucket.name}` where type = 'n1_ql_test' AND rating IN [1,2] ORDER BY name ASC", options) - } + n1ql :by_rating_emit, emit_key: :rating + n1ql :by_custom_rating_values, emit_key: [:rating], query_fn: proc { |bucket, values, options| cluster.query("SELECT raw meta().id FROM `#{bucket.name}` where type = 'n1_ql_test' AND rating IN #{quote(values[0])} ORDER BY name ASC", options) } @@ -24,14 +21,18 @@ class N1QLTest < CouchbaseOrm::Base n1ql :by_rating_without_docs, emit_key: :rating, include_docs: false # This generates both: - # view :by_rating, emit_key: :rating # same as above + # view :by_rating, emit_key: :rating # def self.find_by_rating(rating); end # also provide this helper function index_n1ql :rating end describe CouchbaseOrm::N1ql do before(:each) do - N1QLTest.all.each(&:destroy) + N1QLTest.delete_all + end + + it "should not allow n1ql to override existing methods" do + expect { N1QLTest.n1ql :all }.to raise_error(ArgumentError) end it "should perform a query and return the n1ql" do @@ -162,6 +163,6 @@ class N1QLTest < CouchbaseOrm::Base end after(:all) do - N1QLTest.all.to_a.each(&:destroy) + N1QLTest.delete_all end end diff --git a/spec/relation_spec.rb b/spec/relation_spec.rb new file mode 100644 index 00000000..8efbe5a9 --- /dev/null +++ b/spec/relation_spec.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true, encoding: ASCII-8BIT + +require File.expand_path("../support", __FILE__) + + +class RelationModel < CouchbaseOrm::Base + attribute :name, :string + attribute :last_name, :string + attribute :active, :boolean + attribute :age, :integer +end + +describe CouchbaseOrm::Relation do + before(:each) do + RelationModel.delete_all + CouchbaseOrm.logger.debug "Cleaned before tests" + end + + after(:all) do + CouchbaseOrm.logger.debug "Cleanup after all tests" + RelationModel.delete_all + end + + it "should return a relation" do + expect(RelationModel.all).to be_a(CouchbaseOrm::Relation::CouchbaseOrm_Relation) + end + + it "should query with conditions" do + RelationModel.create! name: :bob, active: true, age: 10 + RelationModel.create! name: :alice, active: true, age: 20 + RelationModel.create! name: :john, active: false, age: 30 + expect(RelationModel.where(active: true).count).to eq(2) + + expect(RelationModel.where(active: true).to_a.map(&:name)).to match_array(%w[bob alice]) + expect(RelationModel.where(active: true).where(age: 10).to_a.map(&:name)).to match_array(%w[bob]) + end + + it "should query with merged conditions" do + RelationModel.create! name: :bob, active: true, age: 10 + RelationModel.create! name: :bob, active: false, age: 10 + RelationModel.create! name: :alice, active: true, age: 20 + RelationModel.create! name: :john, active: false, age: 30 + + expect(RelationModel.where(active: true).where(name: 'bob').count).to eq(1) + end + + + it "should count without loading models" do + RelationModel.create! name: :bob, active: true, age: 10 + RelationModel.create! name: :alice, active: false, age: 20 + + expect(RelationModel).not_to receive(:find) + + expect(RelationModel.where(active: true).count).to eq(1) + end + + it "Should delete_all" do + RelationModel.create! + RelationModel.create! + RelationModel.delete_all + expect(RelationModel.ids).to match_array([]) + end + + it "Should delete_all with conditions" do + RelationModel.create! + jane = RelationModel.create! name: "Jane" + RelationModel.where(name: nil).delete_all + expect(RelationModel.ids).to match_array([jane.id]) + end + + it "Should query ids" do + expect(RelationModel.ids).to match_array([]) + m1 = RelationModel.create! + m2 = RelationModel.create! + expect(RelationModel.ids).to match_array([m1.id, m2.id]) + end + + it "Should query ids with conditions" do + m1 = RelationModel.create!(active: true, name: "Jane") + _m2 = RelationModel.create!(active: false, name: "Bob" ) + _m3 = RelationModel.create!(active: false, name: "Jane") + expect(RelationModel.where(active: true, name: "Jane").ids).to match_array([m1.id]) + end + + it "Should query ids with conditions and limit" do + RelationModel.create!(active: true, name: "Jane", age: 2) + RelationModel.create!(active: false, name: "Bob", age: 3) + m = RelationModel.create!(active: true, name: "Jane", age: 1) + RelationModel.create!(active: false, name: "Jane", age: 0) + + expect(RelationModel.where(active: true, name: "Jane").order(:age).limit(1).ids).to match_array([m.id]) + expect(RelationModel.limit(1).where(active: true, name: "Jane").order(:age).ids).to match_array([m.id]) + end + + it "Should query ids with order" do + m1 = RelationModel.create!(age: 10, name: 'b') + m2 = RelationModel.create!(age: 20, name: 'a') + expect(RelationModel.order(age: :desc).ids).to match_array([m2.id, m1.id]) + expect(RelationModel.order(age: :asc).ids).to match_array([m1.id, m2.id]) + expect(RelationModel.order(name: :desc).ids).to match_array([m1.id, m2.id]) + expect(RelationModel.order(name: :asc).ids).to match_array([m2.id, m1.id]) + expect(RelationModel.order(:name).ids).to match_array([m2.id, m1.id]) + expect(RelationModel.order(:age).ids).to match_array([m1.id, m2.id]) + end + + it "Should query with list order" do + m1 = RelationModel.create!(age: 20, name: 'b') + m2 = RelationModel.create!(age: 5, name: 'a') + m3 = RelationModel.create!(age: 20, name: 'a') + expect(RelationModel.order(:age, :name).ids).to match_array([m2.id, m3.id, m1.id]) + end + + it "Should query with chained order" do + m1 = RelationModel.create!(age: 10, name: 'b') + m2 = RelationModel.create!(age: 20, name: 'a') + m3 = RelationModel.create!(age: 20, name: 'c') + expect(RelationModel.order(age: :desc).order(name: :asc).ids).to match_array([m2.id, m3.id, m1.id]) + end + + it "Should query with order chained with list" do + m1 = RelationModel.create!(age: 20, name: 'b') + m2 = RelationModel.create!(age: 5, name: 'a') + m3 = RelationModel.create!(age: 20, name: 'a', last_name: 'c') + m4 = RelationModel.create!(age: 20, name: 'a', last_name: 'a') + expect(RelationModel.order(:age, :name).order(:last_name).ids).to match_array([m2.id, m4.id, m3.id, m1.id]) + end + + it "Should query all" do + m1 = RelationModel.create!(active: true) + m2 = RelationModel.create!(active: false) + expect(RelationModel.all).to match_array([m1, m2]) + end + + it "should query all with condition and order" do + m1 = RelationModel.create!(active: true, age: 10) + m2 = RelationModel.create!(active: true, age: 20) + _m3 = RelationModel.create!(active: false, age: 30) + expect(RelationModel.where(active: true).order(age: :desc).all).to match_array([m2, m1]) + expect(RelationModel.all.where(active: true).order(age: :asc)).to match_array([m1, m2]) + end + + it "should return a relation when using not" do + expect(RelationModel.not(active: true)).to be_a(CouchbaseOrm::Relation::CouchbaseOrm_Relation) + expect(RelationModel.all.not(active: true)).to be_a(CouchbaseOrm::Relation::CouchbaseOrm_Relation) + end + + it "should have a to_ary method" do + expect(RelationModel.not(active: true)).to respond_to(:to_ary) + expect(RelationModel.all.not(active: true)).to respond_to(:to_ary) + end + + it "should have a each method" do + expect(RelationModel.not(active: true)).to respond_to(:each) + expect(RelationModel.all.not(active: true)).to respond_to(:each) + end + + it "should query true boolean" do + m1 = RelationModel.create!(active: true) + _m2 = RelationModel.create!(active: false) + _m3 = RelationModel.create!(active: nil) + expect(RelationModel.where(active: true)).to match_array([m1]) + end + + it "should not query true boolean" do + _m1 = RelationModel.create!(active: true) + m2 = RelationModel.create!(active: false) + _m3 = RelationModel.create!(active: nil) + expect(RelationModel.not(active: true)).to match_array([m2]) # keep ActiveRecord compatibility by not returning _m3 + end + + it "should query false boolean" do + _m1 = RelationModel.create!(active: true) + m2 = RelationModel.create!(active: false) + _m3 = RelationModel.create!(active: nil) + expect(RelationModel.where(active: false)).to match_array([m2]) + end + + it "should not query false boolean" do + m1 = RelationModel.create!(active: true) + _m2 = RelationModel.create!(active: false) + _m3 = RelationModel.create!(active: nil) + expect(RelationModel.not(active: false)).to match_array([m1]) # keep ActiveRecord compatibility by not returning _m3 + end + + it "should query nil boolean" do + _m1 = RelationModel.create!(active: true) + _m2 = RelationModel.create!(active: false) + m3 = RelationModel.create!(active: nil) + expect(RelationModel.where(active: nil)).to match_array([m3]) + end + + it "should not query nil boolean" do + m1 = RelationModel.create!(active: true) + m2 = RelationModel.create!(active: false) + _m3 = RelationModel.create!(active: nil) + expect(RelationModel.not(active: nil)).to match_array([m1, m2]) + end + + it "should query nil and false boolean" do + _m1 = RelationModel.create!(active: true) + m2 = RelationModel.create!(active: false) + m3 = RelationModel.create!(active: nil) + expect(RelationModel.where(active: [false, nil])).to match_array([m2, m3]) + end + + it "should not query nil and false boolean" do + m1 = RelationModel.create!(active: true) + _m2 = RelationModel.create!(active: false) + _m3 = RelationModel.create!(active: nil) + expect(RelationModel.not(active: [false, nil])).to match_array([m1]) + end +end + diff --git a/spec/type_spec.rb b/spec/type_spec.rb index ff59e7d9..935095ee 100644 --- a/spec/type_spec.rb +++ b/spec/type_spec.rb @@ -21,8 +21,6 @@ class TypeTest < CouchbaseOrm::Base attribute :precision_time, :datetime3decimal attribute :active, :boolean - n1ql :all - index :age, presence: false index :renewal_date, presence: false index :some_time, presence: false @@ -39,8 +37,6 @@ class N1qlTypeTest < CouchbaseOrm::Base attribute :precision_time, :datetime3decimal attribute :active, :boolean - n1ql :all - index_n1ql :name, validate: false index_n1ql :age, validate: false index_n1ql :size, validate: false @@ -84,8 +80,8 @@ class N1qlTypeTest < CouchbaseOrm::Base describe CouchbaseOrm::Base do before(:each) do - TypeTest.all.each(&:destroy) - N1qlTypeTest.all.each(&:destroy) + TypeTest.delete_all + N1qlTypeTest.delete_all end it "should be createable" do diff --git a/spec/views_spec.rb b/spec/views_spec.rb index 08ddb768..3ae3f865 100644 --- a/spec/views_spec.rb +++ b/spec/views_spec.rb @@ -7,11 +7,10 @@ class ViewTest < CouchbaseOrm::Base attribute :name, type: String enum rating: [:awesome, :good, :okay, :bad], default: :okay - view :all - view :by_rating, emit_key: :rating + view :vall # This generates both: - # view :by_rating, emit_key: :rating # same as above + # view :by_rating, emit_key: :rating # def self.find_by_rating(rating); end # also provide this helper function index_view :rating end @@ -19,19 +18,23 @@ class ViewTest < CouchbaseOrm::Base describe CouchbaseOrm::Views do before(:each) do - ViewTest.all.each(&:destroy) + ViewTest.delete_all rescue Couchbase::Error::DesignDocumentNotFound # ignore (FIXME: check before merge) mainly because if there is nothing in all we should not have an error end after(:each) do - ViewTest.all.each(&:destroy) + ViewTest.delete_all rescue Couchbase::Error::InternalServerFailure # ignore (FIXME: check before merge) rescue Couchbase::Error::DesignDocumentNotFound # ignore (FIXME: check before merge) (7.1) end + it "should not allow n1ql to override existing methods" do + expect { ViewTest.view :all }.to raise_error(ArgumentError) + end + it "should save a new design document" do begin ViewTest.bucket.view_indexes.drop_design_document(ViewTest.design_document, :production) @@ -48,14 +51,14 @@ class ViewTest < CouchbaseOrm::Base end it "should return an empty array when there is no objects" do - expect(ViewTest.all).to eq([]) + expect(ViewTest.vall).to eq([]) end it "should perform a map-reduce and return the view" do ViewTest.ensure_design_document! ViewTest.create! name: :bob, rating: :good - docs = ViewTest.all.collect { |ob| + docs = ViewTest.vall.collect { |ob| ob.destroy ob.name }