cequel 0.0.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/lib/cequel.rb +16 -0
  2. data/lib/cequel/batch.rb +58 -0
  3. data/lib/cequel/cql_row_specification.rb +22 -0
  4. data/lib/cequel/data_set.rb +346 -0
  5. data/lib/cequel/errors.rb +4 -0
  6. data/lib/cequel/keyspace.rb +106 -0
  7. data/lib/cequel/model.rb +95 -0
  8. data/lib/cequel/model/associations.rb +120 -0
  9. data/lib/cequel/model/callbacks.rb +32 -0
  10. data/lib/cequel/model/class_internals.rb +48 -0
  11. data/lib/cequel/model/column.rb +20 -0
  12. data/lib/cequel/model/dictionary.rb +202 -0
  13. data/lib/cequel/model/dirty.rb +53 -0
  14. data/lib/cequel/model/dynamic.rb +31 -0
  15. data/lib/cequel/model/errors.rb +13 -0
  16. data/lib/cequel/model/inheritable.rb +48 -0
  17. data/lib/cequel/model/instance_internals.rb +23 -0
  18. data/lib/cequel/model/local_association.rb +42 -0
  19. data/lib/cequel/model/magic.rb +79 -0
  20. data/lib/cequel/model/mass_assignment_security.rb +21 -0
  21. data/lib/cequel/model/naming.rb +17 -0
  22. data/lib/cequel/model/observer.rb +42 -0
  23. data/lib/cequel/model/persistence.rb +173 -0
  24. data/lib/cequel/model/properties.rb +143 -0
  25. data/lib/cequel/model/railtie.rb +33 -0
  26. data/lib/cequel/model/remote_association.rb +40 -0
  27. data/lib/cequel/model/scope.rb +362 -0
  28. data/lib/cequel/model/scoped.rb +50 -0
  29. data/lib/cequel/model/subclass_internals.rb +45 -0
  30. data/lib/cequel/model/timestamps.rb +52 -0
  31. data/lib/cequel/model/translation.rb +17 -0
  32. data/lib/cequel/model/validations.rb +50 -0
  33. data/lib/cequel/new_relic_instrumentation.rb +22 -0
  34. data/lib/cequel/row_specification.rb +63 -0
  35. data/lib/cequel/statement.rb +23 -0
  36. data/lib/cequel/version.rb +3 -0
  37. data/spec/environment.rb +3 -0
  38. data/spec/examples/data_set_spec.rb +382 -0
  39. data/spec/examples/keyspace_spec.rb +63 -0
  40. data/spec/examples/model/associations_spec.rb +109 -0
  41. data/spec/examples/model/callbacks_spec.rb +79 -0
  42. data/spec/examples/model/dictionary_spec.rb +413 -0
  43. data/spec/examples/model/dirty_spec.rb +39 -0
  44. data/spec/examples/model/dynamic_spec.rb +41 -0
  45. data/spec/examples/model/inheritable_spec.rb +45 -0
  46. data/spec/examples/model/magic_spec.rb +199 -0
  47. data/spec/examples/model/mass_assignment_security_spec.rb +13 -0
  48. data/spec/examples/model/naming_spec.rb +9 -0
  49. data/spec/examples/model/observer_spec.rb +86 -0
  50. data/spec/examples/model/persistence_spec.rb +201 -0
  51. data/spec/examples/model/properties_spec.rb +81 -0
  52. data/spec/examples/model/scope_spec.rb +677 -0
  53. data/spec/examples/model/serialization_spec.rb +20 -0
  54. data/spec/examples/model/spec_helper.rb +12 -0
  55. data/spec/examples/model/timestamps_spec.rb +52 -0
  56. data/spec/examples/model/translation_spec.rb +23 -0
  57. data/spec/examples/model/validations_spec.rb +86 -0
  58. data/spec/examples/spec_helper.rb +9 -0
  59. data/spec/models/asset.rb +21 -0
  60. data/spec/models/asset_observer.rb +5 -0
  61. data/spec/models/blog.rb +14 -0
  62. data/spec/models/blog_posts.rb +6 -0
  63. data/spec/models/category.rb +9 -0
  64. data/spec/models/comment.rb +12 -0
  65. data/spec/models/photo.rb +5 -0
  66. data/spec/models/post.rb +88 -0
  67. data/spec/models/post_comments.rb +14 -0
  68. data/spec/models/post_observer.rb +43 -0
  69. data/spec/support/helpers.rb +26 -0
  70. data/spec/support/result_stub.rb +27 -0
  71. metadata +125 -23
@@ -0,0 +1,53 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module Dirty
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include ActiveModel::Dirty
11
+ include ChangedAttributesWithIndifferentAccess
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ def column(name, type, options = {})
17
+ define_attribute_method(name)
18
+ super
19
+ end
20
+
21
+ end
22
+
23
+ def save
24
+ super.tap do
25
+ @previously_changed = changes
26
+ changed_attributes.clear
27
+ end
28
+ end
29
+
30
+ def _hydrate(row)
31
+ super.tap { changed_attributes.clear }
32
+ end
33
+
34
+ private
35
+
36
+ def write_attribute(name, value)
37
+ attribute_will_change!(name) if value != read_attribute(name)
38
+ super
39
+ end
40
+
41
+ end
42
+
43
+ module ChangedAttributesWithIndifferentAccess
44
+
45
+ def changed_attributes
46
+ @changed_attributes ||= HashWithIndifferentAccess.new
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,31 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module Dynamic
6
+
7
+ def [](column)
8
+ read_attribute(column)
9
+ end
10
+
11
+ def []=(column, value)
12
+ write_attribute(column, value)
13
+ end
14
+
15
+ private
16
+
17
+ def attribute_change(attr)
18
+ if attribute_changed?(attr)
19
+ if respond_to_without_attributes?(attr)
20
+ super
21
+ else
22
+ [changed_attributes[attr], self[attr]]
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,13 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ Error = Class.new(Cequel::Error)
6
+ RecordNotFound = Class.new(Error)
7
+ RecordInvalid = Class.new(Error)
8
+ MissingKey = Class.new(Error)
9
+ InvalidQuery = Class.new(Error)
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,48 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module Inheritable
6
+
7
+ module SubclassMethods
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ module ClassMethods
12
+
13
+ def all
14
+ super.where(@_cequel.type_column.name => name)
15
+ end
16
+
17
+ end
18
+
19
+ def initialize(*args, &block)
20
+ super
21
+ __send__("#{self.class.type_column.name}=", self.class.name)
22
+ end
23
+
24
+ end
25
+
26
+ def inherited(subclass)
27
+ super
28
+ unless @_cequel.type_column
29
+ raise ArgumentError,
30
+ "Can't subclass model class that does not define a type column"
31
+ end
32
+ subclass._cequel = SubclassInternals.new(subclass, @_cequel)
33
+ subclass.module_eval { include(SubclassMethods) }
34
+ end
35
+
36
+ def base_class
37
+ @_cequel.base_class
38
+ end
39
+
40
+ protected
41
+
42
+ attr_writer :_cequel
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,23 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ #
6
+ # @private
7
+ #
8
+ class InstanceInternals
9
+
10
+ attr_accessor :key, :attributes, :persisted
11
+ attr_reader :associations
12
+
13
+ def initialize(instance)
14
+ @instance = instance
15
+ @attributes = ActiveSupport::HashWithIndifferentAccess.new
16
+ @associations = {}
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,42 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ class LocalAssociation
6
+
7
+ attr_reader :clazz, :name
8
+
9
+ def initialize(name, owning_class, options)
10
+ @name, @owning_class = name, owning_class
11
+ @class_name = options[:class_name] || name.to_s.classify.to_sym
12
+ @foreign_key_name = options[:foreign_key]
13
+ end
14
+
15
+ def primary_key
16
+ @primary_key ||= clazz.key_column
17
+ end
18
+
19
+ def primary_key_name
20
+ @primary_key_name ||= primary_key.name
21
+ end
22
+
23
+ def foreign_key_name
24
+ @foreign_key_name ||= :"#{name}_id"
25
+ end
26
+
27
+ def scope(instance)
28
+ clazz.where(primary_key_name => instance.__send__(foreign_key_name))
29
+ end
30
+
31
+ def clazz
32
+ @clazz ||= @class_name.to_s.constantize
33
+ end
34
+
35
+ def dependent
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,79 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module Magic
6
+
7
+ FIND_BY_PATTERN = /^find_by_(\w+)$/
8
+ FIND_ALL_BY_PATTERN = /^find_all_by_(\w+)$/
9
+ FIND_OR_CREATE_BY_PATTERN = /^find_or_create_by_(\w+)$/
10
+ FIND_OR_INITIALIZE_BY_PATTERN = /^find_or_initialize_by_(\w+)$/
11
+
12
+ def self.scope(scope, columns_string, args)
13
+ scope.where(extract_row_specifications(columns_string, args))
14
+ end
15
+
16
+ def self.find_or_create_by(scope, columns_string, args, &block)
17
+ find_or_initialize_by(scope, columns_string, args, &block).tap do |instance|
18
+ instance.save unless instance.persisted?
19
+ end
20
+ end
21
+
22
+ def self.find_or_initialize_by(scope, columns_string, args, &block)
23
+ row_specifications = extract_row_specifications(columns_string, args)
24
+ instance = scope.where(row_specifications).first
25
+ if instance.nil?
26
+ if args.length == 1 && args.first.is_a?(Hash)
27
+ attributes = args.first
28
+ else
29
+ attributes = row_specifications
30
+ end
31
+ instance = scope.new(attributes, &block)
32
+ end
33
+ instance
34
+ end
35
+
36
+ def self.extract_row_specifications(columns_string, args)
37
+ columns = columns_string.split('_and_').map { |column| column.to_sym }
38
+ if args.length == 1 && args.first.is_a?(Hash)
39
+ args.first.symbolize_keys.slice(*columns)
40
+ else
41
+ if columns.length != args.length
42
+ raise ArgumentError,
43
+ "wrong number of arguments(#{args.length} for #{columns.length})"
44
+ end
45
+ Hash[columns.zip(args)]
46
+ end
47
+ end
48
+
49
+ def respond_to?(method, priv = false)
50
+ case method
51
+ when FIND_BY_PATTERN, FIND_ALL_BY_PATTERN,
52
+ FIND_OR_CREATE_BY_PATTERN, FIND_OR_INITIALIZE_BY_PATTERN
53
+
54
+ true
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ def method_missing(method, *args, &block)
61
+ case method.to_s
62
+ when FIND_BY_PATTERN
63
+ Magic.scope(all, $1, args).first
64
+ when FIND_ALL_BY_PATTERN
65
+ Magic.scope(all, $1, args).to_a
66
+ when FIND_OR_CREATE_BY_PATTERN
67
+ Magic.find_or_create_by(all, $1, args, &block)
68
+ when FIND_OR_INITIALIZE_BY_PATTERN
69
+ Magic.find_or_initialize_by(all, $1, args, &block)
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,21 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module MassAssignmentSecurity
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include ActiveModel::MassAssignmentSecurity
11
+ end
12
+
13
+ def attributes=(attributes)
14
+ super(sanitize_for_mass_assignment(attributes))
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,17 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module Naming
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include ActiveModel::Naming
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,42 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ #
6
+ # This is ripped directly off of ActiveRecord::Observer
7
+ #
8
+ class Observer < ActiveModel::Observer
9
+
10
+ protected
11
+
12
+ def observed_classes
13
+ klasses = super
14
+ klasses + klasses.map { |klass| klass.descendants }.flatten
15
+ end
16
+
17
+ def add_observer!(klass)
18
+ super
19
+ define_callbacks klass
20
+ end
21
+
22
+ def define_callbacks(klass)
23
+ observer = self
24
+ observer_name = observer.class.name.underscore.gsub('/', '__')
25
+
26
+ Cequel::Model::Callbacks::CALLBACKS.each do |callback|
27
+ next unless respond_to?(callback)
28
+ callback_meth = :"_notify_#{observer_name}_for_#{callback}"
29
+ unless klass.respond_to?(callback_meth)
30
+ klass.send(:define_method, callback_meth) do |&block|
31
+ observer.update(callback, self, &block)
32
+ end
33
+ klass.send(callback, callback_meth)
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,173 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module Persistence
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+
11
+ delegate :update_all, :destroy_all, :delete_all, :to => :all
12
+
13
+ def index_preference(*columns)
14
+ @_cequel.index_preference.concat(columns.map { |c| c.to_sym })
15
+ end
16
+
17
+ def index_preference_columns
18
+ @_cequel.index_preference
19
+ end
20
+
21
+ def find(*keys)
22
+ coerce_array = keys.first.is_a?(Array)
23
+ keys.flatten!
24
+ if keys.length == 1
25
+ instance = find_one(keys.first)
26
+ coerce_array ? [instance] : instance
27
+ else
28
+ find_many(keys)
29
+ end
30
+ end
31
+
32
+ def create(attributes = {}, &block)
33
+ new(attributes, &block).tap { |instance| instance.save }
34
+ end
35
+
36
+ def column_family_name
37
+ @_cequel.column_family_name
38
+ end
39
+
40
+ def column_family
41
+ keyspace[column_family_name]
42
+ end
43
+
44
+ def keyspace
45
+ Cequel::Model.keyspace
46
+ end
47
+
48
+ def _hydrate(row)
49
+ type_column_name = @_cequel.type_column.try(:name)
50
+ if type_column_name && row[type_column_name]
51
+ clazz = row[type_column_name].constantize
52
+ else
53
+ clazz = self
54
+ end
55
+ clazz.allocate._hydrate(row.except(:type))
56
+ end
57
+
58
+ private
59
+
60
+ def find_one(key)
61
+ all.where!(key_alias => key).first.tap do |result|
62
+ if result.nil?
63
+ raise RecordNotFound,
64
+ "Couldn't find #{name} with #{key_alias}=#{key}"
65
+ end
66
+ end
67
+ end
68
+
69
+ def find_many(keys)
70
+ results = all.where!(key_alias => keys).reject do |result|
71
+ result.attributes.keys == [key_alias.to_s]
72
+ end
73
+
74
+ if results.length < keys.length
75
+ raise RecordNotFound,
76
+ "Couldn't find all #{name.pluralize} with #{key_alias} (#{keys.join(', ')})" <<
77
+ "(found #{results.length} results, but was looking for #{keys.length}"
78
+ end
79
+ results
80
+ end
81
+
82
+ end
83
+
84
+ def save
85
+ persisted? ? update : insert
86
+ end
87
+
88
+ def update_attributes(attributes)
89
+ self.attributes = attributes
90
+ save
91
+ end
92
+
93
+ def update_attribute(column, value)
94
+ update_attributes(column => value)
95
+ end
96
+
97
+ def insert
98
+ raise MissingKey if @_cequel.key.nil?
99
+ return if @_cequel.attributes.empty?
100
+ self.class.column_family.insert(attributes)
101
+ persisted!
102
+ end
103
+
104
+ def update
105
+ update_attributes, delete_attributes = {}, []
106
+ changed.each do |attr|
107
+ new = read_attribute(attr)
108
+ if new.nil?
109
+ delete_attributes << attr
110
+ else
111
+ update_attributes[attr] = new
112
+ end
113
+ end
114
+ data_set.update(update_attributes) if update_attributes.any?
115
+ data_set.delete(*delete_attributes) if delete_attributes.any?
116
+ transient! if @_cequel.attributes.empty?
117
+ end
118
+
119
+ def destroy
120
+ data_set.delete
121
+ end
122
+
123
+ def reload
124
+ result = data_set.first
125
+ key_alias = self.class.key_alias
126
+ if result.keys == [key_alias.to_s]
127
+ raise RecordNotFound,
128
+ "Couldn't find #{self.class.name} with #{key_alias}=#{@_cequel.key}"
129
+ end
130
+ _hydrate(result)
131
+ self
132
+ end
133
+
134
+ def _hydrate(row)
135
+ @_cequel = InstanceInternals.new(self)
136
+ tap do
137
+ key_alias = self.class.key_alias.to_s
138
+ key_alias = 'KEY' if key_alias.upcase == 'KEY'
139
+ @_cequel.key = row[key_alias]
140
+ @_cequel.attributes = row.except(key_alias)
141
+ persisted!
142
+ end
143
+ end
144
+
145
+ def persisted!
146
+ @_cequel.persisted = true
147
+ end
148
+
149
+ def transient!
150
+ @_cequel.persisted = false
151
+ end
152
+
153
+ def persisted?
154
+ !!@_cequel.persisted
155
+ end
156
+
157
+ def transient?
158
+ !persisted?
159
+ end
160
+
161
+ private
162
+
163
+ def data_set
164
+ raise MissingKey if @_cequel.key.nil?
165
+ self.class.column_family.
166
+ where(self.class.key_alias => @_cequel.key)
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+
173
+ end