Module: CouchbaseOrm::Views::ClassMethods

Defined in:
lib/couchbase-orm/views.rb

Constant Summary collapse

ViewDefaults =
{include_docs: true}.freeze

Instance Method Summary collapse

Instance Method Details

#ensure_design_document!Boolean

Ensures that the Couchbase design document is up-to-date with the defined views.

This method checks the current state of the design document in the Couchbase bucket and updates it if there are any discrepancies with the views defined in the current class.

Examples:

Ensure the design document is up-to-date

class User
  include CouchbaseOrm::Model

  view :by_email, emit_key: :email
  view :by_username, emit_key: :username
end

# This will check the current design document for discrepancies and update if needed
User.ensure_design_document!

Returns:

  • (Boolean)

    `true` if the design document was updated, `false` otherwise.

Raises:

  • (Couchbase::Error::DesignDocumentNotFound)

    if the design document is not found.



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/couchbase-orm/views.rb', line 180

def ensure_design_document!
  return false unless @views && !@views.empty?

  existing = {}
  update_required = false

    # Grab the existing view details
  begin
    ddoc = bucket.view_indexes.get_design_document(@design_document, :production)
  rescue Couchbase::Error::DesignDocumentNotFound
  end
  existing = ddoc.views if ddoc
  views_actual = {}
    # Fill in the design documents
  @views.each do |name, document|
    views_actual[name.to_s] = Couchbase::Management::View.new(
      document[:map]&.gsub('{{design_document}}', @design_document),
      document[:reduce]&.gsub('{{design_document}}', @design_document)
    )
  end

    # Check there are no changes we need to apply
  views_actual.each do |name, desired|
    check = existing[name]
    if check
      cmap = (check.map || '').gsub(/\s+/, '')
      creduce = (check.reduce || '').gsub(/\s+/, '')
      dmap = (desired.map || '').gsub(/\s+/, '')
      dreduce = (desired.reduce || '').gsub(/\s+/, '')

      unless cmap == dmap && creduce == dreduce
        update_required = true
        break
      end
    else
      update_required = true
      break
    end
  end

    # Updated the design document
  if update_required
    document = Couchbase::Management::DesignDocument.new
    document.views = views_actual
    document.name = @design_document
    bucket.view_indexes.upsert_design_document(document, :production)

    true
  else
    false
  end
end

#index_view(attr, validate: true, find_method: nil, view_method: nil) ⇒ void

This method returns an undefined value.

Sets up a Couchbase view and a corresponding finder method for the given attribute.

Couchbase views allow you to create custom queries on your data using map and reduce functions. They are defined in design documents and can be queried to retrieve documents or aggregated data. For more details, see the [Couchbase Views Basics](docs.couchbase.com/server/current/learn/views/views-basics.html).

Examples:

Define an index view for the `email` attribute

class User
  include CouchbaseOrm::Model
  index_view :email
end

# This creates a view method `by_email` and a finder method `find_by_email`
users_by_email = User.by_email(key: 'user@example.com')
user = User.find_by_email('user@example.com')

Define an index view for the `username` attribute with custom method names

class User
  include CouchbaseOrm::Model
  index_view :username, find_method: :find_user_by_username, view_method: :by_username_view
end

# This creates a view method `by_username_view` and a finder method `find_user_by_username`
users_by_username = User.by_username_view(key: 'john_doe')
user = User.find_user_by_username('john_doe')

Parameters:

  • attr (Symbol)

    The attribute to create the view and finder method for.

  • validate (Boolean) (defaults to: true)

    Whether to validate the presence of the attribute (default: true).

  • find_method (Symbol, String, nil) (defaults to: nil)

    The name of the finder method to be created (optional).

  • view_method (Symbol, String, nil) (defaults to: nil)

    The name of the view method to be created (optional).



147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/couchbase-orm/views.rb', line 147

def index_view(attr, validate: true, find_method: nil, view_method: nil)
  view_method ||= "by_#{attr}"
  find_method ||= "find_#{view_method}"

  validates(attr, presence: true) if validate
  view view_method, emit_key: attr

  instance_eval "
              def self.#{find_method}(#{attr})
                  #{view_method}(key: #{attr})
              end
          ", __FILE__, __LINE__ - 4
end

#view(name, map: nil, emit_key: nil, reduce: nil, **options) ⇒ void

This method returns an undefined value.

Defines a Couchbase view with dynamic method creation.

Couchbase views allow you to create custom queries on your data using map and reduce functions. They are defined in design documents and can be queried to retrieve documents or aggregated data. For more details, see the [Couchbase Views Basics](docs.couchbase.com/server/current/learn/views/views-basics.html).

Examples:

Define a simple view to emit documents by `created_at`

class MyModel
  include CouchbaseOrm::Model
  view :by_created_at, emit_key: :created_at
end

# You can now use the dynamically defined method:
results = MyModel.by_created_at

Define a view with multiple emit keys

class MyModel
  include CouchbaseOrm::Model
  view :by_user_and_date, emit_key: [:user_id, :created_at]
end

# Use the defined view method
results = MyModel.by_user_and_date

Define a view with a custom map function

class MyModel
  include CouchbaseOrm::Model
  view :by_custom_map, map: <<-MAP
    function (doc) {
      if (doc.type === "my_model") {
        emit(doc.custom_field, null);
      }
    }
  MAP
end

# Use the defined view method
results = MyModel.by_custom_map

Parameters:

  • name (Symbol, String)

    The name of the view.

  • map (String, nil) (defaults to: nil)

    The map function for the view (optional).

  • emit_key (Symbol, Array<Symbol>, nil) (defaults to: nil)

    The key(s) to emit from the map function (optional).

  • reduce (String, nil) (defaults to: nil)

    The reduce function for the view (optional).

  • options (Hash)

    Additional options for the view query.

Raises:

  • (ArgumentError)

    if the class already responds to the given name.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/couchbase-orm/views.rb', line 57

def view(name, map: nil, emit_key: nil, reduce: nil, **options)
  raise ArgumentError.new("#{self} already respond_to? #{name}") if self.respond_to?(name)

  if emit_key.is_a?(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)
    end
  elsif emit_key && !attribute_names.include?(emit_key.to_s)
    raise "unknown emit_key attribute for view :#{name}, emit_key: :#{emit_key}"
  end

  options = ViewDefaults.merge(options)

  method_opts = {}
  method_opts[:map]    = map    if map
  method_opts[:reduce] = reduce if reduce

  unless method_opts.key? :map
    if emit_key.instance_of?(Array)
      method_opts[:map] = <<~EMAP
        function(doc) {
            if (doc.type === "{{design_document}}") {
                emit([#{emit_key.map{ |key| 'doc.' + key.to_s }.join(',')}], null);
            }
        }
      EMAP
    else
      emit_key ||= :created_at
      method_opts[:map] = <<~EMAP
        function(doc) {
            if (doc.type === "{{design_document}}") {
                emit(doc.#{emit_key}, null);
            }
        }
      EMAP
    end
  end

  @views ||= {}

  name = name.to_sym
  @views[name] = method_opts

  singleton_class.__send__(:define_method, name) do |**opts, &result_modifier|
    opts = options.merge(opts).reverse_merge(scan_consistency: :request_plus)
    CouchbaseOrm.logger.debug("View [#{@design_document}, #{name.inspect}] options: #{opts.inspect}")
    if result_modifier
      include_docs(bucket.view_query(@design_document, name.to_s,
                                     Couchbase::Options::View.new(**opts.except(:include_docs)))).map(&result_modifier)
    elsif opts[:include_docs]
      include_docs(bucket.view_query(@design_document, name.to_s,
                                     Couchbase::Options::View.new(**opts.except(:include_docs))))
    else
      bucket.view_query(@design_document, name.to_s, Couchbase::Options::View.new(**opts.except(:include_docs)))
    end
  end
end