cassandra_object 0.6.0.pre

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