cassandra_object 0.6.0.pre

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.
Files changed (113) hide show
  1. data/lib/cassandra_object/associations/one_to_many.rb +136 -0
  2. data/lib/cassandra_object/associations/one_to_one.rb +77 -0
  3. data/lib/cassandra_object/associations.rb +35 -0
  4. data/lib/cassandra_object/attributes.rb +93 -0
  5. data/lib/cassandra_object/base.rb +104 -0
  6. data/lib/cassandra_object/callbacks.rb +10 -0
  7. data/lib/cassandra_object/collection.rb +8 -0
  8. data/lib/cassandra_object/cursor.rb +86 -0
  9. data/lib/cassandra_object/dirty.rb +27 -0
  10. data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
  11. data/lib/cassandra_object/identity/key.rb +20 -0
  12. data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
  13. data/lib/cassandra_object/identity/uuid_key_factory.rb +37 -0
  14. data/lib/cassandra_object/identity.rb +61 -0
  15. data/lib/cassandra_object/indexes.rb +129 -0
  16. data/lib/cassandra_object/legacy_callbacks.rb +33 -0
  17. data/lib/cassandra_object/migrations.rb +72 -0
  18. data/lib/cassandra_object/mocking.rb +15 -0
  19. data/lib/cassandra_object/persistence.rb +193 -0
  20. data/lib/cassandra_object/serialization.rb +6 -0
  21. data/lib/cassandra_object/type_registration.rb +7 -0
  22. data/lib/cassandra_object/types.rb +128 -0
  23. data/lib/cassandra_object/validation.rb +58 -0
  24. data/lib/cassandra_object.rb +30 -0
  25. data/vendor/active_support_shims.rb +4 -0
  26. data/vendor/activemodel/CHANGELOG +13 -0
  27. data/vendor/activemodel/CHANGES +12 -0
  28. data/vendor/activemodel/MIT-LICENSE +21 -0
  29. data/vendor/activemodel/README +21 -0
  30. data/vendor/activemodel/Rakefile +52 -0
  31. data/vendor/activemodel/activemodel.gemspec +19 -0
  32. data/vendor/activemodel/examples/validations.rb +29 -0
  33. data/vendor/activemodel/lib/active_model/attribute_methods.rb +291 -0
  34. data/vendor/activemodel/lib/active_model/callbacks.rb +91 -0
  35. data/vendor/activemodel/lib/active_model/conversion.rb +8 -0
  36. data/vendor/activemodel/lib/active_model/deprecated_error_methods.rb +33 -0
  37. data/vendor/activemodel/lib/active_model/dirty.rb +126 -0
  38. data/vendor/activemodel/lib/active_model/errors.rb +162 -0
  39. data/vendor/activemodel/lib/active_model/lint.rb +91 -0
  40. data/vendor/activemodel/lib/active_model/locale/en.yml +27 -0
  41. data/vendor/activemodel/lib/active_model/naming.rb +45 -0
  42. data/vendor/activemodel/lib/active_model/observing.rb +191 -0
  43. data/vendor/activemodel/lib/active_model/railtie.rb +2 -0
  44. data/vendor/activemodel/lib/active_model/serialization.rb +30 -0
  45. data/vendor/activemodel/lib/active_model/serializers/json.rb +96 -0
  46. data/vendor/activemodel/lib/active_model/serializers/xml.rb +204 -0
  47. data/vendor/activemodel/lib/active_model/state_machine/event.rb +62 -0
  48. data/vendor/activemodel/lib/active_model/state_machine/machine.rb +75 -0
  49. data/vendor/activemodel/lib/active_model/state_machine/state.rb +47 -0
  50. data/vendor/activemodel/lib/active_model/state_machine/state_transition.rb +40 -0
  51. data/vendor/activemodel/lib/active_model/state_machine.rb +70 -0
  52. data/vendor/activemodel/lib/active_model/test_case.rb +18 -0
  53. data/vendor/activemodel/lib/active_model/translation.rb +44 -0
  54. data/vendor/activemodel/lib/active_model/validations/acceptance.rb +55 -0
  55. data/vendor/activemodel/lib/active_model/validations/confirmation.rb +47 -0
  56. data/vendor/activemodel/lib/active_model/validations/exclusion.rb +42 -0
  57. data/vendor/activemodel/lib/active_model/validations/format.rb +64 -0
  58. data/vendor/activemodel/lib/active_model/validations/inclusion.rb +42 -0
  59. data/vendor/activemodel/lib/active_model/validations/length.rb +117 -0
  60. data/vendor/activemodel/lib/active_model/validations/numericality.rb +111 -0
  61. data/vendor/activemodel/lib/active_model/validations/presence.rb +42 -0
  62. data/vendor/activemodel/lib/active_model/validations/with.rb +59 -0
  63. data/vendor/activemodel/lib/active_model/validations.rb +120 -0
  64. data/vendor/activemodel/lib/active_model/validator.rb +110 -0
  65. data/vendor/activemodel/lib/active_model/version.rb +9 -0
  66. data/vendor/activemodel/lib/active_model.rb +61 -0
  67. data/vendor/activemodel/test/cases/attribute_methods_test.rb +46 -0
  68. data/vendor/activemodel/test/cases/callbacks_test.rb +70 -0
  69. data/vendor/activemodel/test/cases/helper.rb +23 -0
  70. data/vendor/activemodel/test/cases/lint_test.rb +28 -0
  71. data/vendor/activemodel/test/cases/naming_test.rb +28 -0
  72. data/vendor/activemodel/test/cases/observing_test.rb +133 -0
  73. data/vendor/activemodel/test/cases/serializeration/json_serialization_test.rb +83 -0
  74. data/vendor/activemodel/test/cases/serializeration/xml_serialization_test.rb +110 -0
  75. data/vendor/activemodel/test/cases/state_machine/event_test.rb +49 -0
  76. data/vendor/activemodel/test/cases/state_machine/machine_test.rb +43 -0
  77. data/vendor/activemodel/test/cases/state_machine/state_test.rb +72 -0
  78. data/vendor/activemodel/test/cases/state_machine/state_transition_test.rb +84 -0
  79. data/vendor/activemodel/test/cases/state_machine_test.rb +312 -0
  80. data/vendor/activemodel/test/cases/tests_database.rb +37 -0
  81. data/vendor/activemodel/test/cases/translation_test.rb +45 -0
  82. data/vendor/activemodel/test/cases/validations/acceptance_validation_test.rb +71 -0
  83. data/vendor/activemodel/test/cases/validations/conditional_validation_test.rb +141 -0
  84. data/vendor/activemodel/test/cases/validations/confirmation_validation_test.rb +58 -0
  85. data/vendor/activemodel/test/cases/validations/exclusion_validation_test.rb +47 -0
  86. data/vendor/activemodel/test/cases/validations/format_validation_test.rb +118 -0
  87. data/vendor/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +175 -0
  88. data/vendor/activemodel/test/cases/validations/i18n_validation_test.rb +527 -0
  89. data/vendor/activemodel/test/cases/validations/inclusion_validation_test.rb +71 -0
  90. data/vendor/activemodel/test/cases/validations/length_validation_test.rb +437 -0
  91. data/vendor/activemodel/test/cases/validations/numericality_validation_test.rb +180 -0
  92. data/vendor/activemodel/test/cases/validations/presence_validation_test.rb +70 -0
  93. data/vendor/activemodel/test/cases/validations/with_validation_test.rb +166 -0
  94. data/vendor/activemodel/test/cases/validations_test.rb +215 -0
  95. data/vendor/activemodel/test/config.rb +3 -0
  96. data/vendor/activemodel/test/fixtures/topics.yml +41 -0
  97. data/vendor/activemodel/test/models/contact.rb +7 -0
  98. data/vendor/activemodel/test/models/custom_reader.rb +17 -0
  99. data/vendor/activemodel/test/models/developer.rb +6 -0
  100. data/vendor/activemodel/test/models/person.rb +9 -0
  101. data/vendor/activemodel/test/models/reply.rb +34 -0
  102. data/vendor/activemodel/test/models/topic.rb +9 -0
  103. data/vendor/activemodel/test/models/track_back.rb +4 -0
  104. data/vendor/activemodel/test/schema.rb +14 -0
  105. data/vendor/activesupport/lib/active_support/autoload.rb +48 -0
  106. data/vendor/activesupport/lib/active_support/concern.rb +25 -0
  107. data/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb +20 -0
  108. data/vendor/activesupport/lib/active_support/core_ext/object/blank.rb +58 -0
  109. data/vendor/activesupport/lib/active_support/core_ext/object/tap.rb +6 -0
  110. data/vendor/activesupport/lib/active_support/dependency_module.rb +17 -0
  111. data/vendor/activesupport/lib/active_support/i18n.rb +2 -0
  112. data/vendor/activesupport/lib/active_support/locale/en.yml +33 -0
  113. metadata +230 -0
@@ -0,0 +1,136 @@
1
+ module CassandraObject
2
+ class OneToManyAssociation
3
+ def initialize(association_name, owner_class, options)
4
+ @association_name = association_name.to_s
5
+ @owner_class = owner_class
6
+ @target_class_name = options[:class_name] || association_name.to_s.singularize.camelize
7
+ @options = options
8
+
9
+ define_methods!
10
+ end
11
+
12
+ def find(owner, options = {})
13
+ reversed = options.has_key?(:reversed) ? options[:reversed] : reversed?
14
+ cursor = CassandraObject::Cursor.new(target_class, column_family, owner.key.to_s, @association_name, :start_after => options[:start_after], :reversed => reversed)
15
+ cursor.find(options[:limit] || 100)
16
+ end
17
+
18
+ def add(owner, record, set_inverse = true)
19
+ connection.insert(column_family, owner.key.to_s, {@association_name=>{new_key=>record.key.to_s}})
20
+ if has_inverse? && set_inverse
21
+ inverse.set_inverse(record, owner)
22
+ end
23
+ end
24
+
25
+ def new_key
26
+ SimpleUUID::UUID.new
27
+ end
28
+
29
+ def column_family
30
+ @owner_class.to_s + "Relationships"
31
+ end
32
+
33
+ def connection
34
+ @owner_class.connection
35
+ end
36
+
37
+ def target_class
38
+ @target_class ||= @target_class_name.constantize
39
+ end
40
+
41
+ def new_proxy(owner)
42
+ OneToManyAssociationProxy.new(self, owner)
43
+ end
44
+
45
+ def has_inverse?
46
+ @options[:inverse_of]
47
+ end
48
+
49
+ def inverse
50
+ has_inverse? && target_class.associations[@options[:inverse_of]]
51
+ end
52
+
53
+ def set_inverse(owner, record)
54
+ add(owner, record, false)
55
+ end
56
+
57
+ def reversed?
58
+ @options[:reversed] == true
59
+ end
60
+
61
+ def define_methods!
62
+ @owner_class.class_eval <<-eos
63
+ def #{@association_name}
64
+ @_#{@association_name} ||= self.class.associations[:#{@association_name}].new_proxy(self)
65
+ end
66
+ eos
67
+ end
68
+ end
69
+
70
+ class OneToManyAssociationProxy
71
+ def initialize(association, owner)
72
+ @association = association
73
+ @owner = owner
74
+ end
75
+
76
+ include Enumerable
77
+ def each
78
+ target.each do |i|
79
+ yield i
80
+ end
81
+ end
82
+
83
+ def <<(record)
84
+ @association.add(@owner, record)
85
+ if loaded?
86
+ @target << record
87
+ end
88
+ end
89
+
90
+ # Get the targets of this association proxy
91
+ #
92
+ # @param [Hash] options the options with which to modify this query
93
+ # @option options [String] :start_after the key after which to start returning results
94
+ # @option options [Boolean] :reversed (false or association default) return the results in reverse order
95
+ # @option options [Integer] :limit the max number of results to return
96
+ # @return [Array<CassandraObject::Base>] an array of objects of type self#target_class
97
+ #
98
+ def all(options = {})
99
+ @association.find(@owner, options)
100
+ end
101
+
102
+ # Create a record of the associated type with
103
+ # the supplied attributes and add it to this
104
+ # association
105
+ #
106
+ # @param [Hash] attributes the attributes with which to create the object
107
+ # @return [CassandraObject::Base] the newly created object
108
+ #
109
+ def create(attributes)
110
+ returning @association.target_class.create(attributes) do |record|
111
+ if record.valid?
112
+ self << record
113
+ end
114
+ end
115
+ end
116
+
117
+ def create!(attributes)
118
+ returning @association.target_class.create!(attributes) do |record|
119
+ self << record
120
+ end
121
+ end
122
+
123
+ def target
124
+ @target ||= begin
125
+ @loaded = true
126
+ @association.find(@owner)
127
+ end
128
+ end
129
+
130
+ alias to_a target
131
+
132
+ def loaded?
133
+ defined?(@loaded) && @loaded
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,77 @@
1
+ module CassandraObject
2
+ class OneToOneAssociation
3
+ def initialize(association_name, owner_class, options)
4
+ @association_name = association_name.to_s
5
+ @owner_class = owner_class
6
+ @target_class_name = options[:class_name] || association_name.to_s.camelize
7
+ @options = options
8
+
9
+ define_methods!
10
+ end
11
+
12
+ def define_methods!
13
+ @owner_class.class_eval <<-eos
14
+ def #{@association_name}
15
+ @_#{@association_name} ||= self.class.associations[:#{@association_name}].find(self)
16
+ end
17
+
18
+ def #{@association_name}=(record)
19
+ @_#{@association_name} = record
20
+ self.class.associations[:#{@association_name}].set(self, record)
21
+ end
22
+ eos
23
+ end
24
+
25
+ def clear(owner)
26
+ connection.remove(column_family, owner.key.to_s, @association_name)
27
+ end
28
+
29
+ def find(owner)
30
+ if key = connection.get(column_family, owner.key.to_s, @association_name.to_s, :count=>1).values.first
31
+ target_class.get(key)
32
+ else
33
+ nil
34
+ end
35
+ end
36
+
37
+ def set(owner, record, set_inverse = true)
38
+ clear(owner)
39
+ connection.insert(column_family, owner.key.to_s, {@association_name=>{new_key => record.key.to_s}})
40
+ if has_inverse? && set_inverse
41
+ inverse.set_inverse(record, owner)
42
+ end
43
+ end
44
+
45
+ def new_key
46
+ SimpleUUID::UUID.new
47
+ end
48
+
49
+ def set_inverse(owner, record)
50
+ set(owner, record, false)
51
+ end
52
+
53
+ def has_inverse?
54
+ @options[:inverse_of]
55
+ end
56
+
57
+ def inverse
58
+ has_inverse? && target_class.associations[@options[:inverse_of]]
59
+ end
60
+
61
+ def column_family
62
+ @owner_class.to_s + "Relationships"
63
+ end
64
+
65
+ def connection
66
+ @owner_class.connection
67
+ end
68
+
69
+ def target_class
70
+ @target_class ||= @target_class_name.constantize
71
+ end
72
+
73
+ def new_proxy(owner)
74
+ # OneToManyAssociationProxy.new(self, owner)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,35 @@
1
+ require 'cassandra_object/associations/one_to_many'
2
+ require 'cassandra_object/associations/one_to_one'
3
+
4
+ module CassandraObject
5
+ module Associations
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_inheritable_hash :associations
10
+ end
11
+
12
+ module ClassMethods
13
+ def column_family_configuration
14
+ super << {:Name=>"#{name}Relationships", :CompareWith=>"UTF8Type", :CompareSubcolumnsWith=>"TimeUUIDType", :ColumnType=>"Super"}
15
+ end
16
+
17
+ def association(association_name, options= {})
18
+ if options[:unique]
19
+ write_inheritable_hash(:associations, {association_name => OneToOneAssociation.new(association_name, self, options)})
20
+ else
21
+ write_inheritable_hash(:associations, {association_name => OneToManyAssociation.new(association_name, self, options)})
22
+ end
23
+ end
24
+
25
+ def remove(key)
26
+ begin
27
+ connection.remove("#{name}Relationships", key.to_s)
28
+ rescue Cassandra::AccessError => e
29
+ raise e unless e.message =~ /Invalid column family/
30
+ end
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,93 @@
1
+ module CassandraObject
2
+ class Attribute
3
+
4
+ attr_reader :name, :converter, :expected_type
5
+ def initialize(name, owner_class, converter, expected_type, options)
6
+ @name = name.to_s
7
+ @owner_class = owner_class
8
+ @converter = converter
9
+ @expected_type = expected_type
10
+ @options = options
11
+ end
12
+
13
+ def check_value!(value)
14
+ converter.encode(value) unless value.nil? && @options[:allow_nil]
15
+ value
16
+ end
17
+
18
+ def define_methods!
19
+ @owner_class.define_attribute_methods(true)
20
+ end
21
+ end
22
+
23
+ module Attributes
24
+ extend ActiveSupport::Concern
25
+ include ActiveModel::AttributeMethods
26
+
27
+ module ClassMethods
28
+ def attribute(name, options)
29
+
30
+ unless type_mapping = attribute_types[options[:type]]
31
+ type_mapping = { :expected_type => options[:type],
32
+ :converter => options[:converter] }.with_indifferent_access
33
+ end
34
+
35
+ new_attr = Attribute.new(name, self, type_mapping[:converter], type_mapping[:expected_type], options)
36
+ write_inheritable_hash(:model_attributes, {name => new_attr}.with_indifferent_access)
37
+ new_attr.define_methods!
38
+ end
39
+
40
+ def define_attribute_methods(force = false)
41
+ return unless model_attributes
42
+ undefine_attribute_methods if force
43
+ super(model_attributes.keys)
44
+ end
45
+
46
+ def register_attribute_type(name, expected_type, converter)
47
+ attribute_types[name] = { :expected_type => expected_type, :converter => converter }.with_indifferent_access
48
+ end
49
+ end
50
+
51
+ included do
52
+ class_inheritable_hash :model_attributes
53
+ attribute_method_suffix("", "=")
54
+
55
+ cattr_accessor :attribute_types
56
+ self.attribute_types = {}.with_indifferent_access
57
+ end
58
+
59
+ module InstanceMethods
60
+ def write_attribute(name, value)
61
+ if ma = self.class.model_attributes[name]
62
+ @attributes[name.to_s] = ma.check_value!(value)
63
+ else
64
+ raise NoMethodError, "Unknown attribute #{name.inspect}"
65
+ end
66
+ end
67
+
68
+ def read_attribute(name)
69
+ @attributes[name.to_s]
70
+ end
71
+
72
+ def attributes=(attributes)
73
+ attributes.each do |(name, value)|
74
+ send("#{name}=", value)
75
+ end
76
+ end
77
+
78
+ protected
79
+ def attribute_method?(name)
80
+ !!model_attributes[name.to_sym]
81
+ end
82
+
83
+ private
84
+ def attribute(name)
85
+ read_attribute(name.to_sym)
86
+ end
87
+
88
+ def attribute=(name, value)
89
+ write_attribute(name.to_sym, value)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,104 @@
1
+ require 'cassandra'
2
+ require 'set'
3
+ require 'cassandra_object/attributes'
4
+ require 'cassandra_object/dirty'
5
+ require 'cassandra_object/persistence'
6
+
7
+ if CassandraObject.old_active_support
8
+ require 'cassandra_object/legacy_callbacks'
9
+ else
10
+ require 'cassandra_object/callbacks'
11
+ end
12
+
13
+ require 'cassandra_object/validation'
14
+ require 'cassandra_object/identity'
15
+ require 'cassandra_object/indexes'
16
+ require 'cassandra_object/serialization'
17
+ require 'cassandra_object/associations'
18
+ require 'cassandra_object/migrations'
19
+ require 'cassandra_object/cursor'
20
+ require 'cassandra_object/collection'
21
+ require 'cassandra_object/types'
22
+ require 'cassandra_object/mocking'
23
+
24
+ module CassandraObject
25
+ class Base
26
+ class_inheritable_accessor :connection
27
+ class_inheritable_writer :connection_class
28
+
29
+ def self.connection_class
30
+ read_inheritable_attribute(:connection_class) || Cassandra
31
+ end
32
+
33
+ module ConnectionManagement
34
+ def establish_connection(*args)
35
+ self.connection = connection_class.new(*args)
36
+ end
37
+ end
38
+ extend ConnectionManagement
39
+
40
+ module Naming
41
+ def column_family=(column_family)
42
+ @column_family = column_family
43
+ end
44
+
45
+ def column_family
46
+ @column_family || name.pluralize
47
+ end
48
+ end
49
+ extend Naming
50
+
51
+ if CassandraObject.old_active_support
52
+ def self.lookup_ancestors
53
+ super.select { |x| x.model_name.present? }
54
+ end
55
+ end
56
+
57
+ extend ActiveModel::Naming
58
+
59
+ module ConfigurationDumper
60
+ def storage_config_xml
61
+ subclasses.map(&:constantize).map(&:column_family_configuration).flatten.map do |config|
62
+ config_to_xml(config)
63
+ end.join("\n")
64
+ end
65
+
66
+ def config_to_xml(config)
67
+ xml = "<ColumnFamily "
68
+ config.each do |(attr_name, attr_value)|
69
+ xml << " #{attr_name}=\"#{attr_value}\""
70
+ end
71
+ xml << " />"
72
+ xml
73
+ end
74
+ end
75
+ extend ConfigurationDumper
76
+
77
+ include Callbacks
78
+ include Identity
79
+ include Attributes
80
+ include Persistence
81
+ include Indexes
82
+ include Dirty
83
+
84
+ include Validation
85
+ include Associations
86
+
87
+ attr_reader :attributes
88
+ attr_accessor :key
89
+
90
+ include Serialization
91
+ include Migrations
92
+ include Mocking
93
+
94
+ def initialize(attributes={})
95
+ @key = attributes.delete(:key)
96
+ @new_record = true
97
+ @attributes = {}.with_indifferent_access
98
+ self.attributes = attributes
99
+ @schema_version = self.class.current_schema_version
100
+ end
101
+ end
102
+ end
103
+
104
+ require 'cassandra_object/type_registration'
@@ -0,0 +1,10 @@
1
+ module CassandraObject
2
+ module Callbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend ActiveModel::Callbacks
7
+ define_model_callbacks :save, :create, :destroy, :update
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module CassandraObject
2
+ class Collection < Array
3
+ attr_accessor :last_column_name
4
+ def inspect
5
+ "<CassandraObject::Collection##{object_id} contents: #{super} last_column_name: #{last_column_name.inspect}>"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,86 @@
1
+ module CassandraObject
2
+ class Cursor
3
+ def initialize(target_class, column_family, key, super_column, options={})
4
+ @target_class = target_class
5
+ @column_family = column_family
6
+ @key = key.to_s
7
+ @super_column = super_column
8
+ @options = options
9
+ @validators = []
10
+ end
11
+
12
+ def find(number_to_find)
13
+ limit = number_to_find
14
+ objects = CassandraObject::Collection.new
15
+ out_of_keys = false
16
+
17
+ if start_with = @options[:start_after]
18
+ limit += 1
19
+ else
20
+ start_with = nil
21
+ end
22
+
23
+ while objects.size < number_to_find && !out_of_keys
24
+ index_results = connection.get(@column_family, @key, @super_column, :count=>limit,
25
+ :start=>start_with,
26
+ :reversed=>@options[:reversed])
27
+
28
+ out_of_keys = index_results.size < limit
29
+
30
+ if !start_with.blank?
31
+ index_results.delete(start_with)
32
+ end
33
+
34
+ keys = index_results.keys
35
+ values = index_results.values
36
+
37
+ missing_keys = []
38
+
39
+ results = values.empty? ? {} : @target_class.multi_get(values)
40
+ results.each do |(key, result)|
41
+ if result.nil?
42
+ missing_keys << key
43
+ end
44
+ end
45
+
46
+ unless missing_keys.empty?
47
+ @target_class.multi_get(missing_keys, :quorum=>true).each do |(key, result)|
48
+ index_key = index_results.index(key)
49
+ if result.nil?
50
+ remove(index_key)
51
+ results.delete(key)
52
+ else
53
+ results[key] = result
54
+ end
55
+ end
56
+ end
57
+
58
+ results.values.each do |o|
59
+ if @validators.all? {|v| v.call(o) }
60
+ objects << o
61
+ else
62
+ remove(index_results.index(o.key))
63
+ end
64
+ end
65
+
66
+ start_with = objects.last_column_name = keys.last
67
+ limit = (number_to_find - results.size) + 1
68
+
69
+ end
70
+
71
+ return objects
72
+ end
73
+
74
+ def connection
75
+ @target_class.connection
76
+ end
77
+
78
+ def remove(index_key)
79
+ connection.remove(@column_family, @key, @super_column, index_key)
80
+ end
81
+
82
+ def validator(&validator)
83
+ @validators << validator
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,27 @@
1
+ module CassandraObject
2
+ module Dirty
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Dirty
5
+
6
+ module InstanceMethods
7
+ def attributes_changed!(attributes)
8
+ attributes.each do |attr_name|
9
+ attribute_will_change!(attr_name)
10
+ end
11
+ end
12
+
13
+ def save
14
+ super.tap { changed_attributes.clear }
15
+ end
16
+
17
+ def write_attribute(name, value)
18
+ name = name.to_s
19
+ unless attribute_changed?(name)
20
+ old = read_attribute(name)
21
+ changed_attributes[name] = old if old != value
22
+ end
23
+ super
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ module CassandraObject
2
+ module Identity
3
+ # Key factories need to support 3 operations
4
+ class AbstractKeyFactory
5
+ # Next key takes an object and returns the key object it should use.
6
+ # object will be ignored with synthetic keys but could be useful with natural ones
7
+ #
8
+ # @param [CassandraObject::Base] the object that needs a new key
9
+ # @return [CassandraObject::Identity::Key] the key
10
+ #
11
+ def next_key(object)
12
+ raise NotImplementedError, "#{self.class.name}#next_key isn't implemented."
13
+ end
14
+
15
+ # Parse should create a new key object from the 'to_param' format
16
+ #
17
+ # @param [String] the result of calling key.to_param
18
+ # @return [CassandraObject::Identity::Key] the parsed key
19
+ #
20
+ def parse(string)
21
+ raise NotImplementedError, "#{self.class.name}#parse isn't implemented."
22
+ end
23
+
24
+
25
+ # create should create a new key object from the cassandra format.
26
+ #
27
+ # @param [String] the result of calling key.to_s
28
+ # @return [CassandraObject::Identity::Key] the key
29
+ #
30
+ def create(string)
31
+ raise NotImplementedError, "#{self.class.name}#create isn't implemented."
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,20 @@
1
+ module CassandraObject
2
+ module Identity
3
+ # An "interface" that keys need to implement
4
+ #
5
+ # You don't have to include this. But, there's no reason I can think of not to.
6
+ #
7
+ module Key
8
+ # to_param should return a nice-readable representation of the key suitable to chuck into URLs
9
+ #
10
+ # @return [String] a nice readable representation of the key suitable for URLs
11
+ def to_param; end
12
+
13
+ # to_s should return the bytes which will be written to cassandra both as keys and values for associations.
14
+ #
15
+ # @return [String] the bytes which will be written to cassandra as keys
16
+ def to_s; end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,51 @@
1
+ module CassandraObject
2
+ module Identity
3
+ class NaturalKeyFactory < AbstractKeyFactory
4
+ class NaturalKey
5
+ include Key
6
+
7
+ attr_reader :value
8
+
9
+ def initialize(value)
10
+ @value = value
11
+ end
12
+
13
+ def to_s
14
+ value
15
+ end
16
+
17
+ def to_param
18
+ value
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(NaturalKey) && other.value == value
23
+ end
24
+
25
+ def eql?(other)
26
+ other == self
27
+ end
28
+ end
29
+
30
+ attr_reader :attributes, :separator
31
+
32
+ def initialize(options)
33
+ @attributes = [*options[:attributes]]
34
+ @separator = options[:separator] || "-"
35
+ end
36
+
37
+ def next_key(object)
38
+ NaturalKey.new(attributes.map { |a| object.attributes[a.to_s] }.join(separator))
39
+ end
40
+
41
+ def parse(paramized_key)
42
+ NaturalKey.new(paramized_key)
43
+ end
44
+
45
+ def create(paramized_key)
46
+ NaturalKey.new(paramized_key)
47
+ end
48
+ end
49
+ end
50
+ end
51
+