Module: Cassie::Model::ClassMethods

Defined in:
lib/cassie/model.rb

Instance Method Summary collapse

Instance Method Details

#batchObject

All insert, update, and delete calls within the block will be sent as a single batch to Cassandra.



271
272
273
274
275
# File 'lib/cassie/model.rb', line 271

def batch
  connection.batch 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=`.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/cassie/model.rb', line 69

def column(name, type, as: nil)
  name = name.to_sym
  type_class = nil
  begin
    type_class = "Cassandra::Types::#{type.to_s.downcase.classify}".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)
  
  define_method("#{name}="){ |value| instance_variable_set(:"@#{name}", self.class.send(:coerce, value, type_class)) }
  attr_reader name
  if as && as.to_s != name.to_s
    self._column_aliases = self._column_aliases.merge(as => name)
    define_method(as){ send(name) }
    define_method("#{as}="){|value| send("#{name}=", value) }
  end
end

#column_name(name_or_alias) ⇒ Object

Returns the internal column name after resolving any aliases.



96
97
98
# File 'lib/cassie/model.rb', line 96

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.



91
92
93
# File 'lib/cassie/model.rb', line 91

def column_names
  _columns.keys
end

#connectionObject

Returns the Cassie instance used to communicate with Cassandra.



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

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.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/cassie/model.rb', line 222

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, 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.



245
246
247
248
249
# File 'lib/cassie/model.rb', line 245

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.



253
254
255
256
257
# File 'lib/cassie/model.rb', line 253

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.



261
262
263
264
265
266
267
# File 'lib/cassie/model.rb', line 261

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)
end

#find(where) ⇒ Object

Find a single record that matches the where argument.



203
204
205
206
207
208
209
210
# File 'lib/cassie/model.rb', line 203

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.



214
215
216
217
218
# File 'lib/cassie/model.rb', line 214

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)


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/cassie/model.rb', line 162

def find_all(where:, select: nil, order: nil, limit: nil, options: nil)
  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, options)
  records = [] unless block_given?
  loop do
    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
  records
end

#full_table_nameObject

Return the full table name including the keyspace.



137
138
139
140
141
142
143
# File 'lib/cassie/model.rb', line 137

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

#keyspaceObject

Return the keyspace name where the table is located.



132
133
134
# File 'lib/cassie/model.rb', line 132

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.



127
128
129
# File 'lib/cassie/model.rb', line 127

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).



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/cassie/model.rb', line 293

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)


118
119
120
121
122
# File 'lib/cassie/model.rb', line 118

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.



113
114
115
# File 'lib/cassie/model.rb', line 113

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.



102
103
104
105
106
107
108
109
110
# File 'lib/cassie/model.rb', line 102

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