Module: Cassie::Model::ClassMethods

Defined in:
lib/cassie/model.rb

Instance Method Summary collapse

Instance Method Details

#batch(options = nil) ⇒ Object

All insert, update, and delete calls within the block will be sent as a single batch to Cassandra. The consistency level will default to the write consistency level if it’s been set.



335
336
337
338
339
340
# File 'lib/cassie/model.rb', line 335

def batch(options = nil)
  options = consistency_options(write_consistency, options)
  connection.batch(options) do
    yield
  end
end

#column(name, type, as: nil) ⇒ Object

Define a column name and type from the table. Columns must be defined in order to be used. This method will handle defining the getter and setter methods as well.

The type specified must be a valid CQL data type.

Because Cassandra stores column names with each row it is beneficial to use very short column names. You can specify the :as option to define a more human readable version. This will add the appropriate getter and setter methods as well as allow you to use the alias name in the methods that take an attributes hash.

Defining a column will also define getter and setter methods for both the column name and the alias name (if specified). So ‘column :i, :int, as: :id` will define the methods `i`, `i=`, `id`, and `id=`.

If you define a counter column then it will define methods for ‘increment_i!` and `decrement_i!` which take an optional amount argument. Note that if you have a counter column you cannot have any other non-primary key columns and you cannot call create, update, or save and must use the increment and decrement commands.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/cassie/model.rb', line 100

def column(name, type, as: nil)
  name = name.to_sym
  type_class = nil
  type_name = type.to_s.downcase.classify
  # Backward compatibility with older driver versions.
  type_name = "Text" if type_name == "Varchar"
  begin
    type_class = "Cassandra::Types::#{type_name}".constantize
  rescue NameError
    raise ArgumentError.new("#{type.inspect} is not an allowed Cassandra type")
  end
  
  self._columns = _columns.merge(name => type_class)
  self._column_aliases = self._column_aliases.merge(name => name)

  aliased = (as && as.to_s != name.to_s)
  if aliased
    self._column_aliases = self._column_aliases.merge(as => name)
  end

  if type.to_s == "counter".freeze
    self._counter_table = true
    
    define_method(name){ instance_variable_get(:"@#{name}") || 0 }
    define_method("#{name}="){ |value| instance_variable_set(:"@#{name}", value.to_i) }
    
    define_method("increment_#{name}!"){ |amount=1, ttl: nil| send(:adjust_counter!, name, amount, ttl: ttl) }
    define_method("decrement_#{name}!"){ |amount=1, ttl: nil| send(:adjust_counter!, name, -amount, ttl: ttl) }
    if aliased
      define_method(as){ send(name) }
      define_method("increment_#{as}!"){ |amount=1, ttl: nil| send("increment_#{name}!", amount, ttl: ttl) }
      define_method("decrement_#{as}!"){ |amount=1, ttl: nil| send("increment_#{name}!", amount, ttl: ttl) }
    end
  else
    attr_reader name
    define_method("#{name}="){ |value| instance_variable_set(:"@#{name}", self.class.send(:coerce, value, type_class)) }
    attr_reader name
    if aliased
      define_method(as){ send(name) }
      define_method("#{as}="){|value| send("#{name}=", value) }
    end
  end
end

#column_name(name_or_alias) ⇒ Object

Returns the internal column name after resolving any aliases.



150
151
152
# File 'lib/cassie/model.rb', line 150

def column_name(name_or_alias)
  name = _column_aliases[name_or_alias] || name_or_alias
end

#column_namesObject

Returns an array of the defined column names as symbols.



145
146
147
# File 'lib/cassie/model.rb', line 145

def column_names
  _columns.keys
end

#connectionObject

Returns the Cassie instance used to communicate with Cassandra.



343
344
345
# File 'lib/cassie/model.rb', line 343

def connection
  Cassie.instance
end

#count(where = nil) ⇒ Object

Return the count of rows in the table. If the where argument is specified then it will be added as the WHERE clause.



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/cassie/model.rb', line 285

def count(where = nil)
  options = nil
  if where.is_a?(Hash) && where.include?(:options)
    where = where.dup
    options = where.delete(:options)
  end
  
  cql = "SELECT COUNT(*) FROM #{self.full_table_name}"
  values = nil

  if where
    where_clause, values = cql_where_clause(where)
    cql << " WHERE #{where_clause}"
  else
    where = connection.prepare(cql)
  end
  
  results = connection.find(cql, values, consistency_options(read_consistency, options))
  results.rows.first["count"]
end

#create(attributes) ⇒ Object

Returns a newly created record. If the record is not valid then it won’t be persisted.



308
309
310
311
312
# File 'lib/cassie/model.rb', line 308

def create(attributes)
  record = new(attributes)
  record.save
  record
end

#create!(attributes) ⇒ Object

Returns a newly created record or raises an ActiveRecord::RecordInvalid error if the record is not valid.



316
317
318
319
320
# File 'lib/cassie/model.rb', line 316

def create!(attributes)
  record = new(attributes)
  record.save!
  record
end

#delete_all(key_hash) ⇒ Object

Delete all rows from the table that match the key hash. This method bypasses any destroy callbacks defined on the model.



324
325
326
327
328
329
330
# File 'lib/cassie/model.rb', line 324

def delete_all(key_hash)
  cleanup_up_hash = {}
  key_hash.each do |name, value|
    cleanup_up_hash[column_name(name)] = value
  end
  connection.delete(full_table_name, cleanup_up_hash, :consistency => write_consistency)
end

#find(where) ⇒ Object

Find a single record that matches the where argument.



266
267
268
269
270
271
272
273
# File 'lib/cassie/model.rb', line 266

def find(where)
  options = nil
  if where.is_a?(Hash) && where.include?(:options)
    where = where.dup
    options = where.delete(:options)
  end
  find_all(where: where, limit: 1, options: options).first
end

#find!(where) ⇒ Object

Find a single record that matches the where argument or raise an ActiveRecord::RecordNotFound error if none is found.



277
278
279
280
281
# File 'lib/cassie/model.rb', line 277

def find!(where)
  record = find(where)
  raise Cassie::RecordNotFound unless record
  record
end

#find_all(where:, select: nil, order: nil, limit: nil, options: nil) ⇒ Object

Find all records.

The where argument can be a Hash, Array, or String WHERE clause to filter the rows returned. It is required so that you don’t accidentally release code that returns all rows. If you really want to select all rows from a table you can specify the value :all.

The select argument can be used to limit which columns are returned and should be passed as an array of column names which can include aliases.

The order argument is a CQL fragment indicating the order. Note that Cassandra will only allow ordering by rows in the primary key.

The limit argument specifies how many rows to return.

You can provide a block to this method in which case it will yield each record as it is foundto the block instead of returning them.

Raises:

  • (ArgumentError)


216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/cassie/model.rb', line 216

def find_all(where:, select: nil, order: nil, limit: nil, options: nil)
  start_time = Time.now
  columns = (select ? Array(select).collect{|c| column_name(c)} : column_names)
  cql = "SELECT #{columns.join(', ')} FROM #{full_table_name}"
  values = nil

  raise ArgumentError.new("Where clause cannot be blank. Pass :all to find all records.") if where.blank?
  if where && where != :all
    where_clause, values = cql_where_clause(where)
  else
    values = []
  end
  cql << " WHERE #{where_clause}" if where_clause

  if order
    cql << " ORDER BY #{order}"
  end

  if limit
    cql << " LIMIT ?"
    values << Integer(limit)
  end

  results = connection.find(cql, values, consistency_options(read_consistency, options))
  records = [] unless block_given?
  row_count = 0
  loop do
    row_count += results.size
    results.each do |row|
      record = new(row)
      record.instance_variable_set(:@persisted, true)
      if block_given?
        yield record
      else
        records << record
      end
    end
    break if results.last_page?
    results = results.next_page
  end
  
  if find_subscribers && !find_subscribers.empty?
    payload = FindMessage.new(cql, values, options, Time.now - start_time, row_count)
    find_subscribers.each{|subscriber| subscriber.call(payload)}
  end
  
  records
end

#full_table_nameObject

Return the full table name including the keyspace.



191
192
193
194
195
196
197
# File 'lib/cassie/model.rb', line 191

def full_table_name
  if _keyspace
    "#{keyspace}.#{table_name}"
  else
    table_name
  end
end

#keyspaceObject

Return the keyspace name where the table is located.



186
187
188
# File 'lib/cassie/model.rb', line 186

def keyspace
  connection.config.keyspace(_keyspace)
end

#keyspace=(name) ⇒ Object

Set the keyspace for the table. The name should be an abstract keyspace name that is mapped to an actual keyspace name in the configuration. If the name provided is not mapped in the configuration, then the raw value will be used.



181
182
183
# File 'lib/cassie/model.rb', line 181

def keyspace=(name)
  self._keyspace = name.to_s
end

#offset_to_id(key, offset, order: nil, batch_size: 1000, min: nil, max: nil) ⇒ Object

Since Cassandra doesn’t support offset we need to find the order key of record at the specified the offset.

The key is a Hash describing the primary keys to search minus the last column defined for the primary key. This column is assumed to be an ordering key. If it isn’t, this method will fail.

The order argument can be used to specify an order for the ordering key (:asc or :desc). It will default to the natural order of the last ordering key as defined by the ordering_key method.

The min and max can be used to limit the offset calculation to a range of values (exclusive).



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/cassie/model.rb', line 358

def offset_to_id(key, offset, order: nil, batch_size: 1000, min: nil, max: nil)
  ordering_key = primary_key.last
  cluster_order = _ordering_keys[ordering_key] || :asc
  order ||= cluster_order
  order_cql = "#{ordering_key} #{order}" unless order == cluster_order
  
  from = (order == :desc ? max : min)
  to = (order == :desc ? min : max)
  loop do
    limit = (offset > batch_size ? batch_size : offset + 1)
    conditions_cql = []
    conditions = []
    if from
      conditions_cql << "#{ordering_key} #{order == :desc ? '<' : '>'} ?"
      conditions << from
    end
    if to
      conditions_cql << "#{ordering_key} #{order == :desc ? '>' : '<'} ?"
      conditions << to
    end
    key.each do |name, value|
      conditions_cql << "#{column_name(name)} = ?"
      conditions << value
    end
    conditions.unshift(conditions_cql.join(" AND "))

    results = find_all(:select => [ordering_key], :where => conditions, :limit => limit, :order => order_cql)
    last_row = results.last if results.size == limit
    last_id = last_row.send(ordering_key) if last_row
  
    if last_id.nil?
      return nil
    elsif limit >= offset
      return last_id
    else
      offset -= results.size
      from = last_id
    end
  end
end

#ordering_key(name, order) ⇒ Object

Define and ordering key for the table. The order attribute should be either :asc or :desc

Raises:

  • (ArgumentError)


172
173
174
175
176
# File 'lib/cassie/model.rb', line 172

def ordering_key(name, order)
  order = order.to_sym
  raise ArgumentError.new("order must be either :asc or :desc") unless order == :asc || order == :desc
  _ordering_keys[name.to_sym] = order
end

#primary_keyObject

Return an array of column names for the table primary key.



167
168
169
# File 'lib/cassie/model.rb', line 167

def primary_key
  _primary_key
end

#primary_key=(value) ⇒ Object

Set the primary key for the table. The value should be set as an array with the clustering key first.



156
157
158
159
160
161
162
163
164
# File 'lib/cassie/model.rb', line 156

def primary_key=(value)
  self._primary_key = Array(value).map { |column|
    if column.is_a?(Array)
      column.map(&:to_sym)
    else
      column.to_sym
    end
  }.flatten
end