Class: DatastaxRails::Base
- Extended by:
- ActiveSupport::DescendantsTracker
- Includes:
- ActiveModel::Model, Associations, AttributeAssignment, AttributeMethods, Batches, Callbacks, Connection, FinderMethods, Inheritance, Persistence, Reflection, Scoping, Serialization, SolrRepair, Timestamps, Validations
- Defined in:
- lib/datastax_rails/base.rb
Overview
DatastaxRails
DatastaxRails-based objects differ from Active Record objects in that they specify their attributes directly on the model. This is necessary because of the fact that Cassandra column families do not have a set list of columns but rather can have different columns per row. (This is not strictly true any more, but it’s still not as nailed down as SQL.) By specifying the attributes on the model, getters and setters are automatically created, and the attribute is automatically indexed into SOLR.
Primary Keys
Several types of primary keys are supported in DSR. The most common type used is UUID. In general, incrementing numbers are not used as there is no way to guarantee a consistent one-up number across nodes. The following will cause a unique UUID to be generated for each model. This works best if you are using the RandomPartitioner in your Datastax cluster.
class Person < DatastaxRails::Base
uuid :id
end
You don’t have to use a uuid. You can use a different column as your primary key.
class Person < DatastaxRails::Base
self.primary_key = 'userid'
string :userid
end
Attributes
Attributes are specified near the top of the model. The following attribute types are supported:
-
binary - a large object that will not be indexed into SOLR (e.g., BLOB)
-
boolean - true/false values
-
date - a date without a time component
-
float - a number in floating point notation
-
integer - a whole, round number of any size
-
list - an ordered list of values of a single type
-
map - a collection of key/value pairs of a single type (keys are always strings)
-
set - an un-ordered set of unique values of a single type
-
string - a generic string type that is not tokenized by default
-
text - like strings but will be tokenized for full-text searching by default
-
time - a datetime object
-
timestamps - a special type that instructs DSR to include created_at and updated_at
-
uuid - a UUID in standard UUID format
The following options may be specified on the various types to control how they are indexed into SOLR:
-
solr_index - If the attribute should the attribute be indexed into SOLR. Defaults to true for everything but binary.
-
solr_store - If the attribute should the attribute be stored in SOLR. Defaults to true for everything but binary. (see note)
-
sortable - If the attribute should be sortable by SOLR. Defaults to true for everything but binary and text. (see note)
-
tokenized - If the attribute should be tokenized for full-text searching within the field. Defaults to true for text.
-
fulltext - If the attribute should be included in the default field for full-text searches. Defaults to true for text and string.
-
multi_valued - If the field will contain multiple values in Solr. Defaults to true for list and set. This should never need to be set manually.
NOTES:
-
No fields are actually stored in SOLR. When a field is requested from SOLR, the field is retrieved from Cassandra behind the scenes and returned as if it were stored. The stored parameter actually controls whether SOLR will return the field at all. If a field is not stored then asking SOLR for it will return a nil value. It will also not be included in the field list when all (*) fields are requested.
-
If you want a field both sortable and searchable (e.g., a subject) then declare it a text field with
:sortable => true
. This will create two copies of the field in SOLR, one that gets tokenized and one that is a single token for sorting. As this inflates the size of the index, you don’t want to do this for large fields (which probably don’t make sense to sort on anyways).
EXAMPLE:
class Person < DatastaxRails::Base
uuid :id
string :first_name
string :user_name
text :bio
date :birthdate
boolean :active
end
Schemas
DSR will automatically manage both the Cassandra and Solr schemas for you based on the attributes that you specify on the model. You can override the Solr schema if you want to have something custom. There is a rake task that manages all of the schema information. It will create column families and columns as needed and upload the Solr schema when necessary. If there are changes, it will automatically kick off a reindex in the background.
As of Cassandra 1.2, there is no way to remove a column. Cassandra 2.0 supports it, but it hasn’t been implemented in DSR yet.
TODO: Need a way to remove ununsed column families.
Creation
DatastaxRails objects accept constructor parameters either in a hash or as a block. The hash method is especially useful when you’re receiving the data from somewhere else, like an HTTP request. It works like this:
user = User.new(name: "David", occupation: "Code Artist")
user.name # => "David"
You can also use block initialization:
user = User.new do |u|
u.name = "David"
u.occupation = "Code Artist"
end
And of course you can just create a bare object and specify the attributes after the fact:
user = User.new
user.name = "David"
user.occupation = "Code Artist"
Consistency
Cassandra has a concept of consistency levels when it comes to saving records. For a detailed discussion on Cassandra data consistency, see: www.datastax.com/documentation/cassandra/1.2/cassandra/dml/dml_config_consistency_c.html
DatastaxRails allows you to specify the consistency when you save and retrieve objects.
user = User.new(name: 'David')
user.save(consistency: 'ALL')
User.create(params[:user], {consistency: :local_quorum})
User.consistency(:local_quorum).where(name: 'David')
The default consistency level in DatastaxRails is QUORUM for writes and for retrieval by ID. SOLR only supports a consistency level of ONE. See the documentation for SearchMethods#consistency for a more detailed explanation.
The overall default consistency for a given model can be overridden by setting the default_consistency
property.
class Model < DatastaxRails::Base
self.default_consistency = :local_quorum
end
The default consistency for all models can be selected by setting the property on DatastaxRails::Base.
DatastaxRails::Base.default_consistency = :one
Conditions
Conditions are specified as a hash representing key/value pairs that will eventually be passed to SOLR or as a chained call for greater_than and less_than conditions. In addition, fulltext queries may be specified as a string that will eventually be parsed by SOLR as a standard SOLR query.
A simple hash without a statement will generate conditions based on equality using boolean AND logic. For instance:
Student.where(first_name: "Harvey", status: 1)
Student.where(params[:student])
A range may be used in the hash to use a SOLR range query:
Student.where(grade: 9..12)
An array may be used in the hash to construct a SOLR OR query:
Student.where(grade: [9,11,12])
Inequality can be tested for like so:
Student.where_not(grade: 9)
Student.where(:grade).greater_than(9)
Student.where(:grade).less_than(10)
NOTE that Solr inequalities are inclusive so really, the second example above is retrieving records where grace is greater than or equal to 9. Be sure to keep this in mind when you do inequality queries.
Fulltext searching is natively supported. All string and text fields are automatically indexed for fulltext searching.
Post.fulltext('Apple AND "iPhone 4s"')
See the documentation on SearchMethods for more information and examples.
Overwriting default accessors
All column values are automatically available through basic accessors on the object, but sometimes you want to specialize this behavior. This can be done by overwriting the default accessors (using the same name as the attribute) and calling read_attribute(attr_name)
and write_attribute(attr_name, value)
to actually change things.
class Song < DatastaxRails::Base
# Uses an integer of seconds to hold the length of the song
def length=(minutes)
write_attribute(:length, minutes.to_i * 60)
end
def length
read_attribute(:length) / 60
end
end
You can alternatively use self[:attribute]=(value)
and self[:attribute]
instead of write_attribute(:attribute, value)
and read_attribute(:attribute)
.
Dynamic attribute-based finders
Dynamic finders have been removed from Rails. As a result, they have also been removed from DSR. In its place, the find_by
method can be used:
Student.find_by(name: 'Jason')
NOTE: there is a subtle difference between the following that does not exist in ActiveRecord:
Student.find_by(name: 'Jason')
Student.where(name: 'Jason').first
The difference is that the first is escaped so that special characters can be used. The second method requires you to do the escaping yourself if you need it done. As an example,
Company.find_by(name: 'All*') #=> finds only the company with the literal name 'All*'
Company.where(name: 'All*').first #=> finds the first company whose name begins with All
See DatastaxRails::FinderMethods for more information
Facets
DSR support both field and range facets. For additional detail on facets, see the documentation available under the FacetMethods module. The result is available through the facets accessor.
results = Article.field_facet(:author)
results.facets #=> {"author"=>["vonnegut", 2. "asimov", 3]}
Model.field_facet(:author)
Model.field_facet(:author, sort: 'count', limit: 10, mincount: 1)
Model.range_facet(:price, 500, 1000, 10)
Model.range_facet(:price, 500, 1000, 10, include: 'all')
Model.range_facet(:publication_date, "1968-01-01T00:00:00Z", "2000-01-01T00:00:00Z", "+1YEAR")
Range Gap syntax for dates: 1YEAR, 5YEAR, 5YEARS, 1MONTH, +1DAY
Useful constants:
DatastaxRails::FacetMethods::BY_YEAR (+1YEAR) DatastaxRails::FacetMethods::BY_MONTH (+1MONTH) DatastaxRails::FacetMethods::BY_DAY (+1DAY)
Model.range_facet(:publication_date, “1968-01-01T00:00:00Z”, “2000-01-01T00:00:00Z”, DatastaxRails::FacetMethods::BY_YEAR)
Collections
Cassandra supports the notion of collections on a row. The three types of supported collections are set
, list
, and map
.
By default collections hold strings. You can override this by passing a :holds option in the attribute definition. Sets can hold anything other than other collections, however, a given collection can only hold a single type of values.
NOTE: There is a limitation in Cassandra where only the first 64k entries of a collection are ever returned with a query. Therefore, if you put more than 64k entries in a collection you will lose data.
Set
A set is an un-ordered collection of unique values. This collection is fully searchable in Solr.
class User < DatastaxRails::Base
uuid :id
string :username
set :emails
end
The default set will hold strings. You can modify this behavior like so:
class Student < DatastaxRails::Base
uuid :id
string :name
set :grades, holds: :integers
end
User.where(emails: '[email protected]') #=> Returns all users where [email protected] is in the set
user = User.new(name: 'Jim', emails: ['[email protected]'])
user.emails << '[email protected]'
user.emails #=> ['[email protected]']
List
An ordered collection of values. They do not necessarily have to be unique. The collection will be fully searchable in Solr.
class Student < DatastaxRails::Base
uuid :id
string :name
list :classrooms, holds: integers
end
Student.where(classrooms: 307) #=> Returns all students that have a class in room 307.
student = Student.new(name: 'Sally', classrooms: [307, 305, 301, 307])
student.classrooms << 304
student.classrooms #=> [307, 305, 301, 307, 304]
Map
A collection of key/value pairs where the key is a string and the value is the specified type. The collection becomes available in Solr as dynamic fields.
class Student < DatastaxRails::Base
uuid :id
string :name
map :scores_, holds: :integers
end
student = Student.new(:name 'Sally')
student.scores['midterm'] = 98
student.scores['final'] = 97
student.scores #=> {'scores_midterm' => 98, 'scores_final' => 97}
Student.where(scores_final: 97) #=> Returns all students that scored 97 on their final
Note that the map name gets prepended to the key. This is how Solr maps it’s dynamic fields into the cassandra map. For this reason, it’s usually a good idea to put an underscore (_) at the end of the map name to prevent collisions.
Exceptions
-
DatastaxRailsError - Generic error class and superclass of all other errors raised by DatastaxRails.
-
AssociationTypeMismatch - The object assigned to the association wasn’t of the type specified in the association definition.
-
ConnectionNotEstablished+ - No connection has been established. Use
establish_connection
before querying. -
RecordNotFound - No record responded to the
find
method. Either the row with the given ID doesn’t exist or the row didn’t meet the additional restrictions. Somefind
calls do not raise this exception to signal nothing was found, please check its documentation for further details. -
UnknownAttributeError - The specified attribute isn’t defined on your model.
See the documentation for SearchMethods for more examples of using the search API.
Direct Known Subclasses
Constant Summary
Constants included from Callbacks
Class Attribute Summary collapse
-
.column_family ⇒ Object
Returns the column family name.
Instance Attribute Summary collapse
-
#attributes ⇒ Object
readonly
Returns the value of attribute attributes.
-
#key ⇒ Object
Returns the value of attribute key.
-
#loaded_attributes ⇒ Object
readonly
Returns the value of attribute loaded_attributes.
Attributes included from Associations
Class Method Summary collapse
-
.attribute_names ⇒ Object
(also: column_names)
Returns an array of attribute names as strings.
- .base_class ⇒ Object
- .columns ⇒ Object
-
.default_page_size ⇒ Object
SOLR always paginates all requests.
- .find_by_id(id) ⇒ Object
-
.inspect ⇒ Object
Returns a string like ‘Post(id:integer, title:string, body:text)’.
- .legacy_mapping? ⇒ Boolean
- .logger ⇒ Object
- .models ⇒ Object
- .payload_model? ⇒ Boolean
- .search_ids(&block) ⇒ Object
-
.valid_consistency?(level) ⇒ Boolean
:nodoc:.
- .wide_storage_model? ⇒ Boolean
Instance Method Summary collapse
- #==(other) ⇒ Object
- #attribute_names ⇒ Object
- #column_names ⇒ Object
- #eql?(other) ⇒ Boolean
-
#freeze ⇒ Object
Freeze the attributes hash such that associations are still accessible, even on destroyed records.
-
#frozen? ⇒ Boolean
Returns
true
if the attributes hash has been frozen. - #hash ⇒ Object
- #init_changed_attributes ⇒ Object
- #init_internals ⇒ Object
-
#init_with(coder) ⇒ Object
Initialize an empty model object from
coder
. -
#initialize(attributes = {}, _options = {}) {|_self| ... } ⇒ Base
constructor
A new instance of Base.
- #to_param ⇒ Object
-
#valid_consistency?(level) ⇒ Boolean
:nodoc:.
Methods included from SolrRepair
Methods included from Serialization
#as_json, #serializable_hash, #to_xml
Methods included from Timestamps
Methods included from Associations
#association, #clear_association_cache
Methods included from Callbacks
Methods included from Validations
Methods included from AttributeMethods
#attribute_exists?, #column_for_attribute, #method_missing, #respond_to?
Methods included from AttributeAssignment
Methods included from Batches
#find_each, #find_each_with_index, #find_in_batches
Methods included from FinderMethods
#find, #find_by, #find_by!, #first, #first!, #last, #last!
Methods included from Persistence
#destroy, #persisted?, #reload, #save, #save!, #toggle, #toggle!, #update_attribute, #update_attributes, #update_attributes!
Constructor Details
#initialize(attributes = {}, _options = {}) {|_self| ... } ⇒ Base
Returns a new instance of Base.
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/datastax_rails/base.rb', line 407 def initialize(attributes = {}, = {}) defaults = self.class.column_defaults.dup defaults.each { |_k, v| v.duplicable? ? v.dup : v } @attributes = initialize_attributes(defaults) @column_types = self.class.columns_hash init_internals init_changed_attributes populate_with_current_scope_attributes assign_attributes(attributes) if attributes yield self if block_given? run_callbacks :initialize unless _initialize_callbacks.empty? end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class DatastaxRails::AttributeMethods
Class Attribute Details
.column_family ⇒ Object
Returns the column family name. If it has been set manually, the set name is returned. Otherwise returns the pluralized version of the class name.
Returns [String] the name of the column family
543 544 545 |
# File 'lib/datastax_rails/base.rb', line 543 def column_family @column_family || name.underscore.pluralize end |
Instance Attribute Details
#attributes ⇒ Object (readonly)
Returns the value of attribute attributes.
395 396 397 |
# File 'lib/datastax_rails/base.rb', line 395 def attributes @attributes end |
#key ⇒ Object
Returns the value of attribute key.
397 398 399 |
# File 'lib/datastax_rails/base.rb', line 397 def key @key end |
#loaded_attributes ⇒ Object (readonly)
Returns the value of attribute loaded_attributes.
396 397 398 |
# File 'lib/datastax_rails/base.rb', line 396 def loaded_attributes @loaded_attributes end |
Class Method Details
.attribute_names ⇒ Object Also known as: column_names
Returns an array of attribute names as strings
580 581 582 |
# File 'lib/datastax_rails/base.rb', line 580 def attribute_names @attribute_names ||= attribute_definitions.keys.map { |a|a.to_s } end |
.base_class ⇒ Object
563 564 565 566 567 |
# File 'lib/datastax_rails/base.rb', line 563 def base_class klass = self klass = klass.superclass while klass.superclass != Base klass end |
.columns ⇒ Object
585 586 587 |
# File 'lib/datastax_rails/base.rb', line 585 def columns @columns ||= attribute_definitions.values end |
.default_page_size ⇒ Object
SOLR always paginates all requests. There is no way to disable it, so we are setting the default page size to an arbitrarily high number so that we effectively remove pagination. If you instead want a model set to something more sane, then override this method in your model and set it. Of course, the page size can always be raised or lowered for an individual request.
class Model < DatastaxRails::Base
def self.default_page_size
30
end
end
600 601 602 |
# File 'lib/datastax_rails/base.rb', line 600 def default_page_size 100_000 end |
.find_by_id(id) ⇒ Object
569 570 571 572 573 |
# File 'lib/datastax_rails/base.rb', line 569 def find_by_id(id) scoped.with_cassandra.find(id) rescue RecordNotFound nil end |
.inspect ⇒ Object
Returns a string like ‘Post(id:integer, title:string, body:text)’
614 615 616 617 618 619 620 621 |
# File 'lib/datastax_rails/base.rb', line 614 def inspect if self == Base super else attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' "#{super}(#{attr_list})" end end |
.legacy_mapping? ⇒ Boolean
559 560 561 |
# File 'lib/datastax_rails/base.rb', line 559 def legacy_mapping? legacy_mapping end |
.logger ⇒ Object
575 576 577 |
# File 'lib/datastax_rails/base.rb', line 575 def logger Rails.logger end |
.models ⇒ Object
547 548 549 |
# File 'lib/datastax_rails/base.rb', line 547 def models descendants.reject { |m|m.abstract_class? } end |
.payload_model? ⇒ Boolean
551 552 553 |
# File 'lib/datastax_rails/base.rb', line 551 def payload_model? ancestors.include?(DatastaxRails::PayloadModel) end |
.search_ids(&block) ⇒ Object
604 605 606 607 |
# File 'lib/datastax_rails/base.rb', line 604 def search_ids(&block) search = solr_search(&block) search.raw_results.map { |result| result.primary_key } end |
.valid_consistency?(level) ⇒ Boolean
:nodoc:
609 610 611 |
# File 'lib/datastax_rails/base.rb', line 609 def valid_consistency?(level) #:nodoc: DatastaxRails::Cql::Consistency::VALID_CONSISTENCY_LEVELS.include?(level) end |
.wide_storage_model? ⇒ Boolean
555 556 557 |
# File 'lib/datastax_rails/base.rb', line 555 def wide_storage_model? ancestors.include?(DatastaxRails::WideStorageModel) end |
Instance Method Details
#==(other) ⇒ Object
493 494 495 496 497 498 |
# File 'lib/datastax_rails/base.rb', line 493 def ==(other) super || other.instance_of?(self.class) && id.present? && other.id.eql?(id) end |
#attribute_names ⇒ Object
504 505 506 |
# File 'lib/datastax_rails/base.rb', line 504 def attribute_names self.class.attribute_names end |
#column_names ⇒ Object
507 508 509 |
# File 'lib/datastax_rails/base.rb', line 507 def attribute_names self.class.attribute_names end |
#eql?(other) ⇒ Boolean
500 501 502 |
# File 'lib/datastax_rails/base.rb', line 500 def eql?(other) self == (other) end |
#freeze ⇒ Object
Freeze the attributes hash such that associations are still accessible, even on destroyed records.
475 476 477 478 |
# File 'lib/datastax_rails/base.rb', line 475 def freeze @attributes.freeze self end |
#frozen? ⇒ Boolean
Returns true
if the attributes hash has been frozen.
481 482 483 |
# File 'lib/datastax_rails/base.rb', line 481 def frozen? @attributes.frozen? end |
#hash ⇒ Object
489 490 491 |
# File 'lib/datastax_rails/base.rb', line 489 def hash id.hash end |
#init_changed_attributes ⇒ Object
465 466 467 468 469 470 471 472 |
# File 'lib/datastax_rails/base.rb', line 465 def init_changed_attributes # Intentionally avoid using #column_defaults since overridden defaults # won't get written unless they get marked as changed self.class.columns.each do |c| attr, orig_value = c.name, c.default @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr]) end end |
#init_internals ⇒ Object
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
# File 'lib/datastax_rails/base.rb', line 449 def init_internals pk = self.class.primary_key @attributes[pk] = nil unless @attributes.key?(pk) @association_cache = {} @attributes_cache = {} @previously_changed = {}.with_indifferent_access @changed_attributes = {}.with_indifferent_access @loaded_attributes = Hash[@attributes.map { |k, _v| [k, true] }].with_indifferent_access @readonly = false @destroyed = false @marked_for_destruction = false @destroyed_by_association = nil @new_record = true end |
#init_with(coder) ⇒ Object
Initialize an empty model object from coder
. coder
must contain the attributes necessary for initializing an empty model object. For example:
class Post < DatastaxRails::Base
end
post = Post.allocate
post.init_with('attributes' => { 'title' => 'hello world' })
post.title # => 'hello world'
434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/datastax_rails/base.rb', line 434 def init_with(coder) Types::DirtyCollection.ignore_modifications do @attributes = initialize_attributes(coder['attributes']) @column_types_override = coder['column_types'] @column_types = self.class.columns_hash init_internals @new_record = false run_callbacks :find run_callbacks :initialize end self end |
#to_param ⇒ Object
485 486 487 |
# File 'lib/datastax_rails/base.rb', line 485 def to_param id.to_s if persisted? end |
#valid_consistency?(level) ⇒ Boolean
:nodoc:
509 510 511 |
# File 'lib/datastax_rails/base.rb', line 509 def valid_consistency?(level) #:nodoc: self.class.validate_consistency(level.to_s.upcase) end |