gotime-cassandra_object 0.6.1

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 (54) hide show
  1. data/CHANGELOG +3 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE +13 -0
  5. data/README.markdown +79 -0
  6. data/Rakefile +74 -0
  7. data/TODO +2 -0
  8. data/VERSION +1 -0
  9. data/gotime-cassandra_object.gemspec +134 -0
  10. data/lib/cassandra_object.rb +13 -0
  11. data/lib/cassandra_object/associations.rb +35 -0
  12. data/lib/cassandra_object/associations/one_to_many.rb +136 -0
  13. data/lib/cassandra_object/associations/one_to_one.rb +77 -0
  14. data/lib/cassandra_object/attributes.rb +93 -0
  15. data/lib/cassandra_object/base.rb +97 -0
  16. data/lib/cassandra_object/callbacks.rb +10 -0
  17. data/lib/cassandra_object/collection.rb +8 -0
  18. data/lib/cassandra_object/cursor.rb +86 -0
  19. data/lib/cassandra_object/dirty.rb +27 -0
  20. data/lib/cassandra_object/identity.rb +61 -0
  21. data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
  22. data/lib/cassandra_object/identity/key.rb +20 -0
  23. data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
  24. data/lib/cassandra_object/identity/uuid_key_factory.rb +37 -0
  25. data/lib/cassandra_object/indexes.rb +129 -0
  26. data/lib/cassandra_object/log_subscriber.rb +17 -0
  27. data/lib/cassandra_object/migrations.rb +72 -0
  28. data/lib/cassandra_object/mocking.rb +15 -0
  29. data/lib/cassandra_object/persistence.rb +195 -0
  30. data/lib/cassandra_object/serialization.rb +6 -0
  31. data/lib/cassandra_object/type_registration.rb +7 -0
  32. data/lib/cassandra_object/types.rb +128 -0
  33. data/lib/cassandra_object/validation.rb +49 -0
  34. data/test/basic_scenarios_test.rb +243 -0
  35. data/test/callbacks_test.rb +19 -0
  36. data/test/config/cassandra.in.sh +53 -0
  37. data/test/config/log4j.properties +38 -0
  38. data/test/config/storage-conf.xml +221 -0
  39. data/test/connection.rb +25 -0
  40. data/test/cursor_test.rb +66 -0
  41. data/test/dirty_test.rb +34 -0
  42. data/test/fixture_models.rb +90 -0
  43. data/test/identity/natural_key_factory_test.rb +94 -0
  44. data/test/index_test.rb +69 -0
  45. data/test/legacy/test_helper.rb +18 -0
  46. data/test/migration_test.rb +21 -0
  47. data/test/one_to_many_associations_test.rb +163 -0
  48. data/test/test_case.rb +28 -0
  49. data/test/test_helper.rb +16 -0
  50. data/test/time_test.rb +32 -0
  51. data/test/types_test.rb +252 -0
  52. data/test/validation_test.rb +25 -0
  53. data/test/z_mock_test.rb +36 -0
  54. metadata +243 -0
@@ -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,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,97 @@
1
+ require 'cassandra'
2
+ require 'set'
3
+ require 'cassandra_object/attributes'
4
+ require 'cassandra_object/dirty'
5
+ require 'cassandra_object/persistence'
6
+
7
+ require 'cassandra_object/callbacks'
8
+
9
+ require 'cassandra_object/validation'
10
+ require 'cassandra_object/identity'
11
+ require 'cassandra_object/indexes'
12
+ require 'cassandra_object/serialization'
13
+ require 'cassandra_object/associations'
14
+ #require 'cassandra_object/sets'
15
+ require 'cassandra_object/migrations'
16
+ require 'cassandra_object/cursor'
17
+ require 'cassandra_object/collection'
18
+ require 'cassandra_object/types'
19
+ require 'cassandra_object/mocking'
20
+
21
+ require 'cassandra_object/log_subscriber'
22
+
23
+ module CassandraObject
24
+ class Base
25
+ class_inheritable_accessor :connection
26
+ class_inheritable_writer :connection_class
27
+
28
+ def self.connection_class
29
+ read_inheritable_attribute(:connection_class) || Cassandra
30
+ end
31
+
32
+ module ConnectionManagement
33
+ def establish_connection(*args)
34
+ self.connection = connection_class.new(*args)
35
+ end
36
+ end
37
+ extend ConnectionManagement
38
+
39
+ module Naming
40
+ def column_family=(column_family)
41
+ @column_family = column_family
42
+ end
43
+
44
+ def column_family
45
+ @column_family || name.pluralize
46
+ end
47
+ end
48
+ extend Naming
49
+
50
+ extend ActiveModel::Naming
51
+
52
+ module ConfigurationDumper
53
+ def storage_config_xml
54
+ subclasses.map(&:constantize).map(&:column_family_configuration).flatten.map do |config|
55
+ config_to_xml(config)
56
+ end.join("\n")
57
+ end
58
+
59
+ def config_to_xml(config)
60
+ xml = "<ColumnFamily "
61
+ config.each do |(attr_name, attr_value)|
62
+ xml << " #{attr_name}=\"#{attr_value}\""
63
+ end
64
+ xml << " />"
65
+ xml
66
+ end
67
+ end
68
+ extend ConfigurationDumper
69
+
70
+ include Callbacks
71
+ include Identity
72
+ include Attributes
73
+ include Persistence
74
+ include Indexes
75
+ include Dirty
76
+
77
+ include Validation
78
+ include Associations
79
+
80
+ attr_reader :attributes
81
+ attr_accessor :key
82
+
83
+ include Serialization
84
+ include Migrations
85
+ include Mocking
86
+
87
+ def initialize(attributes={})
88
+ @key = attributes.delete(:key)
89
+ @new_record = true
90
+ @attributes = {}.with_indifferent_access
91
+ self.attributes = attributes
92
+ @schema_version = self.class.current_schema_version
93
+ end
94
+ end
95
+ end
96
+
97
+ 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,61 @@
1
+ require 'cassandra_object/identity/abstract_key_factory'
2
+ require 'cassandra_object/identity/key'
3
+ require 'cassandra_object/identity/uuid_key_factory'
4
+ require 'cassandra_object/identity/natural_key_factory'
5
+
6
+ module CassandraObject
7
+ # Some docs will be needed here but the gist of this is simple. Instead of returning a string, Base#key now returns a key object.
8
+ # There are corresponding key factories which generate them
9
+ module Identity
10
+ extend ActiveSupport::Concern
11
+ module ClassMethods
12
+ # Indicate what kind of key the model will have: uuid or natural
13
+ #
14
+ # @param [:uuid, :natural] the type of key
15
+ # @param the options you want to pass along to the key factory (like :attributes => :name, for a natural key).
16
+ #
17
+ def key(name_or_factory = :uuid, *options)
18
+ @key_factory = case name_or_factory
19
+ when :uuid
20
+ UUIDKeyFactory.new
21
+ when :natural
22
+ NaturalKeyFactory.new(*options)
23
+ else
24
+ name_or_factory
25
+ end
26
+ end
27
+
28
+ def next_key(object = nil)
29
+ @key_factory.next_key(object).tap do |key|
30
+ raise "Keys may not be nil" if key.nil?
31
+ end
32
+ end
33
+
34
+ def parse_key(string)
35
+ @key_factory.parse(string)
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+
41
+ def ==(comparison_object)
42
+ comparison_object.equal?(self) ||
43
+ (comparison_object.instance_of?(self.class) &&
44
+ comparison_object.key == key &&
45
+ !comparison_object.new_record?)
46
+ end
47
+
48
+ def eql?(comparison_object)
49
+ self == (comparison_object)
50
+ end
51
+
52
+ def hash
53
+ key.to_s.hash
54
+ end
55
+
56
+ def to_param
57
+ key.to_param
58
+ end
59
+ end
60
+ end
61
+ end