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,37 @@
1
+ module CassandraObject
2
+ module Identity
3
+ # Key factories need to support 3 operations
4
+ class UUIDKeyFactory < AbstractKeyFactory
5
+ class UUID < SimpleUUID::UUID
6
+ include Key
7
+
8
+ def to_param
9
+ to_guid
10
+ end
11
+
12
+ def to_s
13
+ # FIXME - this should probably write the raw bytes
14
+ # but it's very hard to debug without this for now.
15
+ to_guid
16
+ end
17
+ end
18
+
19
+ # Next key takes an object and returns the key object it should use.
20
+ # object will be ignored with synthetic keys but could be useful with natural ones
21
+ def next_key(object)
22
+ UUID.new
23
+ end
24
+
25
+ # Parse should create a new key object from the 'to_param' format
26
+ def parse(string)
27
+ UUID.new(string)
28
+ end
29
+
30
+ # create should create a new key object from the cassandra format.
31
+ def create(string)
32
+ UUID.new(string)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -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
+ returning(@key_factory.next_key(object)) 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
@@ -0,0 +1,129 @@
1
+ module CassandraObject
2
+ module Indexes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_inheritable_accessor :indexes
7
+ end
8
+
9
+ class UniqueIndex
10
+ def initialize(attribute_name, model_class, options)
11
+ @attribute_name = attribute_name
12
+ @model_class = model_class
13
+ end
14
+
15
+ def find(attribute_value)
16
+ # first find the key value
17
+ key = @model_class.connection.get(column_family, attribute_value.to_s, 'key')
18
+ # then pass to get
19
+ if key
20
+ @model_class.get(key.to_s)
21
+ else
22
+ @model_class.connection.remove(column_family, attribute_value.to_s)
23
+ nil
24
+ end
25
+ end
26
+
27
+ def write(record)
28
+ @model_class.connection.insert(column_family, record.send(@attribute_name).to_s, {'key'=>record.key.to_s})
29
+ end
30
+
31
+ def remove(record)
32
+ @model_class.connection.remove(column_family, record.send(@attribute_name).to_s)
33
+ end
34
+
35
+ def column_family
36
+ @model_class.column_family + "By" + @attribute_name.to_s.camelize
37
+ end
38
+
39
+ def column_family_configuration
40
+ {:Name=>column_family, :CompareWith=>"UTF8Type"}
41
+ end
42
+ end
43
+
44
+ class Index
45
+ def initialize(attribute_name, model_class, options)
46
+ @attribute_name = attribute_name
47
+ @model_class = model_class
48
+ @reversed = options[:reversed]
49
+ end
50
+
51
+ def find(attribute_value, options = {})
52
+ cursor = CassandraObject::Cursor.new(@model_class, column_family, attribute_value.to_s, @attribute_name.to_s, :start_after=>options[:start_after], :reversed=>@reversed)
53
+ cursor.validator do |object|
54
+ object.send(@attribute_name) == attribute_value
55
+ end
56
+ cursor.find(options[:limit] || 100)
57
+ end
58
+
59
+ def write(record)
60
+ @model_class.connection.insert(column_family, record.send(@attribute_name).to_s, {@attribute_name.to_s=>{new_key=>record.key.to_s}})
61
+ end
62
+
63
+ def remove(record)
64
+ end
65
+
66
+ def column_family
67
+ @model_class.column_family + "By" + @attribute_name.to_s.camelize
68
+ end
69
+
70
+ def new_key
71
+ SimpleUUID::UUID.new
72
+ end
73
+
74
+ def column_family_configuration
75
+ {:Name=>column_family, :CompareWith=>"UTF8Type", :ColumnType=>"Super", :CompareSubcolumnsWith=>"TimeUUIDType"}
76
+ end
77
+
78
+ end
79
+
80
+ module ClassMethods
81
+ def column_family_configuration
82
+ if indexes
83
+ super + indexes.values.map(&:column_family_configuration)
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ def index(attribute_name, options = {})
90
+ self.indexes ||= {}.with_indifferent_access
91
+ if options.delete(:unique)
92
+ self.indexes[attribute_name] = UniqueIndex.new(attribute_name, self, options)
93
+ class_eval <<-eom
94
+ def self.find_by_#{attribute_name}(value)
95
+ indexes[:#{attribute_name}].find(value)
96
+ end
97
+
98
+ after_save do |record|
99
+ self.indexes[:#{attribute_name}].write(record)
100
+ true
101
+ end
102
+
103
+ after_destroy do |record|
104
+ record.class.indexes[:#{attribute_name}].remove(record)
105
+ true
106
+ end
107
+ eom
108
+ else
109
+ self.indexes[attribute_name] = Index.new(attribute_name, self, options)
110
+ class_eval <<-eom
111
+ def self.find_all_by_#{attribute_name}(value, options = {})
112
+ self.indexes[:#{attribute_name}].find(value, options)
113
+ end
114
+
115
+ after_save do |record|
116
+ record.class.indexes[:#{attribute_name}].write(record)
117
+ true
118
+ end
119
+
120
+ after_destroy do |record|
121
+ record.class.indexes[:#{attribute_name}].remove(record)
122
+ true
123
+ end
124
+ eom
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,33 @@
1
+ module CassandraObject
2
+ module Callbacks
3
+ extend ActiveSupport::Concern
4
+ include ActiveSupport::Callbacks
5
+
6
+ included do
7
+ define_model_callbacks :save, :create, :destroy, :update
8
+ end
9
+
10
+ module ClassMethods
11
+ def define_model_callbacks(*callbacks)
12
+ callbacks.each do |callback|
13
+ define_callbacks "before_#{callback}"
14
+ define_callbacks "after_#{callback}"
15
+ end
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ def run_callbacks(callback)
21
+ if block_given?
22
+ unless false == super("before_#{callback}")
23
+ yield.tap do
24
+ super("after_#{callback}")
25
+ end
26
+ end
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,72 @@
1
+ module CassandraObject
2
+ module Migrations
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ class_inheritable_array :migrations
6
+ class_inheritable_accessor :current_schema_version
7
+ self.current_schema_version = 0
8
+ end
9
+
10
+ class Migration
11
+ attr_reader :version
12
+ def initialize(version, block)
13
+ @version = version
14
+ @block = block
15
+ end
16
+
17
+ def run(attrs)
18
+ @block.call(attrs)
19
+ end
20
+ end
21
+
22
+ class MigrationNotFoundError < StandardError
23
+ def initialize(record_version, migrations)
24
+ super("Cannot migrate a record from #{record_version.inspect}. Migrations exist for #{migrations.map(&:version)}")
25
+ end
26
+ end
27
+
28
+ module InstanceMethods
29
+ def schema_version
30
+ Integer(@schema_version || self.class.current_schema_version)
31
+ end
32
+ end
33
+
34
+ module ClassMethods
35
+ def migrate(version, &blk)
36
+ write_inheritable_array(:migrations, [Migration.new(version, blk)])
37
+
38
+ if version > self.current_schema_version
39
+ self.current_schema_version = version
40
+ end
41
+ end
42
+
43
+ def instantiate(key, attributes)
44
+ version = attributes.delete('schema_version')
45
+ original_attributes = attributes.dup
46
+ if version == current_schema_version
47
+ return super(key, attributes)
48
+ end
49
+
50
+ versions_to_migrate = ((version.to_i + 1)..current_schema_version)
51
+
52
+ migrations_to_run = versions_to_migrate.map do |v|
53
+ migrations.find {|m| m.version == v}
54
+ end
55
+
56
+ if migrations_to_run.any?(&:nil?)
57
+ raise MigrationNotFoundError.new(version, migrations)
58
+ end
59
+
60
+ migrations_to_run.inject(attributes) do |attrs, migration|
61
+ migration.run(attrs)
62
+ @schema_version = migration.version.to_s
63
+ attrs
64
+ end
65
+
66
+ returning super(key, attributes) do |record|
67
+ record.attributes_changed!(original_attributes.diff(attributes).keys)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,15 @@
1
+ require 'cassandra/mock'
2
+ module CassandraObject
3
+ module Mocking
4
+ extend ActiveSupport::Concern
5
+ module ClassMethods
6
+ def use_mock!(really=true)
7
+ if really
8
+ self.connection_class = Cassandra::Mock
9
+ else
10
+ self.connection_class = Cassandra
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,193 @@
1
+ module CassandraObject
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ class_inheritable_writer :write_consistency
6
+ class_inheritable_writer :read_consistency
7
+ end
8
+
9
+ VALID_READ_CONSISTENCY_LEVELS = [:one, :quorum, :all]
10
+ VALID_WRITE_CONSISTENCY_LEVELS = VALID_READ_CONSISTENCY_LEVELS + [:zero]
11
+
12
+ module ClassMethods
13
+ def consistency_levels(levels)
14
+ if levels.has_key?(:write)
15
+ unless valid_write_consistency_level?(levels[:write])
16
+ raise ArgumentError, "Invalid write consistency level. Valid levels are: #{VALID_WRITE_CONSISTENCY_LEVELS.inspect}. You gave me #{levels[:write].inspect}"
17
+ end
18
+ self.write_consistency = levels[:write]
19
+ end
20
+
21
+ if levels.has_key?(:read)
22
+ unless valid_read_consistency_level?(levels[:read])
23
+ raise ArgumentError, "Invalid read consistency level. Valid levels are #{VALID_READ_CONSISTENCY_LEVELS.inspect}. You gave me #{levels[:write].inspect}"
24
+ end
25
+ self.read_consistency = levels[:read]
26
+ end
27
+ end
28
+
29
+ def write_consistency
30
+ read_inheritable_attribute(:write_consistency) || :quorum
31
+ end
32
+
33
+ def read_consistency
34
+ read_inheritable_attribute(:read_consistency) || :quorum
35
+ end
36
+
37
+ def get(key, options = {})
38
+ multi_get([key], options).values.first
39
+ end
40
+
41
+ def multi_get(keys, options = {})
42
+ options = {:consistency => self.read_consistency, :limit => 100}.merge(options)
43
+ unless valid_read_consistency_level?(options[:consistency])
44
+ raise ArgumentError, "Invalid read consistency level: '#{options[:consistency]}'. Valid options are [:quorum, :one]"
45
+ end
46
+
47
+ attribute_results = connection.multi_get(column_family, keys.map(&:to_s), :count=>options[:limit], :consistency=>consistency_for_thrift(options[:consistency]))
48
+
49
+ attribute_results.inject(ActiveSupport::OrderedHash.new) do |memo, (key, attributes)|
50
+ if attributes.empty?
51
+ memo[key] = nil
52
+ else
53
+ memo[parse_key(key)] = instantiate(key, attributes)
54
+ end
55
+ memo
56
+ end
57
+ end
58
+
59
+ def remove(key)
60
+ connection.remove(column_family, key.to_s, :consistency => write_consistency_for_thrift)
61
+ end
62
+
63
+ def all(keyrange = ''..'', options = {})
64
+ results = connection.get_range(column_family, :start => keyrange.first, :finish => keyrange.last, :count=>(options[:limit] || 100))
65
+ keys = results.map(&:key)
66
+ keys.map {|key| get(key) }
67
+ end
68
+
69
+ def first(keyrange = ''..'', options = {})
70
+ all(keyrange, options.merge(:limit=>1)).first
71
+ end
72
+
73
+ def create(attributes)
74
+ returning new(attributes) do |object|
75
+ object.save
76
+ end
77
+ end
78
+
79
+ def write(key, attributes, schema_version)
80
+ returning(key) do |key|
81
+ connection.insert(column_family, key.to_s, encode_columns_hash(attributes, schema_version), :consistency => write_consistency_for_thrift)
82
+ end
83
+ end
84
+
85
+ def instantiate(key, attributes)
86
+ # remove any attributes we don't know about. we would do this earlier, but we want to make such
87
+ # attributes available to migrations
88
+ attributes.delete_if{|k,_| !model_attributes.keys.include?(k)}
89
+ returning allocate do |object|
90
+ object.instance_variable_set("@schema_version", attributes.delete('schema_version'))
91
+ object.instance_variable_set("@key", parse_key(key))
92
+ object.instance_variable_set("@attributes", decode_columns_hash(attributes).with_indifferent_access)
93
+ end
94
+ end
95
+
96
+ def encode_columns_hash(attributes, schema_version)
97
+ attributes.inject(Hash.new) do |memo, (column_name, value)|
98
+ memo[column_name.to_s] = model_attributes[column_name].converter.encode(value)
99
+ memo
100
+ end.merge({"schema_version" => schema_version.to_s})
101
+ end
102
+
103
+ def decode_columns_hash(attributes)
104
+ attributes.inject(Hash.new) do |memo, (column_name, value)|
105
+ memo[column_name.to_s] = model_attributes[column_name].converter.decode(value)
106
+ memo
107
+ end
108
+ end
109
+
110
+ def column_family_configuration
111
+ [{:Name=>column_family, :CompareWith=>"UTF8Type"}]
112
+ end
113
+
114
+ protected
115
+ def valid_read_consistency_level?(level)
116
+ !!VALID_READ_CONSISTENCY_LEVELS.include?(level)
117
+ end
118
+
119
+ def valid_write_consistency_level?(level)
120
+ !!VALID_WRITE_CONSISTENCY_LEVELS.include?(level)
121
+ end
122
+
123
+ def write_consistency_for_thrift
124
+ consistency_for_thrift(write_consistency)
125
+ end
126
+
127
+ def read_consistency_for_thrift
128
+ consistency_for_thrift(read_consistency)
129
+ end
130
+
131
+ def consistency_for_thrift(consistency)
132
+ {
133
+ :zero => Cassandra::Consistency::ZERO,
134
+ :one => Cassandra::Consistency::ONE,
135
+ :quorum => Cassandra::Consistency::QUORUM,
136
+ :all => Cassandra::Consistency::ALL
137
+ }[consistency]
138
+ end
139
+ end
140
+
141
+ module InstanceMethods
142
+ def save
143
+ run_callbacks :save do
144
+ create_or_update
145
+ end
146
+ end
147
+
148
+ def create_or_update
149
+ if new_record?
150
+ create
151
+ else
152
+ update
153
+ end
154
+ true
155
+ end
156
+
157
+ def create
158
+ run_callbacks :create do
159
+ @key ||= self.class.next_key(self)
160
+ _write
161
+ @new_record = false
162
+ true
163
+ end
164
+ end
165
+
166
+ def update
167
+ run_callbacks :update do
168
+ _write
169
+ end
170
+ end
171
+
172
+ def _write
173
+ changed_attributes = changed.inject({}) { |h, n| h[n] = read_attribute(n); h }
174
+ self.class.write(key, changed_attributes, schema_version)
175
+ end
176
+
177
+ def new_record?
178
+ @new_record || false
179
+ end
180
+
181
+ def destroy
182
+ run_callbacks :destroy do
183
+ self.class.remove(key)
184
+ end
185
+ end
186
+
187
+ def reload
188
+ self.class.get(self.key)
189
+ end
190
+
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,6 @@
1
+ module CassandraObject
2
+ module Serialization
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Serializers::JSON
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ CassandraObject::Base.register_attribute_type(:integer, Integer, CassandraObject::IntegerType)
2
+ CassandraObject::Base.register_attribute_type(:float, Float, CassandraObject::FloatType)
3
+ CassandraObject::Base.register_attribute_type(:date, Date, CassandraObject::DateType)
4
+ CassandraObject::Base.register_attribute_type(:time, Time, CassandraObject::TimeType)
5
+ CassandraObject::Base.register_attribute_type(:time_with_zone, ActiveSupport::TimeWithZone, CassandraObject::TimeWithZoneType)
6
+ CassandraObject::Base.register_attribute_type(:string, String, CassandraObject::StringType)
7
+ CassandraObject::Base.register_attribute_type(:hash, Hash, CassandraObject::HashType)
@@ -0,0 +1,128 @@
1
+ module CassandraObject
2
+ module IntegerType
3
+ REGEX = /\A[-+]?\d+\Z/
4
+ def encode(int)
5
+ return '' if int.nil?
6
+ raise ArgumentError.new("#{self} requires an Integer. You passed #{int.inspect}") unless int.kind_of?(Integer)
7
+ int.to_s
8
+ end
9
+ module_function :encode
10
+
11
+ def decode(str)
12
+ return nil if str.empty?
13
+ raise ArgumentError.new("#{str} isn't a String that looks like a Integer") unless str.kind_of?(String) && str.match(REGEX)
14
+ str.to_i
15
+ end
16
+ module_function :decode
17
+ end
18
+
19
+ module FloatType
20
+ REGEX = /\A[-+]?\d+(\.\d+)\Z/
21
+ def encode(float)
22
+ return '' if float.nil?
23
+ raise ArgumentError.new("#{self} requires a Float") unless float.kind_of?(Float)
24
+ float.to_s
25
+ end
26
+ module_function :encode
27
+
28
+ def decode(str)
29
+ return nil if str == ''
30
+ raise ArgumentError.new("#{str} isn't a String that looks like a Float") unless str.kind_of?(String) && str.match(REGEX)
31
+ str.to_f
32
+ end
33
+ module_function :decode
34
+ end
35
+
36
+ module DateType
37
+ FORMAT = '%Y-%m-%d'
38
+ REGEX = /\A\d{4}-\d{2}-\d{2}\Z/
39
+ def encode(date)
40
+ raise ArgumentError.new("#{self} requires a Date") unless date.kind_of?(Date)
41
+ date.strftime(FORMAT)
42
+ end
43
+ module_function :encode
44
+
45
+ def decode(str)
46
+ raise ArgumentError.new("#{str} isn't a String that looks like a Date") unless str.kind_of?(String) && str.match(REGEX)
47
+ Date.strptime(str, FORMAT)
48
+ end
49
+ module_function :decode
50
+ end
51
+
52
+ module TimeType
53
+ # lifted from the implementation of Time.xmlschema and simplified
54
+ REGEX = /\A\s*
55
+ (-?\d+)-(\d\d)-(\d\d)
56
+ T
57
+ (\d\d):(\d\d):(\d\d)
58
+ (\.\d*)?
59
+ (Z|[+-]\d\d:\d\d)?
60
+ \s*\z/ix
61
+
62
+ def encode(time)
63
+ raise ArgumentError.new("#{self} requires a Time") unless time.kind_of?(Time)
64
+ time.xmlschema(6)
65
+ end
66
+ module_function :encode
67
+
68
+ def decode(str)
69
+ raise ArgumentError.new("#{str} isn't a String that looks like a Time") unless str.kind_of?(String) && str.match(REGEX)
70
+ Time.xmlschema(str)
71
+ end
72
+ module_function :decode
73
+ end
74
+
75
+ module TimeWithZoneType
76
+ def encode(time)
77
+ TimeType.encode(time.utc)
78
+ end
79
+ module_function :encode
80
+
81
+ def decode(str)
82
+ TimeType.decode(str).in_time_zone
83
+ end
84
+ module_function :decode
85
+ end
86
+
87
+ module StringType
88
+ def encode(str)
89
+ raise ArgumentError.new("#{self} requires a String") unless str.kind_of?(String)
90
+ str
91
+ end
92
+ module_function :encode
93
+
94
+ def decode(str)
95
+ str
96
+ end
97
+ module_function :decode
98
+ end
99
+
100
+ module HashType
101
+ def encode(hash)
102
+ raise ArgumentError.new("#{self} requires a Hash") unless hash.kind_of?(Hash)
103
+ ActiveSupport::JSON.encode(hash)
104
+ end
105
+ module_function :encode
106
+
107
+ def decode(str)
108
+ ActiveSupport::JSON.decode(str)
109
+ end
110
+ module_function :decode
111
+ end
112
+
113
+ module BooleanType
114
+ ALLOWED = [true, false, nil]
115
+ def encode(bool)
116
+ unless ALLOWED.any?{ |a| bool == a }
117
+ raise ArgumentError.new("#{self} requires a Boolean or nil")
118
+ end
119
+ bool ? '1' : '0'
120
+ end
121
+ module_function :encode
122
+
123
+ def decode(bool)
124
+ bool == '1'
125
+ end
126
+ module_function :decode
127
+ end
128
+ end