gotime-cassandra_object 0.6.1

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