datastax_rails 1.2.3 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -8
- data/config/schema.xml.erb +22 -19
- data/config/solrconfig.xml.erb +1 -1
- data/lib/cql-rb_extensions.rb +27 -0
- data/lib/datastax_rails.rb +13 -17
- data/lib/datastax_rails/associations/association.rb +1 -4
- data/lib/datastax_rails/associations/collection_proxy.rb +0 -13
- data/lib/datastax_rails/attribute_assignment.rb +28 -91
- data/lib/datastax_rails/attribute_methods.rb +109 -44
- data/lib/datastax_rails/attribute_methods/before_type_cast.rb +71 -0
- data/lib/datastax_rails/attribute_methods/dirty.rb +52 -11
- data/lib/datastax_rails/attribute_methods/primary_key.rb +87 -0
- data/lib/datastax_rails/attribute_methods/read.rb +120 -0
- data/lib/datastax_rails/attribute_methods/typecasting.rb +52 -21
- data/lib/datastax_rails/attribute_methods/write.rb +59 -0
- data/lib/datastax_rails/base.rb +227 -236
- data/lib/datastax_rails/cassandra_only_model.rb +25 -19
- data/lib/datastax_rails/column.rb +384 -0
- data/lib/datastax_rails/connection.rb +12 -13
- data/lib/datastax_rails/cql/alter_column_family.rb +0 -1
- data/lib/datastax_rails/cql/base.rb +15 -3
- data/lib/datastax_rails/cql/column_family.rb +2 -2
- data/lib/datastax_rails/cql/create_column_family.rb +7 -18
- data/lib/datastax_rails/cql/delete.rb +4 -9
- data/lib/datastax_rails/cql/insert.rb +2 -8
- data/lib/datastax_rails/cql/select.rb +4 -4
- data/lib/datastax_rails/cql/update.rb +8 -17
- data/lib/datastax_rails/dynamic_model.rb +98 -0
- data/lib/datastax_rails/payload_model.rb +19 -31
- data/lib/datastax_rails/persistence.rb +39 -54
- data/lib/datastax_rails/railtie.rb +1 -0
- data/lib/datastax_rails/reflection.rb +1 -1
- data/lib/datastax_rails/relation.rb +20 -20
- data/lib/datastax_rails/relation/batches.rb +18 -16
- data/lib/datastax_rails/relation/facet_methods.rb +1 -1
- data/lib/datastax_rails/relation/finder_methods.rb +6 -10
- data/lib/datastax_rails/relation/search_methods.rb +62 -48
- data/lib/datastax_rails/rsolr_client_wrapper.rb +1 -1
- data/lib/datastax_rails/schema/cassandra.rb +34 -62
- data/lib/datastax_rails/schema/migrator.rb +9 -24
- data/lib/datastax_rails/schema/solr.rb +13 -30
- data/lib/datastax_rails/schema_cache.rb +67 -0
- data/lib/datastax_rails/timestamps.rb +84 -11
- data/lib/datastax_rails/types/dirty_collection.rb +88 -0
- data/lib/datastax_rails/types/dynamic_list.rb +14 -0
- data/lib/datastax_rails/types/dynamic_map.rb +32 -0
- data/lib/datastax_rails/types/dynamic_set.rb +10 -0
- data/lib/datastax_rails/util/solr_repair.rb +4 -5
- data/lib/datastax_rails/validations.rb +6 -12
- data/lib/datastax_rails/validations/uniqueness.rb +0 -4
- data/lib/datastax_rails/version.rb +1 -1
- data/lib/datastax_rails/wide_storage_model.rb +13 -29
- data/lib/schema_migration.rb +4 -0
- data/spec/datastax_rails/associations_spec.rb +0 -1
- data/spec/datastax_rails/attribute_methods_spec.rb +9 -6
- data/spec/datastax_rails/base_spec.rb +26 -0
- data/spec/datastax_rails/column_spec.rb +238 -0
- data/spec/datastax_rails/cql/select_spec.rb +1 -1
- data/spec/datastax_rails/cql/update_spec.rb +2 -2
- data/spec/datastax_rails/persistence_spec.rb +29 -15
- data/spec/datastax_rails/relation/batches_spec.rb +5 -5
- data/spec/datastax_rails/relation/finder_methods_spec.rb +0 -20
- data/spec/datastax_rails/relation/search_methods_spec.rb +8 -0
- data/spec/datastax_rails/relation_spec.rb +7 -0
- data/spec/datastax_rails/schema/migrator_spec.rb +5 -10
- data/spec/datastax_rails/schema/solr_spec.rb +1 -1
- data/spec/datastax_rails/types/dynamic_list_spec.rb +20 -0
- data/spec/datastax_rails/types/dynamic_map_spec.rb +22 -0
- data/spec/datastax_rails/types/dynamic_set_spec.rb +16 -0
- data/spec/dummy/config/application.rb +2 -1
- data/spec/dummy/config/datastax.yml +6 -3
- data/spec/dummy/config/environments/development.rb +4 -5
- data/spec/dummy/config/environments/test.rb +0 -5
- data/spec/dummy/log/development.log +18 -0
- data/spec/dummy/log/test.log +36 -0
- data/spec/feature/dynamic_fields_spec.rb +9 -0
- data/spec/feature/overloaded_tables_spec.rb +24 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/default_consistency_shared_examples.rb +2 -2
- data/spec/support/models.rb +28 -14
- metadata +212 -188
- data/lib/datastax_rails/identity.rb +0 -64
- data/lib/datastax_rails/identity/abstract_key_factory.rb +0 -29
- data/lib/datastax_rails/identity/custom_key_factory.rb +0 -37
- data/lib/datastax_rails/identity/hashed_natural_key_factory.rb +0 -10
- data/lib/datastax_rails/identity/natural_key_factory.rb +0 -39
- data/lib/datastax_rails/identity/uuid_key_factory.rb +0 -27
- data/lib/datastax_rails/type.rb +0 -16
- data/lib/datastax_rails/types.rb +0 -9
- data/lib/datastax_rails/types/array_type.rb +0 -86
- data/lib/datastax_rails/types/base_type.rb +0 -42
- data/lib/datastax_rails/types/binary_type.rb +0 -19
- data/lib/datastax_rails/types/boolean_type.rb +0 -22
- data/lib/datastax_rails/types/date_type.rb +0 -23
- data/lib/datastax_rails/types/float_type.rb +0 -18
- data/lib/datastax_rails/types/integer_type.rb +0 -18
- data/lib/datastax_rails/types/string_type.rb +0 -16
- data/lib/datastax_rails/types/text_type.rb +0 -15
- data/lib/datastax_rails/types/time_type.rb +0 -23
- data/spec/datastax_rails/types/float_type_spec.rb +0 -31
- data/spec/datastax_rails/types/integer_type_spec.rb +0 -31
- data/spec/datastax_rails/types/time_type_spec.rb +0 -28
@@ -0,0 +1,59 @@
|
|
1
|
+
module DatastaxRails
|
2
|
+
module AttributeMethods
|
3
|
+
module Write
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
attribute_method_suffix "="
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
protected
|
12
|
+
|
13
|
+
# See define_method_attribute in read.rb for an explanation of
|
14
|
+
# this code.
|
15
|
+
def define_method_attribute=(name)
|
16
|
+
safe_name = name.unpack('h*').first
|
17
|
+
generated_attribute_methods::AttrNames.set_name_cache safe_name, name
|
18
|
+
|
19
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
20
|
+
def __temp__#{safe_name}=(value)
|
21
|
+
write_attribute(AttrNames::ATTR_#{safe_name}, value)
|
22
|
+
end
|
23
|
+
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
24
|
+
undef_method :__temp__#{safe_name}=
|
25
|
+
STR
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the
|
30
|
+
# specified +value+. Empty strings for fixnum and float columns are
|
31
|
+
# turned into +nil+.
|
32
|
+
def write_attribute(attr_name, value)
|
33
|
+
attr_name = attr_name.to_s
|
34
|
+
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
35
|
+
@attributes_cache.delete(attr_name)
|
36
|
+
column = column_for_attribute(attr_name)
|
37
|
+
|
38
|
+
# If we're dealing with a binary column, write the data to the cache
|
39
|
+
# so we don't attempt to typecast multiple times.
|
40
|
+
if column && column.binary?
|
41
|
+
@attributes_cache[attr_name] = value
|
42
|
+
end
|
43
|
+
|
44
|
+
if column || @attributes.has_key?(attr_name)
|
45
|
+
@attributes[attr_name] = value
|
46
|
+
else
|
47
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias_method :raw_write_attribute, :write_attribute
|
51
|
+
|
52
|
+
private
|
53
|
+
# Handle *= for method_missing.
|
54
|
+
def attribute=(attribute_name, value)
|
55
|
+
write_attribute(attribute_name, value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/datastax_rails/base.rb
CHANGED
@@ -1,11 +1,3 @@
|
|
1
|
-
if Rails.version =~ /^3.*/
|
2
|
-
# Dynamic finders are only supported in Rails 3.x applications (depricated in 4.x)
|
3
|
-
require 'active_record/dynamic_finder_match'
|
4
|
-
require 'active_record/dynamic_scope_match'
|
5
|
-
elsif Rails.version =~ /^4.*./
|
6
|
-
require 'active_record/deprecated_finders/dynamic_matchers'
|
7
|
-
end
|
8
|
-
require 'datastax_rails/types'
|
9
1
|
require 'datastax_rails/errors'
|
10
2
|
module DatastaxRails #:nodoc:
|
11
3
|
# = DatastaxRails
|
@@ -13,7 +5,8 @@ module DatastaxRails #:nodoc:
|
|
13
5
|
# DatastaxRails-based objects differ from Active Record objects in that they specify their
|
14
6
|
# attributes directly on the model. This is necessary because of the fact that Cassandra
|
15
7
|
# column families do not have a set list of columns but rather can have different columns per
|
16
|
-
# row.
|
8
|
+
# row. (This is not strictly true any more, but it's still not as nailed down as SQL.)
|
9
|
+
# By specifying the attributes on the model, getters and setters are automatically
|
17
10
|
# created, and the attribute is automatically indexed into SOLR.
|
18
11
|
#
|
19
12
|
#
|
@@ -26,24 +19,14 @@ module DatastaxRails #:nodoc:
|
|
26
19
|
# your Datastax cluster.
|
27
20
|
#
|
28
21
|
# class Person < DatastaxRails::Base
|
29
|
-
#
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# If you want to use a natural key (i.e., one or more of the columns of your data),
|
33
|
-
# the following would work.
|
34
|
-
#
|
35
|
-
# class Person < DatastaxRails::Base
|
36
|
-
# key :natural, :attributes => [:last_name, :first_name]
|
22
|
+
# uuid :id
|
37
23
|
# end
|
38
24
|
#
|
39
|
-
#
|
25
|
+
# You don't have to use a uuid. You can use a different column as your primary key.
|
40
26
|
#
|
41
27
|
# class Person < DatastaxRails::Base
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# def my_key
|
45
|
-
# # Some logic to generate a key
|
46
|
-
# end
|
28
|
+
# self.primary_key = 'userid'
|
29
|
+
# string :userid
|
47
30
|
# end
|
48
31
|
#
|
49
32
|
# == Attributes
|
@@ -51,30 +34,35 @@ module DatastaxRails #:nodoc:
|
|
51
34
|
# Attributes are specified near the top of the model. The following attribute types
|
52
35
|
# are supported:
|
53
36
|
#
|
54
|
-
# * array - an array of strings
|
55
37
|
# * binary - a large object that will not be indexed into SOLR (e.g., BLOB)
|
56
38
|
# * boolean - true/false values
|
57
39
|
# * date - a date without a time component
|
58
40
|
# * float - a number in floating point notation
|
59
41
|
# * integer - a whole, round number of any size
|
42
|
+
# * list - an ordered list of values of a single type
|
43
|
+
# * map - a collection of key/value pairs of a single type (keys are always strings)
|
44
|
+
# * set - an un-ordered set of unique values of a single type
|
60
45
|
# * string - a generic string type that is not tokenized by default
|
61
46
|
# * text - like strings but will be tokenized for full-text searching by default
|
62
47
|
# * time - a datetime object
|
63
48
|
# * timestamps - a special type that instructs DSR to include created_at and updated_at
|
49
|
+
# * uuid - a UUID in standard UUID format
|
64
50
|
#
|
65
51
|
# The following options may be specified on the various types to control how they
|
66
52
|
# are indexed into SOLR:
|
67
53
|
#
|
68
|
-
# *
|
54
|
+
# * solr_index - If the attribute should the attribute be indexed into SOLR.
|
69
55
|
# Defaults to true for everything but binary.
|
70
|
-
# *
|
56
|
+
# * solr_store - If the attribute should the attribute be stored in SOLR.
|
71
57
|
# Defaults to true for everything but binary. (see note)
|
72
58
|
# * sortable - If the attribute should be sortable by SOLR.
|
73
59
|
# Defaults to true for everything but binary and text. (see note)
|
74
60
|
# * tokenized - If the attribute should be tokenized for full-text searching within the field.
|
75
|
-
# Defaults to true for
|
61
|
+
# Defaults to true for text.
|
76
62
|
# * fulltext - If the attribute should be included in the default field for full-text searches.
|
77
63
|
# Defaults to true for text and string.
|
64
|
+
# * multi_valued - If the field will contain multiple values in Solr.
|
65
|
+
# Defaults to true for list and set. This should never need to be set manually.
|
78
66
|
#
|
79
67
|
# NOTES:
|
80
68
|
# * No fields are actually stored in SOLR. When a field is requested from SOLR, the field
|
@@ -87,14 +75,11 @@ module DatastaxRails #:nodoc:
|
|
87
75
|
# one that gets tokenized and one that is a single token for sorting. As this inflates the
|
88
76
|
# size of the index, you don't want to do this for large fields (which probably don't make
|
89
77
|
# sense to sort on anyways).
|
90
|
-
# * Arrays are tokenized specially. Each element of the array is treated as a single token.
|
91
|
-
# This means that you can match against any single element, but you cannot search within
|
92
|
-
# elements. This functionality may be added at a later time.
|
93
78
|
#
|
94
79
|
# EXAMPLE:
|
95
80
|
#
|
96
81
|
# class Person < DatastaxRails::Base
|
97
|
-
#
|
82
|
+
# uuid :id
|
98
83
|
# string :first_name
|
99
84
|
# string :user_name
|
100
85
|
# text :bio
|
@@ -105,13 +90,15 @@ module DatastaxRails #:nodoc:
|
|
105
90
|
#
|
106
91
|
# == Schemas
|
107
92
|
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
93
|
+
# DSR will automatically manage both the Cassandra and Solr schemas for you based on the
|
94
|
+
# attributes that you specify on the model. You can override the Solr schema if you
|
95
|
+
# want to have something custom. There is a rake task that manages all of the schema
|
96
|
+
# information. It will create column families and columns as needed and upload the
|
97
|
+
# Solr schema when necessary. If there are changes, it will automatically kick off a
|
98
|
+
# reindex in the background.
|
99
|
+
#
|
100
|
+
# As of Cassandra 1.2, there is no way to remove a column. Cassandra 2.0 supports it,
|
101
|
+
# but it hasn't been implemented in DSR yet.
|
115
102
|
#
|
116
103
|
# TODO: Need a way to remove ununsed column families.
|
117
104
|
#
|
@@ -121,7 +108,7 @@ module DatastaxRails #:nodoc:
|
|
121
108
|
# method is especially useful when you're receiving the data from somewhere else, like an
|
122
109
|
# HTTP request. It works like this:
|
123
110
|
#
|
124
|
-
# user = User.new(:
|
111
|
+
# user = User.new(name: "David", occupation: "Code Artist")
|
125
112
|
# user.name # => "David"
|
126
113
|
#
|
127
114
|
# You can also use block initialization:
|
@@ -141,16 +128,16 @@ module DatastaxRails #:nodoc:
|
|
141
128
|
#
|
142
129
|
# Cassandra has a concept of consistency levels when it comes to saving records. For a
|
143
130
|
# detailed discussion on Cassandra data consistency, see:
|
144
|
-
# http://www.datastax.com/
|
131
|
+
# http://www.datastax.com/documentation/cassandra/1.2/cassandra/dml/dml_config_consistency_c.html
|
145
132
|
#
|
146
133
|
# DatastaxRails allows you to specify the consistency when you save and retrieve objects.
|
147
134
|
#
|
148
|
-
# user = User.new(:
|
149
|
-
# user.save(:
|
135
|
+
# user = User.new(name: 'David')
|
136
|
+
# user.save(consistency: 'ALL')
|
150
137
|
#
|
151
|
-
# User.create(params[:user], {:
|
138
|
+
# User.create(params[:user], {consistency: :local_quorum})
|
152
139
|
#
|
153
|
-
# User.consistency(:local_quorum).where(:
|
140
|
+
# User.consistency(:local_quorum).where(name: 'David')
|
154
141
|
#
|
155
142
|
# The default consistency level in DatastaxRails is QUORUM for writes and for retrieval
|
156
143
|
# by ID. SOLR only supports a consistency level of ONE. See the documentation for
|
@@ -177,33 +164,36 @@ module DatastaxRails #:nodoc:
|
|
177
164
|
# A simple hash without a statement will generate conditions based on equality using boolean AND logic.
|
178
165
|
# For instance:
|
179
166
|
#
|
180
|
-
# Student.where(:
|
167
|
+
# Student.where(first_name: "Harvey", status: 1)
|
181
168
|
# Student.where(params[:student])
|
182
169
|
#
|
183
170
|
# A range may be used in the hash to use a SOLR range query:
|
184
171
|
#
|
185
|
-
# Student.where(:
|
172
|
+
# Student.where(grade: 9..12)
|
186
173
|
#
|
187
174
|
# An array may be used in the hash to construct a SOLR OR query:
|
188
175
|
#
|
189
|
-
# Student.where(:
|
176
|
+
# Student.where(grade: [9,11,12])
|
190
177
|
#
|
191
178
|
# Inequality can be tested for like so:
|
192
179
|
#
|
193
|
-
# Student.where_not(:
|
180
|
+
# Student.where_not(grade: 9)
|
194
181
|
# Student.where(:grade).greater_than(9)
|
195
182
|
# Student.where(:grade).less_than(10)
|
196
183
|
#
|
197
|
-
#
|
198
|
-
#
|
184
|
+
# NOTE that Solr inequalities are inclusive so really, the second example above is retrieving records
|
185
|
+
# where grace is greater than or equal to 9. Be sure to keep this in mind when you do inequality queries.
|
186
|
+
#
|
187
|
+
# Fulltext searching is natively supported. All string and text fields are automatically indexed for
|
188
|
+
# fulltext searching.
|
199
189
|
#
|
200
190
|
# Post.fulltext('Apple AND "iPhone 4s"')
|
201
191
|
#
|
202
|
-
# See the documentation on DatastaxRails::SearchMethods for more information and examples.
|
192
|
+
# See the documentation on {DatastaxRails::SearchMethods} for more information and examples.
|
203
193
|
#
|
204
194
|
# == Overwriting default accessors
|
205
195
|
#
|
206
|
-
# All column values are automatically available through basic accessors on the
|
196
|
+
# All column values are automatically available through basic accessors on the object,
|
207
197
|
# but sometimes you want to specialize this behavior. This can be done by overwriting
|
208
198
|
# the default accessors (using the same name as the attribute) and calling
|
209
199
|
# <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
|
@@ -226,81 +216,121 @@ module DatastaxRails #:nodoc:
|
|
226
216
|
#
|
227
217
|
# == Dynamic attribute-based finders
|
228
218
|
#
|
229
|
-
#
|
219
|
+
# Dynamic finders have been removed from Rails. As a result, they have also been removed from DSR.
|
220
|
+
# In its place, the +find_by+ method can be used:
|
230
221
|
#
|
231
|
-
#
|
232
|
-
# by simple queries without using where chains. They work by appending the name of an attribute
|
233
|
-
# to <tt>find_by_</tt> or <tt>find_all_by_</tt> and thus produces finders
|
234
|
-
# like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
|
235
|
-
# <tt>Payment.find_by_transaction_id</tt>. Instead of writing
|
236
|
-
# <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
|
237
|
-
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
|
238
|
-
# <tt>Person.find_all_by_last_name(last_name)</tt>.
|
222
|
+
# Student.find_by(name: 'Jason')
|
239
223
|
#
|
240
|
-
#
|
224
|
+
# NOTE: there is a subtle difference between the following that does not exist in ActiveRecord:
|
241
225
|
#
|
242
|
-
#
|
243
|
-
#
|
226
|
+
# Student.find_by(name: 'Jason')
|
227
|
+
# Student.where(name: 'Jason').first
|
244
228
|
#
|
245
|
-
#
|
229
|
+
# The difference is that the first is escaped so that special characters can be used. The
|
230
|
+
# second method requires you to do the escaping yourself if you need it done. As an example,
|
246
231
|
#
|
247
|
-
#
|
248
|
-
#
|
232
|
+
# Company.find_by(name: 'All*') #=> finds only the company with the literal name 'All*'
|
233
|
+
# Company.where(name: 'All*').first #=> finds the first company whose name begins with All
|
249
234
|
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
# unless they are given in a block.
|
235
|
+
# See DatastaxRails::FinderMethods for more information
|
236
|
+
#
|
237
|
+
# == Facets
|
254
238
|
#
|
255
|
-
#
|
239
|
+
# DSR support both field and range facets. For additional detail on facets, see the documentation
|
240
|
+
# available under the {DatastaxRails::FacetMethods} module. The result is available through the
|
241
|
+
# facets accessor.
|
256
242
|
#
|
257
|
-
#
|
258
|
-
#
|
243
|
+
# results = Article.field_facet(:author)
|
244
|
+
# results.facets #=> {"author"=>["vonnegut", 2. "asimov", 3]}
|
259
245
|
#
|
260
|
-
#
|
261
|
-
#
|
246
|
+
# Model.field_facet(:author)
|
247
|
+
# Model.field_facet(:author, sort: 'count', limit: 10, mincount: 1)
|
248
|
+
# Model.range_facet(:price, 500, 1000, 10)
|
249
|
+
# Model.range_facet(:price, 500, 1000, 10, include: 'all')
|
250
|
+
# Model.range_facet(:publication_date, "1968-01-01T00:00:00Z", "2000-01-01T00:00:00Z", "+1YEAR")
|
262
251
|
#
|
263
|
-
#
|
264
|
-
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
|
252
|
+
# Range Gap syntax for dates: +1YEAR, +5YEAR, +5YEARS, +1MONTH, +1DAY
|
265
253
|
#
|
266
|
-
#
|
267
|
-
# saving it first. Protected attributes won't be set unless they are given in a block.
|
254
|
+
# Useful constants:
|
268
255
|
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
#
|
256
|
+
# DatastaxRails::FacetMethods::BY_YEAR (+1YEAR)
|
257
|
+
# DatastaxRails::FacetMethods::BY_MONTH (+1MONTH)
|
258
|
+
# DatastaxRails::FacetMethods::BY_DAY (+1DAY)
|
272
259
|
#
|
273
|
-
#
|
274
|
-
# using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
|
275
|
-
# but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
|
260
|
+
# Model.range_facet(:publication_date, "1968-01-01T00:00:00Z", "2000-01-01T00:00:00Z", DatastaxRails::FacetMethods::BY_YEAR)
|
276
261
|
#
|
277
|
-
#
|
262
|
+
# == Collections
|
278
263
|
#
|
279
|
-
#
|
264
|
+
# Cassandra supports the notion of collections on a row. The three types of supported
|
265
|
+
# collections are +set+, +list+, and +map+.
|
280
266
|
#
|
281
|
-
#
|
282
|
-
#
|
267
|
+
# By default collections hold strings. You can override this by passing a :holds option in the
|
268
|
+
# attribute definition. Sets can hold anything other than other collections, however, a given
|
269
|
+
# collection can only hold a single type of values.
|
283
270
|
#
|
284
|
-
#
|
271
|
+
# NOTE: There is a limitation in Cassandra where only the first 64k entries of a collection are
|
272
|
+
# ever returned with a query. Therefore, if you put more than 64k entries in a collection you
|
273
|
+
# will lose data.
|
285
274
|
#
|
286
|
-
#
|
287
|
-
#
|
275
|
+
# === Set
|
276
|
+
#
|
277
|
+
# A set is an un-ordered collection of unique values. This collection is fully searchable in Solr.
|
288
278
|
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
292
|
-
#
|
293
|
-
#
|
279
|
+
# class User < DatastaxRails::Base
|
280
|
+
# uuid :id
|
281
|
+
# string :username
|
282
|
+
# set :emails
|
283
|
+
# end
|
294
284
|
#
|
295
|
-
#
|
285
|
+
# The default set will hold strings. You can modify this behavior like so:
|
296
286
|
#
|
297
|
-
#
|
287
|
+
# class Student < DatastaxRails::Base
|
288
|
+
# uuid :id
|
289
|
+
# string :name
|
290
|
+
# set :grades, holds: :integers
|
291
|
+
# end
|
298
292
|
#
|
299
|
-
#
|
300
|
-
#
|
301
|
-
#
|
293
|
+
# User.where(emails: 'jim@example.com') #=> Returns all users where jim@example.com is in the set
|
294
|
+
# user = User.new(name: 'Jim', emails: ['jim@example.com'])
|
295
|
+
# user.emails << 'jim@example.com'
|
296
|
+
# user.emails #=> ['jim@example.com']
|
302
297
|
#
|
303
|
-
#
|
298
|
+
# === List
|
299
|
+
#
|
300
|
+
# An ordered collection of values. They do not necessarily have to be unique. The collection
|
301
|
+
# will be fully searchable in Solr.
|
302
|
+
#
|
303
|
+
# class Student < DatastaxRails::Base
|
304
|
+
# uuid :id
|
305
|
+
# string :name
|
306
|
+
# list :classrooms, holds: integers
|
307
|
+
# end
|
308
|
+
#
|
309
|
+
# Student.where(classrooms: 307) #=> Returns all students that have a class in room 307.
|
310
|
+
# student = Student.new(name: 'Sally', classrooms: [307, 305, 301, 307])
|
311
|
+
# student.classrooms << 304
|
312
|
+
# student.classrooms #=> [307, 305, 301, 307, 304]
|
313
|
+
#
|
314
|
+
# === Map
|
315
|
+
#
|
316
|
+
# A collection of key/value pairs where the key is a string and the value is the
|
317
|
+
# specified type. The collection becomes available in Solr as dynamic fields.
|
318
|
+
#
|
319
|
+
# class Student < DatastaxRails::Base
|
320
|
+
# uuid :id
|
321
|
+
# string :name
|
322
|
+
# map :scores_, holds: :integers
|
323
|
+
# end
|
324
|
+
#
|
325
|
+
# student = Student.new(:name 'Sally')
|
326
|
+
# student.scores['midterm'] = 98
|
327
|
+
# student.scores['final'] = 97
|
328
|
+
# student.scores #=> {'scores_midterm' => 98, 'scores_final' => 97}
|
329
|
+
# Student.where(scores_final: 97) #=> Returns all students that scored 97 on their final
|
330
|
+
#
|
331
|
+
# Note that the map name gets prepended to the key. This is how Solr maps it's dynamic fields
|
332
|
+
# into the cassandra map. For this reason, it's usually a good idea to put an underscore (_)
|
333
|
+
# at the end of the map name to prevent collisions.
|
304
334
|
#
|
305
335
|
# == Exceptions
|
306
336
|
#
|
@@ -312,30 +342,21 @@ module DatastaxRails #:nodoc:
|
|
312
342
|
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
|
313
343
|
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
|
314
344
|
# nothing was found, please check its documentation for further details.
|
315
|
-
# *
|
316
|
-
#
|
317
|
-
#
|
318
|
-
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
319
|
-
# <tt>attributes=</tt> method.
|
320
|
-
# You can inspect the +attribute+ property of the exception object to determine which attribute
|
321
|
-
# triggered the error.
|
322
|
-
#
|
323
|
-
# See the documentation for SearchMethods for more examples of using the search API.
|
345
|
+
# * UnknownAttributeError - The specified attribute isn't defined on your model.
|
346
|
+
#
|
347
|
+
# See the documentation for {DatastaxRails::SearchMethods} for more examples of using the search API.
|
324
348
|
class Base
|
325
349
|
extend ActiveModel::Naming
|
326
350
|
include ActiveModel::Conversion
|
327
351
|
extend ActiveSupport::DescendantsTracker
|
328
352
|
|
353
|
+
include Persistence
|
329
354
|
include Connection
|
330
355
|
include Inheritance
|
331
|
-
include Identity
|
332
356
|
include FinderMethods
|
333
357
|
include Batches
|
334
358
|
include AttributeAssignment
|
335
359
|
include AttributeMethods
|
336
|
-
include AttributeMethods::Dirty
|
337
|
-
include AttributeMethods::Typecasting
|
338
|
-
include Persistence
|
339
360
|
include Callbacks
|
340
361
|
include Validations
|
341
362
|
include Reflection
|
@@ -349,15 +370,29 @@ module DatastaxRails #:nodoc:
|
|
349
370
|
class_attribute :default_scopes, :instance_writer => false
|
350
371
|
self.default_scopes = []
|
351
372
|
|
352
|
-
# Stores the configuration information
|
373
|
+
# Stores the connection configuration information
|
353
374
|
class_attribute :config
|
354
375
|
|
376
|
+
class_attribute :default_timezone, :instance_writer => false
|
377
|
+
self.default_timezone = :utc
|
378
|
+
|
379
|
+
# Stores the default consistency level (QUORUM by default)
|
355
380
|
class_attribute :default_consistency
|
356
381
|
self.default_consistency = :quorum
|
357
382
|
|
383
|
+
# Stores the method of saving data (CQL by default)
|
358
384
|
class_attribute :storage_method
|
359
385
|
self.storage_method = :cql
|
360
386
|
|
387
|
+
# Stores any additional information that should be used when creating the column family
|
388
|
+
# See {DatastaxRails::WideStorageModel} or {DatastaxRails::Payload} model for an example
|
389
|
+
class_attribute :create_options
|
390
|
+
|
391
|
+
# Stores the attribute that wide models should cluster on. Basically, this is the
|
392
|
+
# attribute that CQL uses to "group" columns into logical records even though they
|
393
|
+
# are stored on the same row.
|
394
|
+
class_attribute :cluster_by
|
395
|
+
|
361
396
|
attr_reader :attributes
|
362
397
|
attr_reader :loaded_attributes
|
363
398
|
attr_accessor :key
|
@@ -371,32 +406,70 @@ module DatastaxRails #:nodoc:
|
|
371
406
|
class_attribute :legacy_mapping
|
372
407
|
|
373
408
|
def initialize(attributes = {}, options = {})
|
374
|
-
|
375
|
-
|
376
|
-
@loaded_attributes = {}.with_indifferent_access
|
377
|
-
|
378
|
-
@new_record = true
|
379
|
-
@destroyed = false
|
380
|
-
@previously_changed = {}
|
381
|
-
@changed_attributes = {}
|
382
|
-
|
383
|
-
__set_defaults
|
409
|
+
defaults = self.class.column_defaults.dup
|
410
|
+
defaults.each { |k, v| v.duplicable? ? v.dup : v }
|
384
411
|
|
412
|
+
@attributes = self.initialize_attributes(defaults)
|
413
|
+
@column_types = self.class.columns_hash
|
414
|
+
|
415
|
+
init_internals
|
416
|
+
init_changed_attributes
|
385
417
|
populate_with_current_scope_attributes
|
386
418
|
|
387
|
-
assign_attributes(attributes
|
419
|
+
assign_attributes(attributes) if attributes
|
388
420
|
|
389
421
|
yield self if block_given?
|
390
|
-
run_callbacks :initialize
|
422
|
+
run_callbacks :initialize unless _initialize_callbacks.empty?
|
391
423
|
end
|
392
424
|
|
393
|
-
#
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
425
|
+
# Initialize an empty model object from +coder+. +coder+ must contain
|
426
|
+
# the attributes necessary for initializing an empty model object. For
|
427
|
+
# example:
|
428
|
+
#
|
429
|
+
# class Post < DatastaxRails::Base
|
430
|
+
# end
|
431
|
+
#
|
432
|
+
# post = Post.allocate
|
433
|
+
# post.init_with('attributes' => { 'title' => 'hello world' })
|
434
|
+
# post.title # => 'hello world'
|
435
|
+
def init_with(coder)
|
436
|
+
Types::DirtyCollection.ignore_modifications do
|
437
|
+
@attributes = self.initialize_attributes(coder['attributes'])
|
438
|
+
@column_types_override = coder['column_types']
|
439
|
+
@column_types = self.class.columns_hash
|
440
|
+
|
441
|
+
init_internals
|
442
|
+
|
443
|
+
@new_record = false
|
444
|
+
|
445
|
+
run_callbacks :find
|
446
|
+
run_callbacks :initialize
|
447
|
+
end
|
448
|
+
self
|
449
|
+
end
|
450
|
+
|
451
|
+
def init_internals
|
452
|
+
pk = self.class.primary_key
|
453
|
+
@attributes[pk] = nil unless @attributes.key?(pk)
|
454
|
+
|
455
|
+
@association_cache = {}
|
456
|
+
@attributes_cache = {}
|
457
|
+
@previously_changed = {}
|
458
|
+
@changed_attributes = {}
|
459
|
+
@loaded_attributes = Hash[@attributes.map{|k,v| [k,true]}].with_indifferent_access
|
460
|
+
@readonly = false
|
461
|
+
@destroyed = false
|
462
|
+
@marked_for_destruction = false
|
463
|
+
@destroyed_by_association = nil
|
464
|
+
@new_record = true
|
465
|
+
end
|
466
|
+
|
467
|
+
def init_changed_attributes
|
468
|
+
# Intentionally avoid using #column_defaults since overridden defaults
|
469
|
+
# won't get written unless they get marked as changed
|
470
|
+
self.class.columns.each do |c|
|
471
|
+
attr, orig_value = c.name, c.default
|
472
|
+
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
|
400
473
|
end
|
401
474
|
end
|
402
475
|
|
@@ -421,7 +494,7 @@ module DatastaxRails #:nodoc:
|
|
421
494
|
def ==(comparison_object)
|
422
495
|
comparison_object.equal?(self) ||
|
423
496
|
(comparison_object.instance_of?(self.class) &&
|
424
|
-
comparison_object.
|
497
|
+
comparison_object.id == self.id &&
|
425
498
|
!comparison_object.new_record?)
|
426
499
|
end
|
427
500
|
|
@@ -432,6 +505,7 @@ module DatastaxRails #:nodoc:
|
|
432
505
|
def attribute_names
|
433
506
|
self.class.attribute_names
|
434
507
|
end
|
508
|
+
alias :column_names :attribute_names
|
435
509
|
|
436
510
|
def valid_consistency?(level) #:nodoc:
|
437
511
|
self.class.validate_consistency(level.to_s.upcase)
|
@@ -485,7 +559,7 @@ module DatastaxRails #:nodoc:
|
|
485
559
|
end
|
486
560
|
|
487
561
|
def legacy_mapping?
|
488
|
-
|
562
|
+
self.legacy_mapping
|
489
563
|
end
|
490
564
|
|
491
565
|
def base_class
|
@@ -506,27 +580,15 @@ module DatastaxRails #:nodoc:
|
|
506
580
|
Rails.logger
|
507
581
|
end
|
508
582
|
|
509
|
-
def respond_to?(method_id, include_private = false)
|
510
|
-
|
511
|
-
if Rails.version =~ /^3.*/
|
512
|
-
if match = ActiveRecord::DynamicFinderMatch.match(method_id)
|
513
|
-
return true if all_attributes_exists?(match.attribute_names)
|
514
|
-
elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
|
515
|
-
return true if all_attributes_exists?(match.attribute_names)
|
516
|
-
end
|
517
|
-
elsif Rails.version =~ /^4.*/
|
518
|
-
if match = ActiveRecord::DynamicMatchers::Method.match(self, method_id)
|
519
|
-
return true if all_attributes_exists?(match.attribute_names)
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
super
|
524
|
-
end
|
525
|
-
|
526
583
|
# Returns an array of attribute names as strings
|
527
584
|
def attribute_names
|
528
585
|
@attribute_names ||= attribute_definitions.keys.collect {|a|a.to_s}
|
529
586
|
end
|
587
|
+
alias :column_names :attribute_names
|
588
|
+
|
589
|
+
def columns
|
590
|
+
@columns ||= attribute_definitions.values
|
591
|
+
end
|
530
592
|
|
531
593
|
# SOLR always paginates all requests. There is no way to disable it, so we are
|
532
594
|
# setting the default page size to an arbitrarily high number so that we effectively
|
@@ -552,9 +614,15 @@ module DatastaxRails #:nodoc:
|
|
552
614
|
DatastaxRails::Cql::Consistency::VALID_CONSISTENCY_LEVELS.include?(level)
|
553
615
|
end
|
554
616
|
|
555
|
-
|
556
|
-
|
557
|
-
|
617
|
+
# Returns a string like 'Post(id:integer, title:string, body:text)'
|
618
|
+
def inspect
|
619
|
+
if self == Base
|
620
|
+
super
|
621
|
+
else
|
622
|
+
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
|
623
|
+
"#{super}(#{attr_list})"
|
624
|
+
end
|
625
|
+
end
|
558
626
|
|
559
627
|
private
|
560
628
|
|
@@ -564,83 +632,6 @@ module DatastaxRails #:nodoc:
|
|
564
632
|
relation
|
565
633
|
end
|
566
634
|
|
567
|
-
# Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
|
568
|
-
# <tt>User.scoped_by_user_name(user_name).
|
569
|
-
#
|
570
|
-
# It's even possible to use all the additional parameters to +find+. For example, the
|
571
|
-
# full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
|
572
|
-
#
|
573
|
-
# Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
|
574
|
-
# is first invoked, so that future attempts to use it do not run through method_missing.
|
575
|
-
def method_missing(method_id, *arguments, &block)
|
576
|
-
if Rails.version =~ /^3.*/
|
577
|
-
if match = ActiveRecord::DynamicFinderMatch.match(method_id)
|
578
|
-
attribute_names = match.attribute_names
|
579
|
-
super unless all_attributes_exists?(attribute_names)
|
580
|
-
if !arguments.first.is_a?(Hash) && arguments.size < attribute_names.size
|
581
|
-
ActiveSupport::Deprecation.warn(
|
582
|
-
"Calling dynamic finder with less number of arguments than the number of attributes in " \
|
583
|
-
"method name is deprecated and will raise an ArguementError in the next version of Rails. " \
|
584
|
-
"Please passing `nil' to the argument you want it to be nil."
|
585
|
-
)
|
586
|
-
end
|
587
|
-
if match.finder?
|
588
|
-
options = arguments.extract_options!
|
589
|
-
relation = options.any? ? scoped(options) : scoped
|
590
|
-
relation.send :find_by_attributes, match, attribute_names, *arguments
|
591
|
-
elsif match.instantiator?
|
592
|
-
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
|
593
|
-
end
|
594
|
-
elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
|
595
|
-
attribute_names = match.attribute_names
|
596
|
-
super unless all_attributes_exists?(attribute_names)
|
597
|
-
if arguments.size < attribute_names.size
|
598
|
-
ActiveSupport::Deprecation.warn(
|
599
|
-
"Calling dynamic scope with less number of arguments than the number of attributes in " \
|
600
|
-
"method name is deprecated and will raise an ArguementError in the next version of Rails. " \
|
601
|
-
"Please passing `nil' to the argument you want it to be nil."
|
602
|
-
)
|
603
|
-
end
|
604
|
-
if match.scope?
|
605
|
-
self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
606
|
-
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
607
|
-
attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
|
608
|
-
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
609
|
-
end # end
|
610
|
-
METHOD
|
611
|
-
send(method_id, *arguments)
|
612
|
-
end
|
613
|
-
else
|
614
|
-
super
|
615
|
-
end
|
616
|
-
elsif Rails.version =~ /^4.*/
|
617
|
-
if match = ActiveRecord::DynamicMatchers::Method.match(self, method_id)
|
618
|
-
attribute_names = match.attribute_names
|
619
|
-
super unless all_attributes_exists?(attribute_names)
|
620
|
-
if !arguments.first.is_a?(Hash) && arguments.size < attribute_names.size
|
621
|
-
ActiveSupport::Deprecation.warn(
|
622
|
-
"Calling dynamic scope with less number of arguments than the number of attributes in " \
|
623
|
-
"method name is deprecated and will raise an ArguementError in the next version of Rails. " \
|
624
|
-
"Please passing `nil' to the argument you want it to be nil."
|
625
|
-
)
|
626
|
-
end
|
627
|
-
if match.finder.present?
|
628
|
-
options = arguments.extract_options!
|
629
|
-
relation = options.any? ? scoped(options) : scoped
|
630
|
-
relation.send :find_by_attributes, match, attribute_names, *arguments
|
631
|
-
elsif match.instantiator?
|
632
|
-
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
|
633
|
-
end
|
634
|
-
end
|
635
|
-
else
|
636
|
-
super
|
637
|
-
end
|
638
|
-
end
|
639
|
-
|
640
|
-
def all_attributes_exists?(attribute_names)
|
641
|
-
(attribute_names - self.attribute_names).empty?
|
642
|
-
end
|
643
|
-
|
644
635
|
def relation #:nodoc:
|
645
636
|
Relation.new(self, column_family)
|
646
637
|
end
|