Skip to content

Commit

Permalink
Merge pull request #40 from doctolib/activemodel7-couchbase-ruby-client
Browse files Browse the repository at this point in the history
STEP 2 Activemodel7 couchbase ruby client
  • Loading branch information
giallon authored Sep 19, 2022
2 parents 099f336 + 43862c6 commit 407f4e3
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 20 deletions.
2 changes: 2 additions & 0 deletions ci/run_couchbase.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ sleep 5
sleep 1
/opt/couchbase/bin/couchbase-cli user-manage -c 127.0.0.1:8091 -u admin -p password --set --rbac-username $USER --rbac-password $PASSWORD --rbac-name "Auto Tester" --roles admin --auth-domain local
curl http://admin:password@localhost:8093/query/service -d "statement=CREATE INDEX \`default_type\` ON \`$BUCKET\`(\`type\`)"
curl http://admin:password@localhost:8093/query/service -d "statement=CREATE INDEX \`default_rating\` ON \`$BUCKET\`(\`rating\`)"
curl http://admin:password@localhost:8093/query/service -d "statement=CREATE INDEX \`default_name\` ON \`$BUCKET\`(\`name\`)"
14 changes: 7 additions & 7 deletions lib/couchbase-orm/n1ql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module ClassMethods
# # ...
# end
# TODO: add range keys [:startkey, :endkey]
def n1ql(name, query_fn: nil, emit_key: [], **options)
def n1ql(name, query_fn: nil, emit_key: [], custom_order: nil, **options)
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)
Expand All @@ -50,7 +50,7 @@ def n1ql(name, query_fn: nil, emit_key: [], **options)
singleton_class.__send__(:define_method, name) do |key: NO_VALUE, **opts, &result_modifier|
opts = options.merge(opts).reverse_merge(scan_consistency: :request_plus)
values = key == NO_VALUE ? NO_VALUE : convert_values(method_opts[:emit_key], key)
current_query = run_query(method_opts[:emit_key], values, query_fn, **opts.except(:include_docs, :key))
current_query = run_query(method_opts[:emit_key], values, query_fn, custom_order: custom_order, **opts.except(:include_docs, :key))
if result_modifier
opts[:include_docs] = true
current_query.results &result_modifier
Expand Down Expand Up @@ -81,7 +81,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|
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|
Expand Down Expand Up @@ -110,7 +110,7 @@ def quote(value)
end

def build_match(key, value)
case
case
when value.nil?
"ISNULL(#{key})"
when value.is_a?(Array)
Expand All @@ -122,7 +122,7 @@ def build_match(key, value)

def build_where(keys, values)
where = values == NO_VALUE ? '' : keys.zip(Array.wrap(values))
.reject { |key, value| key.nil? && value.nil? }
.reject { |key, value| key.nil? && value.nil? }
.map { |key, value| build_match(key, value) }
.join(" AND ")
"type=\"#{design_document}\" #{"AND " + where unless where.blank?}"
Expand All @@ -139,13 +139,13 @@ def build_limit(limit)
limit ? "limit #{limit}" : ""
end

def run_query(keys, values, query_fn, descending: false, limit: nil, **options)
def run_query(keys, values, query_fn, custom_order: nil, descending: false, limit: nil, **options)
if query_fn
N1qlProxy.new(query_fn.call(bucket, values, Couchbase::Options::Query.new(**options)))
else
bucket_name = bucket.name
where = build_where(keys, values)
order = build_order(keys, descending)
order = custom_order || build_order(keys, descending)
limit = build_limit(limit)
n1ql_query = "select raw meta().id from `#{bucket_name}` where #{where} order by #{order} #{limit}"
result = cluster.query(n1ql_query, Couchbase::Options::Query.new(**options))
Expand Down
8 changes: 4 additions & 4 deletions lib/couchbase-orm/persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def destroy(with_cas: false, **options)
options[:cas] = @__metadata__.cas if with_cas
CouchbaseOrm.logger.debug "Data - Destroy #{id}"
self.class.collection.remove(id, **options)

self.id = nil

clear_changes_information
Expand Down Expand Up @@ -225,11 +225,11 @@ def touch(**options)
protected

def serialized_attributes
attributes.map { |k, v|
[k, self.class.attribute_types[k].serialize(v)]
attributes.map { |k, v|
[k, self.class.attribute_types[k].serialize(v)]
}.to_h
end

def _update_record(*_args, with_cas: false, **options)
return false unless perform_validations(:update, options)
return true unless changed?
Expand Down
1 change: 0 additions & 1 deletion lib/couchbase-orm/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
module Rails #:nodoc:
module Couchbase #:nodoc:
class Railtie < Rails::Railtie #:nodoc:

config.couchbase_orm = ActiveSupport::OrderedOptions.new
config.couchbase_orm.ensure_design_documents = true

Expand Down
13 changes: 6 additions & 7 deletions spec/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

require File.expand_path("../support", __FILE__)


class BaseTest < CouchbaseOrm::Base
attribute :name, :string
attribute :job, :string
Expand All @@ -18,7 +17,7 @@ class TimestampTest < CouchbaseOrm::Base

describe CouchbaseOrm::Base do
it "should be comparable to other objects" do
base = BaseTest.create!(name: 'joe')
base = BaseTest.create!(name: 'joe')
base2 = BaseTest.create!(name: 'joe')
base3 = BaseTest.create!(ActiveSupport::HashWithIndifferentAccess.new(name: 'joe'))

Expand Down Expand Up @@ -48,7 +47,7 @@ class TimestampTest < CouchbaseOrm::Base
base_loaded = BaseTest.new(resp, id: base.id)

expect(base_loaded.id).to eq(base.id)
expect(base_loaded).to eq(base)
expect(base_loaded).to eq(base)
expect(base_loaded).not_to be(base)

base.destroy
Expand All @@ -66,8 +65,8 @@ class TimestampTest < CouchbaseOrm::Base
base = BaseTest.create!(name: 'joe')

base_id = base.id
expect(base.to_json).to eq({id: base_id, name: 'joe', job: nil}.to_json)
expect(base.to_json(only: :name)).to eq({name: 'joe'}.to_json)
expect(base.to_json).to eq({ id: base_id, name: 'joe', job: nil }.to_json)
expect(base.to_json(only: :name)).to eq({ name: 'joe' }.to_json)

base.destroy
end
Expand All @@ -87,19 +86,19 @@ class TimestampTest < CouchbaseOrm::Base
expect(base.changes.empty?).to be(false)

# Attributes are set by initializer from hash
base = BaseTest.new({name: 'bob'})
base = BaseTest.new({ name: 'bob' })
expect(base.changes.empty?).to be(false)
expect(base.previous_changes.empty?).to be(true)

# A saved model should have no changes
base = BaseTest.create!(name: 'joe')
expect(base.changes.empty?).to be(true)
expect(base.previous_changes.empty?).to be(true)

# Attributes are copied from the existing model
base = BaseTest.new(base)
expect(base.changes.empty?).to be(false)
expect(base.previous_changes.empty?).to be(true)

ensure
base.destroy if base.id
end
Expand Down
49 changes: 48 additions & 1 deletion spec/n1ql_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@

class N1QLTest < CouchbaseOrm::Base
attribute :name, type: String
attribute :lastname, type: String
enum rating: [:awesome, :good, :okay, :bad], default: :okay

n1ql :all
n1ql :by_name, emit_key: :name
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_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_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 #{values[0]} ORDER BY name ASC", options)
}
n1ql :by_rating_reverse, emit_key: :rating, custom_order: "name DESC"
n1ql :by_rating_without_docs, emit_key: :rating, include_docs: false

# This generates both:
# view :by_rating, emit_key: :rating # same as above
Expand Down Expand Up @@ -84,6 +91,46 @@ class N1QLTest < CouchbaseOrm::Base
}

expect(Set.new(docs)).to eq(Set.new(%w[bob jane]))

docs = N1QLTest.by_custom_rating().collect { |ob|
ob.name
}

expect(Set.new(docs)).to eq(Set.new(%w[bob jane mel]))
end

it "should return matching results with reverse order" do
N1QLTest.create! name: :bob, rating: :awesome
N1QLTest.create! name: :jane, rating: :awesome
N1QLTest.create! name: :greg, rating: :bad
N1QLTest.create! name: :mel, rating: :good

docs = N1QLTest.by_rating_reverse(key: 1).collect { |ob|
ob.name
}

expect(docs).to eq(%w[jane bob])
end

it "should return matching results without full documents" do
inst_bob = N1QLTest.create! name: :bob, rating: :awesome
inst_jane = N1QLTest.create! name: :jane, rating: :awesome
N1QLTest.create! name: :greg, rating: :bad
N1QLTest.create! name: :mel, rating: :good

docs = N1QLTest.by_rating_without_docs(key: 1)

expect(Set.new(docs)).to eq(Set.new([inst_bob.id, inst_jane.id]))
end

it "should return matching results with nil usage" do
N1QLTest.create! name: :bob, lastname: nil
N1QLTest.create! name: :jane, lastname: "dupond"

docs = N1QLTest.by_lastname(key: [nil]).collect { |ob|
ob.name
}
expect(docs).to eq(%w[bob])
end

it "should return matching results with custom n1ql query" do
Expand Down

0 comments on commit 407f4e3

Please sign in to comment.