datastax_rails 1.2.3 → 2.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|