datastax_rails 1.0.8 → 1.0.10

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