# File lib/cassie/model.rb, line 169 def primary_key _primary_key end
module Cassie::Model::ClassMethods
Public Instance Methods
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.
# File lib/cassie/model.rb, line 337 def batch(options = nil) options = consistency_options(write_consistency, options) connection.batch(options) do yield end end
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.
# File lib/cassie/model.rb, line 102 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 = _column_aliases.merge(name => name) aliased = (as && as.to_s != name.to_s) if aliased self._column_aliases = _column_aliases.merge(as => name) end if type.to_s == "counter" 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
Returns the internal column name after resolving any aliases.
# File lib/cassie/model.rb, line 152 def column_name(name_or_alias) _column_aliases[name_or_alias] || name_or_alias end
Returns an array of the defined column names as symbols.
# File lib/cassie/model.rb, line 147 def column_names _columns.keys end
Returns the Cassie
instance used to communicate with Cassandra.
# File lib/cassie/model.rb, line 345 def connection Cassie.instance end
Return the count of rows in the table. If the where
argument is specified then it will be added as the WHERE clause.
# File lib/cassie/model.rb, line 287 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 #{full_table_name}" values = nil if where where_clause, values = cql_where_clause(where) cql += " WHERE #{where_clause}" else connection.prepare(cql) end results = connection.find(cql, values, consistency_options(read_consistency, options)) results.rows.first["count"] end
Returns a newly created record. If the record is not valid then it won't be persisted.
# File lib/cassie/model.rb, line 310 def create(attributes) record = new(attributes) record.save record end
Returns a newly created record or raises an ActiveRecord::RecordInvalid error if the record is not valid.
# File lib/cassie/model.rb, line 318 def create!(attributes) record = new(attributes) record.save! record end
Delete all rows from the table that match the key hash. This method bypasses any destroy callbacks defined on the model.
# File lib/cassie/model.rb, line 326 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 a single record that matches the where
argument.
# File lib/cassie/model.rb, line 268 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 a single record that matches the where
argument or raise an ActiveRecord::RecordNotFound error if none is found.
# File lib/cassie/model.rb, line 279 def find!(where) record = find(where) raise Cassie::RecordNotFound unless record record end
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.
# File lib/cassie/model.rb, line 218 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
Return the full table name including the keyspace.
# File lib/cassie/model.rb, line 193 def full_table_name if _keyspace "#{keyspace}.#{table_name}" else table_name end end
Return the keyspace name where the table is located.
# File lib/cassie/model.rb, line 188 def keyspace connection.config.keyspace(_keyspace) end
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.
# File lib/cassie/model.rb, line 183 def keyspace=(name) self._keyspace = name.to_s end
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).
# File lib/cassie/model.rb, line 360 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
Define and ordering key for the table. The order attribute should be either :asc or :desc
# File lib/cassie/model.rb, line 174 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
Return an array of column names for the table primary key.
Set the primary key for the table. The value should be set as an array with the clustering key first.
# File lib/cassie/model.rb, line 158 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
Private Instance Methods
Force a value to be the correct Cassandra data type.
# File lib/cassie/model.rb, line 433 def coerce(value, type_class) if value.nil? nil elsif type_class == Cassandra::Types::Timeuuid && value.is_a?(Cassandra::TimeUuid) value elsif type_class == Cassandra::Types::Uuid # Work around for bug in cassandra-driver 2.1.3 if value.is_a?(Cassandra::Uuid) value else Cassandra::Uuid.new(value) end elsif type_class == Cassandra::Types::Timestamp && value.is_a?(String) Time.parse(value) elsif type_class == Cassandra::Types::Inet && value.is_a?(::IPAddr) value elsif type_class == Cassandra::Types::List Array.new(value) elsif type_class == Cassandra::Types::Set Set.new(value) elsif type_class == Cassandra::Types::Map Hash[value] else type_class.new(value) end end
# File lib/cassie/model.rb, line 460 def consistency_options(consistency, options) if consistency if options options = options.merge(consistency: consistency) if options[:consistency].nil? options else {consistency: consistency} end else options end end
Turn a hash of column value, array of [cql, value] or a CQL string into a CQL where clause. Returns the values pulled out in an array for making a prepared statement.
# File lib/cassie/model.rb, line 406 def cql_where_clause(where) case where when Hash cql = [] values = [] where.each do |column, value| col_name = column_name(column) if value.is_a?(Array) q = "?#{",?" * (value.size - 1)}" cql << "#{col_name} IN (#{q})" values.concat(value) else cql << "#{col_name} = ?" values << coerce(value, _columns[col_name]) end end [cql.join(" AND "), values] when Array [where.first, where[1, where.size]] when String [where, []] else raise ArgumentError.new("invalid CQL where clause #{where}") end end