sessionm-cassandra_object 2.2.51 → 2.2.52

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.
@@ -25,6 +25,7 @@ module CassandraObject
25
25
  autoload :Type
26
26
  autoload :Schema
27
27
  autoload :RowTTL
28
+ autoload :NestedAttributes
28
29
 
29
30
  module Tasks
30
31
  extend ActiveSupport::Autoload
@@ -43,6 +43,7 @@ module CassandraObject
43
43
  include Batches
44
44
  include FinderMethods
45
45
  include Timestamps
46
+ include NestedAttributes
46
47
 
47
48
  attr_accessor :key
48
49
 
@@ -1,13 +1,13 @@
1
1
  module CassandraObject
2
- class CasssandraObjectError < StandardError
2
+ class CassandraObjectError < StandardError
3
3
  end
4
4
 
5
- class RecordNotSaved < CasssandraObjectError
5
+ class RecordNotSaved < CassandraObjectError
6
6
  end
7
7
 
8
- class RecordNotFound < CasssandraObjectError
8
+ class RecordNotFound < CassandraObjectError
9
9
  end
10
10
 
11
- class InvalidKey < CasssandraObjectError
11
+ class InvalidKey < CassandraObjectError
12
12
  end
13
13
  end
@@ -0,0 +1,168 @@
1
+ require 'active_support/core_ext/hash/except'
2
+ require 'active_support/core_ext/object/try'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+
6
+ module CassandraObject
7
+ module NestedAttributes #:nodoc:
8
+ class TooManyRecords < CassandraObjectError
9
+ end
10
+
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ class_inheritable_accessor :nested_attributes_options, :instance_writer => false
15
+ self.nested_attributes_options = {}
16
+ end
17
+
18
+ module ClassMethods
19
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
20
+
21
+ def accepts_nested_attributes_for(*attr_names)
22
+ options = { :allow_destroy => false, :update_only => false }
23
+ options.update(attr_names.extract_options!)
24
+ options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
25
+ options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
26
+
27
+ attr_names.each do |association_name|
28
+ if reflection = reflect_on_association(association_name)
29
+ reflection.options[:autosave] = true
30
+ add_autosave_association_callbacks(reflection)
31
+ nested_attributes_options[association_name.to_sym] = options
32
+ type = (reflection.collection? ? :collection : :one_to_one)
33
+
34
+ # def pirate_attributes=(attributes)
35
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
36
+ # end
37
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
38
+ if method_defined?(:#{association_name}_attributes=)
39
+ remove_method(:#{association_name}_attributes=)
40
+ end
41
+ def #{association_name}_attributes=(attributes)
42
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
43
+ end
44
+ eoruby
45
+ else
46
+ raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def _destroy
53
+ marked_for_destruction?
54
+ end
55
+
56
+ private
57
+
58
+ UNASSIGNABLE_KEYS = %w( id _destroy )
59
+
60
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
61
+ options = nested_attributes_options[association_name]
62
+ attributes = attributes.with_indifferent_access
63
+ check_existing_record = (options[:update_only] || !attributes['id'].blank?)
64
+
65
+ if check_existing_record && (record = send(association_name)) &&
66
+ (options[:update_only] || record.id.to_s == attributes['id'].to_s)
67
+ assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
68
+
69
+ elsif !attributes['id'].blank?
70
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
71
+
72
+ elsif !reject_new_record?(association_name, attributes)
73
+ method = "build_#{association_name}"
74
+ if respond_to?(method)
75
+ send(method, attributes.except(*UNASSIGNABLE_KEYS))
76
+ else
77
+ raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
78
+ end
79
+ end
80
+ end
81
+
82
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
83
+ options = nested_attributes_options[association_name]
84
+
85
+ unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
86
+ raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
87
+ end
88
+
89
+ if options[:limit] && attributes_collection.size > options[:limit]
90
+ raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
91
+ end
92
+
93
+ if attributes_collection.is_a? Hash
94
+ keys = attributes_collection.keys
95
+ attributes_collection = if keys.include?('id') || keys.include?(:id)
96
+ Array.wrap(attributes_collection)
97
+ else
98
+ attributes_collection.sort_by { |i, _| i.to_i }.map { |_, attributes| attributes }
99
+ end
100
+ end
101
+
102
+ association = send(association_name)
103
+
104
+ existing_records = if association.loaded?
105
+ association.to_a
106
+ else
107
+ attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
108
+ attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : []
109
+ end
110
+
111
+ attributes_collection.each do |attributes|
112
+ attributes = attributes.with_indifferent_access
113
+
114
+ if attributes['id'].blank?
115
+ unless reject_new_record?(association_name, attributes)
116
+ association.build(attributes.except(*UNASSIGNABLE_KEYS))
117
+ end
118
+
119
+ elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
120
+ association.send(:add_record_to_target_with_callbacks, existing_record) if !association.loaded? && !call_reject_if(association_name, attributes)
121
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
122
+
123
+ else
124
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
125
+ end
126
+ end
127
+ end
128
+
129
+ # Updates a record with the +attributes+ or marks it for destruction if
130
+ # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
131
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
132
+ record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
133
+ record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
134
+ end
135
+
136
+ # Determines if a hash contains a truthy _destroy key.
137
+ def has_destroy_flag?(hash)
138
+ value = hash['_destroy']
139
+ if value.is_a?(String) && value.blank?
140
+ nil
141
+ else
142
+ [true, 1, '1', 't', 'T', 'true', 'TRUE'].include?(value)
143
+ end
144
+ end
145
+
146
+ # Determines if a new record should be build by checking for
147
+ # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
148
+ # association and evaluates to +true+.
149
+ def reject_new_record?(association_name, attributes)
150
+ has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
151
+ end
152
+
153
+ def call_reject_if(association_name, attributes)
154
+ return false if has_destroy_flag?(attributes)
155
+ case callback = nested_attributes_options[association_name][:reject_if]
156
+ when Symbol
157
+ method(callback).arity == 0 ? send(callback) : send(callback, attributes)
158
+ when Proc
159
+ callback.call(attributes)
160
+ end
161
+ end
162
+
163
+ def raise_nested_attributes_record_not_found(association_name, record_id)
164
+ reflection = self.class.reflect_on_association(association_name)
165
+ raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
166
+ end
167
+ end
168
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'sessionm-cassandra_object'
5
- s.version = '2.2.51'
5
+ s.version = '2.2.52'
6
6
  s.description = 'Cassandra ActiveModel'
7
7
  s.summary = 'Cassandra ActiveModel'
8
8
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 2
7
7
  - 2
8
- - 51
9
- version: 2.2.51
8
+ - 52
9
+ version: 2.2.52
10
10
  platform: ruby
11
11
  authors:
12
12
  - Michael Koziarski
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2012-05-21 00:00:00 -04:00
19
+ date: 2012-05-29 00:00:00 -04:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -106,6 +106,7 @@ files:
106
106
  - lib/cassandra_object/migrations.rb
107
107
  - lib/cassandra_object/migrations/migration.rb
108
108
  - lib/cassandra_object/mocking.rb
109
+ - lib/cassandra_object/nested_attributes.rb
109
110
  - lib/cassandra_object/persistence.rb
110
111
  - lib/cassandra_object/railtie.rb
111
112
  - lib/cassandra_object/row_ttl.rb