datastax_rails 1.0.8 → 1.0.10

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.
@@ -29,6 +29,7 @@ module DatastaxRails
29
29
  end
30
30
 
31
31
  def replace_keys(record)
32
+ owner.loaded_attributes[reflection.foreign_key] = true
32
33
  owner.send("#{reflection.foreign_key}_will_change!")
33
34
  if record
34
35
  owner[reflection.foreign_key] = record.id
@@ -12,7 +12,7 @@ module DatastaxRails
12
12
  value ||= coder.default
13
13
  return unless value
14
14
 
15
- value = value.kind_of?(String) ? coder.decode(value) : value
15
+ value = (value.kind_of?(String) || value.kind_of?(Array)) ? coder.decode(value) : value
16
16
  coder.wrap(record, name, value)
17
17
  end
18
18
  end
@@ -31,6 +31,7 @@ module DatastaxRails
31
31
 
32
32
  def write_attribute(name, value)
33
33
  name = name.to_s
34
+ loaded_attributes[name] = true
34
35
  old = read_attribute(name)
35
36
  super
36
37
 
@@ -37,6 +37,14 @@ module DatastaxRails
37
37
  self.models << child
38
38
  end
39
39
 
40
+ # Casts a single attribute according to the appropriate coder.
41
+ #
42
+ # @param [DatastaxRails::Base] record the record to which this attribute belongs
43
+ # @param [String] name the name of the attribute
44
+ # @param [Object] value the value of the attribute prior to typecasting
45
+ #
46
+ # @return the typecasted value
47
+ # @raise [NoMethodError] if the attribute is unknown
40
48
  def typecast_attribute(record, name, value)
41
49
  if attribute_definition = attribute_definitions[name.to_sym]
42
50
  attribute_definition.instantiate(record, value)
@@ -51,13 +51,17 @@ module DatastaxRails
51
51
  end
52
52
  end
53
53
 
54
+ # Casts the attribute and stores it in the attribute hash.
54
55
  def write_attribute(name, value)
55
56
  @attributes[name.to_s] = self.class.typecast_attribute(self, name, value)
56
57
  end
57
58
 
59
+ # Returns the attribute out of the attribute hash. If the attribute is lazy loaded and hasn't
60
+ # been loaded yet it will be done so now.
58
61
  def read_attribute(name)
59
- if(lazy_attributes.include?(name.to_sym) && @attributes[name.to_s].nil? && persisted?)
62
+ if(!loaded_attributes[name] && persisted? && !key.blank?)
60
63
  @attributes[name.to_s] = self.class.select(name).with_cassandra.find(self.id).read_attribute(name)
64
+ loaded_attributes[name] = true
61
65
  end
62
66
 
63
67
  @attributes[name.to_s]
@@ -318,11 +318,13 @@ module DatastaxRails #:nodoc:
318
318
  self.models = []
319
319
 
320
320
  attr_reader :attributes
321
+ attr_reader :loaded_attributes
321
322
  attr_accessor :key
322
323
 
323
324
  def initialize(attributes = {}, options = {})
324
325
  @key = attributes.delete(:key)
325
- @attributes = {}
326
+ @attributes = {}.with_indifferent_access
327
+ @loaded_attributes = {}.with_indifferent_access
326
328
 
327
329
  @relation = nil
328
330
  @new_record = true
@@ -44,6 +44,7 @@ module DatastaxRails#:nodoc:
44
44
  if @limit
45
45
  stmt << "LIMIT #{@limit}"
46
46
  end
47
+
47
48
  CassandraCQL::Statement.sanitize(stmt, values)
48
49
  end
49
50
  end
@@ -5,8 +5,9 @@ module DatastaxRails
5
5
  # Next key takes an object and returns the key object it should use.
6
6
  # object will be ignored with synthetic keys but could be useful with natural ones
7
7
  #
8
- # @param [CassandraObject::Base] the object that needs a new key
9
- # @return [CassandraObject::Identity::Key] the key
8
+ # @abstract
9
+ # @param [DatastaxRails::Base] object the object that needs a new key
10
+ # @return [DatastaxRails::Identity::Key] the key
10
11
  #
11
12
  def next_key(object)
12
13
  raise NotImplementedError, "#{self.class.name}#next_key isn't implemented."
@@ -14,8 +15,9 @@ module DatastaxRails
14
15
 
15
16
  # Parse should create a new key object from the 'to_param' format
16
17
  #
17
- # @param [String] the result of calling key.to_param
18
- # @return [CassandraObject::Identity::Key] the parsed key
18
+ # @abstract
19
+ # @param [String] string the result of calling key.to_param
20
+ # @return [DatastaxRails::Identity::Key] the parsed key
19
21
  #
20
22
  def parse(string)
21
23
  raise NotImplementedError, "#{self.class.name}#parse isn't implemented."
@@ -13,8 +13,8 @@ module DatastaxRails #:nodoc:
13
13
  module ClassMethods
14
14
  # Indicate what kind of key the model will have: uuid or natural
15
15
  #
16
- # @param [:uuid, :natural] the type of key
17
- # @param the options you want to pass along to the key factory (like :attributes => :name, for a natural key).
16
+ # @param [:uuid, :natural] name_or_factory the type of key
17
+ # @param [Hash] options the options you want to pass along to the key factory (like :attributes => :name, for a natural key).
18
18
  #
19
19
  def key(name_or_factory = :uuid, *options)
20
20
  @key_factory = case name_or_factory
@@ -29,12 +29,22 @@ module DatastaxRails #:nodoc:
29
29
  end
30
30
  end
31
31
 
32
+ # The next key for the given object. Delegates the actual work to the factory which may
33
+ # or may not use the passed in object to generate the key.
34
+ #
35
+ # @param [DatastaxRails::Base] object the object for which the key is being generated
36
+ # @return [String] a key for this object
32
37
  def next_key(object = nil)
33
38
  @key_factory.next_key(object).tap do |key|
34
39
  raise "Keys may not be nil" if key.nil?
35
40
  end
36
41
  end
37
42
 
43
+ # Parses out a key from the given string. Delegates the actual work to the factory.
44
+ # Return type varies depending on what type of key is used.
45
+ #
46
+ # @param [String] string a string representing a primary key
47
+ # @return an object representing the same key
38
48
  def parse_key(string)
39
49
  @key_factory.parse(string)
40
50
  end
@@ -7,7 +7,18 @@ module DatastaxRails
7
7
  end
8
8
 
9
9
  module ClassMethods
10
- # Removes one or more records with corresponding keys
10
+ # Removes one or more records with corresponding keys. Last parameter can be a hash
11
+ # specifying the consistency level.
12
+ #
13
+ # Model.remove('12345','67890', :consistency => 'LOCAL_QUORUM)
14
+ #
15
+ # @overload remove(*keys, options)
16
+ # Removes one or more keys with the given options
17
+ # @param [String] keys one or more keys to delete
18
+ # @param [Hash] options generally the consistency level to set
19
+ # @overload remove(*keys)
20
+ # Removes one or more keys with the default options
21
+ # @param [String] keys one or more keys to delete
11
22
  def remove(*keys)
12
23
  options = {}
13
24
  if keys.last.is_a?(Hash)
@@ -28,12 +39,12 @@ module DatastaxRails
28
39
  end
29
40
 
30
41
  # Truncates the column_family associated with this class
31
- def delete_all
42
+ def truncate
32
43
  ActiveSupport::Notifications.instrument("truncate.datastax_rails", :column_family => column_family) do
33
44
  cql.truncate.execute
34
45
  end
35
46
  end
36
- alias :truncate :delete_all
47
+ alias :delete_all :truncate
37
48
 
38
49
  def create(attributes = {}, options = {}, &block)
39
50
  new(attributes, &block).tap do |object|
@@ -41,6 +52,13 @@ module DatastaxRails
41
52
  end
42
53
  end
43
54
 
55
+ # Write a record to cassandra. Can be either an insert or an update (they are exactly the same to cassandra)
56
+ #
57
+ # @param [String] key the primary key for the record
58
+ # @param [Hash] attributes a hash containing the columns to set on the record
59
+ # @param [Hash] options a hash containing various options
60
+ # @option options [Symbol] :consistency the consistency to set for the Cassandra operation (e.g., ALL)
61
+ # @option options [String] :schema_version the version of the schema to set for this record
44
62
  def write(key, attributes, options = {})
45
63
  key.tap do |key|
46
64
  attributes = encode_attributes(attributes, options[:schema_version])
@@ -59,13 +77,14 @@ module DatastaxRails
59
77
  end
60
78
  end
61
79
 
62
- def instantiate(key, attributes)
80
+ def instantiate(key, attributes, selected_attributes = [])
63
81
  allocate.tap do |object|
82
+ object.instance_variable_set("@loaded_attributes", {}.with_indifferent_access)
64
83
  object.instance_variable_set("@schema_version", attributes.delete('schema_version'))
65
84
  object.instance_variable_set("@key", parse_key(key)) if key
66
85
  object.instance_variable_set("@new_record", false)
67
86
  object.instance_variable_set("@destroyed", false)
68
- object.instance_variable_set("@attributes", typecast_attributes(object, attributes))
87
+ object.instance_variable_set("@attributes", typecast_attributes(object, attributes, selected_attributes))
69
88
  end
70
89
  end
71
90
 
@@ -75,15 +94,37 @@ module DatastaxRails
75
94
  if value.nil?
76
95
  encoded[column_name.to_s] = ""
77
96
  else
78
- encoded[column_name.to_s] = attribute_definitions[column_name.to_sym].coder.encode(value)
97
+ encoded_value = attribute_definitions[column_name.to_sym].coder.encode(value)
98
+ if(encoded_value.is_a?(Array))
99
+ encoded_value.each_with_index do |chunk,i|
100
+ encoded[column_name.to_s + "_chunk_#{'%05d' % i}"] = chunk
101
+ end
102
+ else
103
+ encoded[column_name.to_s] = encoded_value
104
+ end
79
105
  end
80
106
  end
81
107
  encoded
82
108
  end
83
109
 
84
- def typecast_attributes(object, attributes)
110
+ def typecast_attributes(object, attributes, selected_attributes = [])
85
111
  attributes = attributes.symbolize_keys
86
- Hash[attribute_definitions.map { |k, attribute_definition| [k.to_s, attribute_definition.instantiate(object, attributes[k])] }]
112
+ casted = {}
113
+
114
+ selected_attributes.each do |att|
115
+ object.loaded_attributes[att] = true
116
+ end
117
+
118
+ attribute_definitions.each do |k,definition|
119
+ if(definition.coder.is_a?(DatastaxRails::Types::BinaryType))
120
+ # Need to handle possibly chunked data
121
+ chunks = attributes.select {|key,value| key.to_s =~ /#{k.to_s}_chunk_\d+/ }.sort {|a,b| a.first.to_s <=> b.first.to_s}.collect {|c| c.last}
122
+ casted[k.to_s] = definition.instantiate(object, chunks)
123
+ else
124
+ casted[k.to_s] = definition.instantiate(object, attributes[k])
125
+ end
126
+ end
127
+ casted
87
128
  end
88
129
  end
89
130
 
@@ -131,7 +131,7 @@ module DatastaxRails
131
131
 
132
132
  # Holds all the meta-data about an association as it was specified in the
133
133
  # DatastaxRails class.
134
- class AssociationReflection < MacroReflection #:nodoc:
134
+ class AssociationReflection < MacroReflection
135
135
  # Returns the target association's class.
136
136
  #
137
137
  # class Author < DatastaxRails::Base
@@ -222,14 +222,22 @@ module DatastaxRails
222
222
  # For ad-hoc queries, you will have to use Solr.
223
223
  def query_via_cql
224
224
  select_columns = select_values.empty? ? (@klass.attribute_definitions.keys - @klass.lazy_attributes) : select_values.flatten
225
- cql = @cql.select(select_columns)
225
+ select = []
226
+ select_columns.each do |col|
227
+ if @klass.attribute_definitions[col.to_sym] && @klass.attribute_definitions[col.to_sym].coder.is_a?(DatastaxRails::Types::BinaryType)
228
+ select << "'#{col}_chunk_00000' .. '#{col}_chunk_99999'"
229
+ else
230
+ select << col
231
+ end
232
+ end
233
+ cql = @cql.select(select)
226
234
  cql.using(@consistency_value) if @consistency_value
227
235
  @where_values.each do |wv|
228
236
  cql.conditions(wv)
229
237
  end
230
238
  results = []
231
239
  CassandraCQL::Result.new(cql.execute).fetch do |row|
232
- results << @klass.instantiate(row.row.key,row.to_hash)
240
+ results << @klass.instantiate(row.row.key, row.to_hash, select_columns)
233
241
  end
234
242
  results
235
243
  end
@@ -315,6 +323,8 @@ module DatastaxRails
315
323
  params[:fq] = filter_queries
316
324
  end
317
325
 
326
+ select_columns = select_values.empty? ? (@klass.attribute_definitions.keys - @klass.lazy_attributes) : select_values.flatten
327
+
318
328
  #TODO Need to escape URL stuff (I think)
319
329
  response = rsolr.paginate(@page_value, @per_page_value, 'select', :params => params)["response"]
320
330
  results = DatastaxRails::Collection.new
@@ -328,7 +338,7 @@ module DatastaxRails
328
338
  else
329
339
  response['docs'].each do |doc|
330
340
  key = doc.delete('id')
331
- results << @klass.instantiate(key,doc)
341
+ results << @klass.instantiate(key,doc, select_columns)
332
342
  end
333
343
  end
334
344
  results
@@ -4,7 +4,25 @@ module DatastaxRails
4
4
  DEFAULTS = {:solr_type => false, :indexed => false, :stored => false, :multi_valued => false, :sortable => false, :tokenized => false, :fulltext => false}
5
5
  def encode(str)
6
6
  raise ArgumentError.new("#{self} requires a String") unless str.kind_of?(String)
7
- str.dup
7
+ io = StringIO.new(Base64.encode64(str))
8
+ chunks = []
9
+ while chunk = io.read(1.megabyte)
10
+ chunks << chunk
11
+ end
12
+ chunks
13
+ end
14
+
15
+ def decode(arr)
16
+ if(arr.is_a?(Array))
17
+ io = StringIO.new("","w+")
18
+ arr.each do |chunk|
19
+ io.write(chunk)
20
+ end
21
+ io.rewind
22
+ Base64.decode64(io.read)
23
+ else
24
+ arr
25
+ end
8
26
  end
9
27
 
10
28
  def wrap(record, name, value)
@@ -1,4 +1,4 @@
1
1
  module DatastaxRails
2
2
  # The current version of the gem
3
- VERSION = "1.0.8"
3
+ VERSION = "1.0.10"
4
4
  end