datastax_rails 1.2.3 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -8
  4. data/config/schema.xml.erb +22 -19
  5. data/config/solrconfig.xml.erb +1 -1
  6. data/lib/cql-rb_extensions.rb +27 -0
  7. data/lib/datastax_rails.rb +13 -17
  8. data/lib/datastax_rails/associations/association.rb +1 -4
  9. data/lib/datastax_rails/associations/collection_proxy.rb +0 -13
  10. data/lib/datastax_rails/attribute_assignment.rb +28 -91
  11. data/lib/datastax_rails/attribute_methods.rb +109 -44
  12. data/lib/datastax_rails/attribute_methods/before_type_cast.rb +71 -0
  13. data/lib/datastax_rails/attribute_methods/dirty.rb +52 -11
  14. data/lib/datastax_rails/attribute_methods/primary_key.rb +87 -0
  15. data/lib/datastax_rails/attribute_methods/read.rb +120 -0
  16. data/lib/datastax_rails/attribute_methods/typecasting.rb +52 -21
  17. data/lib/datastax_rails/attribute_methods/write.rb +59 -0
  18. data/lib/datastax_rails/base.rb +227 -236
  19. data/lib/datastax_rails/cassandra_only_model.rb +25 -19
  20. data/lib/datastax_rails/column.rb +384 -0
  21. data/lib/datastax_rails/connection.rb +12 -13
  22. data/lib/datastax_rails/cql/alter_column_family.rb +0 -1
  23. data/lib/datastax_rails/cql/base.rb +15 -3
  24. data/lib/datastax_rails/cql/column_family.rb +2 -2
  25. data/lib/datastax_rails/cql/create_column_family.rb +7 -18
  26. data/lib/datastax_rails/cql/delete.rb +4 -9
  27. data/lib/datastax_rails/cql/insert.rb +2 -8
  28. data/lib/datastax_rails/cql/select.rb +4 -4
  29. data/lib/datastax_rails/cql/update.rb +8 -17
  30. data/lib/datastax_rails/dynamic_model.rb +98 -0
  31. data/lib/datastax_rails/payload_model.rb +19 -31
  32. data/lib/datastax_rails/persistence.rb +39 -54
  33. data/lib/datastax_rails/railtie.rb +1 -0
  34. data/lib/datastax_rails/reflection.rb +1 -1
  35. data/lib/datastax_rails/relation.rb +20 -20
  36. data/lib/datastax_rails/relation/batches.rb +18 -16
  37. data/lib/datastax_rails/relation/facet_methods.rb +1 -1
  38. data/lib/datastax_rails/relation/finder_methods.rb +6 -10
  39. data/lib/datastax_rails/relation/search_methods.rb +62 -48
  40. data/lib/datastax_rails/rsolr_client_wrapper.rb +1 -1
  41. data/lib/datastax_rails/schema/cassandra.rb +34 -62
  42. data/lib/datastax_rails/schema/migrator.rb +9 -24
  43. data/lib/datastax_rails/schema/solr.rb +13 -30
  44. data/lib/datastax_rails/schema_cache.rb +67 -0
  45. data/lib/datastax_rails/timestamps.rb +84 -11
  46. data/lib/datastax_rails/types/dirty_collection.rb +88 -0
  47. data/lib/datastax_rails/types/dynamic_list.rb +14 -0
  48. data/lib/datastax_rails/types/dynamic_map.rb +32 -0
  49. data/lib/datastax_rails/types/dynamic_set.rb +10 -0
  50. data/lib/datastax_rails/util/solr_repair.rb +4 -5
  51. data/lib/datastax_rails/validations.rb +6 -12
  52. data/lib/datastax_rails/validations/uniqueness.rb +0 -4
  53. data/lib/datastax_rails/version.rb +1 -1
  54. data/lib/datastax_rails/wide_storage_model.rb +13 -29
  55. data/lib/schema_migration.rb +4 -0
  56. data/spec/datastax_rails/associations_spec.rb +0 -1
  57. data/spec/datastax_rails/attribute_methods_spec.rb +9 -6
  58. data/spec/datastax_rails/base_spec.rb +26 -0
  59. data/spec/datastax_rails/column_spec.rb +238 -0
  60. data/spec/datastax_rails/cql/select_spec.rb +1 -1
  61. data/spec/datastax_rails/cql/update_spec.rb +2 -2
  62. data/spec/datastax_rails/persistence_spec.rb +29 -15
  63. data/spec/datastax_rails/relation/batches_spec.rb +5 -5
  64. data/spec/datastax_rails/relation/finder_methods_spec.rb +0 -20
  65. data/spec/datastax_rails/relation/search_methods_spec.rb +8 -0
  66. data/spec/datastax_rails/relation_spec.rb +7 -0
  67. data/spec/datastax_rails/schema/migrator_spec.rb +5 -10
  68. data/spec/datastax_rails/schema/solr_spec.rb +1 -1
  69. data/spec/datastax_rails/types/dynamic_list_spec.rb +20 -0
  70. data/spec/datastax_rails/types/dynamic_map_spec.rb +22 -0
  71. data/spec/datastax_rails/types/dynamic_set_spec.rb +16 -0
  72. data/spec/dummy/config/application.rb +2 -1
  73. data/spec/dummy/config/datastax.yml +6 -3
  74. data/spec/dummy/config/environments/development.rb +4 -5
  75. data/spec/dummy/config/environments/test.rb +0 -5
  76. data/spec/dummy/log/development.log +18 -0
  77. data/spec/dummy/log/test.log +36 -0
  78. data/spec/feature/dynamic_fields_spec.rb +9 -0
  79. data/spec/feature/overloaded_tables_spec.rb +24 -0
  80. data/spec/spec_helper.rb +1 -1
  81. data/spec/support/default_consistency_shared_examples.rb +2 -2
  82. data/spec/support/models.rb +28 -14
  83. metadata +212 -188
  84. data/lib/datastax_rails/identity.rb +0 -64
  85. data/lib/datastax_rails/identity/abstract_key_factory.rb +0 -29
  86. data/lib/datastax_rails/identity/custom_key_factory.rb +0 -37
  87. data/lib/datastax_rails/identity/hashed_natural_key_factory.rb +0 -10
  88. data/lib/datastax_rails/identity/natural_key_factory.rb +0 -39
  89. data/lib/datastax_rails/identity/uuid_key_factory.rb +0 -27
  90. data/lib/datastax_rails/type.rb +0 -16
  91. data/lib/datastax_rails/types.rb +0 -9
  92. data/lib/datastax_rails/types/array_type.rb +0 -86
  93. data/lib/datastax_rails/types/base_type.rb +0 -42
  94. data/lib/datastax_rails/types/binary_type.rb +0 -19
  95. data/lib/datastax_rails/types/boolean_type.rb +0 -22
  96. data/lib/datastax_rails/types/date_type.rb +0 -23
  97. data/lib/datastax_rails/types/float_type.rb +0 -18
  98. data/lib/datastax_rails/types/integer_type.rb +0 -18
  99. data/lib/datastax_rails/types/string_type.rb +0 -16
  100. data/lib/datastax_rails/types/text_type.rb +0 -15
  101. data/lib/datastax_rails/types/time_type.rb +0 -23
  102. data/spec/datastax_rails/types/float_type_spec.rb +0 -31
  103. data/spec/datastax_rails/types/integer_type_spec.rb +0 -31
  104. data/spec/datastax_rails/types/time_type_spec.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8971782b7516d5060103ef9b6ed6b56b71a5ad4
4
- data.tar.gz: 19487f48ae56cdf4ead4176377f5e0bbe3ac6bc3
3
+ metadata.gz: 6d858bc7834309a95814f407b2c85512b50cfa7a
4
+ data.tar.gz: c8c2896838848cd1f00a9c8f662958682ec60cff
5
5
  SHA512:
6
- metadata.gz: 7af9042a6919a4567e66b925934cd17e17fcbecb7dd6edf4ab6de5e66c6af378f942eab851bb962c197f6f46a44bd5a657df747c886f6169c1a9796c0ddc9e46
7
- data.tar.gz: 9e0fef7994f2746c934f15b6922e6dfae0b7974b0b4ee88d7594287709f8c3cb7e25cea3dd936a4e16ecaa524eb6fa0ea255bbdc84f460f36040a1f58ca82669
6
+ metadata.gz: b9cd869dd42101301ad8450e10e45bb983628217a079831fc834e0d65652bd87212ac8ecd90455c7c40fc1632a099f682a30786d42686fd9c4bbd8c64a312580
7
+ data.tar.gz: 44a0baf08768ac8147fc59555c6cacc3021c95c0da3138aeb0c09790bf28fbcd0ee506faee060a264e4e23be5c5aa97a8ecd1e65a6c20d98ecb9a66b0d9325c6
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2012 [Jason M. Kusar]
1
+ Copyright (c) 2011-2014 [Jason M. Kusar]
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -29,12 +29,13 @@ First add it to your Gemfile:
29
29
  Configure the config/datastax.yml file:
30
30
 
31
31
  development:
32
- servers: ["localhost:9160"]
32
+ servers: ["127.0.0.1"]
33
+ port: 9042
33
34
  keyspace: "<my_app>_development"
34
35
  strategy_class: "org.apache.cassandra.locator.NetworkTopologyStrategy"
35
36
  strategy_options: {"DC1": "1"}
36
37
  connection_options:
37
- timeout: 2
38
+ timeout: 10
38
39
  solr:
39
40
  port: 8983
40
41
  path: /solr
@@ -45,12 +46,13 @@ NetworkTopologySnitch and set up the cassandra-topology.properties file. See th
45
46
  For a more simple, single datacenter setup, something like this should probably work:
46
47
 
47
48
  development:
48
- servers: ["localhost:9160"]
49
- keyspace: "<my_app>_development"
49
+ servers: ["127.0.0.1"]
50
+ port: 9042
51
+ keyspace: "datastax_rails_development"
50
52
  strategy_class: "org.apache.cassandra.locator.SimpleStrategy"
51
53
  strategy_options: {"replication_factor": "1"}
52
54
  connection_options:
53
- timeout: 2
55
+ timeout: 10
54
56
  solr:
55
57
  port: 8983
56
58
  path: /solr
@@ -73,7 +75,7 @@ attributes on any model. DSR will only upload schema files if they have changed
73
75
  Create a sample Model. See Base documentation for more details:
74
76
 
75
77
  class Person < DatastaxRails::Base
76
- key :uuid
78
+ uuid :id
77
79
  string :first_name
78
80
  string :user_name
79
81
  text :bio
@@ -81,12 +83,22 @@ Create a sample Model. See Base documentation for more details:
81
83
  boolean :active
82
84
  timestamps
83
85
  end
86
+
87
+ === Different types of models
88
+
89
+ In addition to the simple model above, there are several special case models that take advantage of special
90
+ features of Cassandra. For examples of using those, see the following classes:
91
+
92
+ * {DatastaxRails::WideStorageModel}
93
+ * {DatastaxRails::PayloadModel}
94
+ * {DatastaxRails::DynamicModel}
84
95
 
85
96
  === Known issues
86
97
 
87
- When doing a grouped query, the total groups reported is only the total groups included in your query, not the total matching groups (if you're paginating). This is due to the way solr sharding works with grouped queries.
98
+ When doing a grouped query, the total groups reported is only the total groups included in your query, not the total matching groups (if you're paginating).
99
+ This is due to the way solr sharding works with grouped queries.
88
100
 
89
101
  === More information
90
102
 
91
- The documentation for DatastaxRails::Base and DatastaxRails::SearchMethods will give you quite a few examples
103
+ The documentation for {DatastaxRails::Base} and {DatastaxRails::SearchMethods} will give you quite a few examples
92
104
  of things you can do. You can find a copy of the latest documentation on line at http://rdoc.info/github/jasonmk/datastax_rails/master/frames.
@@ -36,35 +36,38 @@
36
36
  <filter class="solr.LowerCaseFilterFactory"/>
37
37
  </analyzer>
38
38
  </fieldType>
39
- <fieldType name="array" class="solr.TextField" positionIncrementGap="100">
40
- <analyzer>
41
- <tokenizer class="solr.PatternTokenizerFactory" pattern="\$\$\$\$"/>
42
- <filter class="solr.LowerCaseFilterFactory"/>
43
- </analyzer>
44
- </fieldType>
45
39
  <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
46
40
  <fieldType name="date" class="solr.TrieDateField" precisionStep="0" positionIncrementGap="0"/>
47
41
  <fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>
48
42
  <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0"/>
49
43
  <fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/>
50
44
  <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" positionIncrementGap="0"/>
45
+ <fieldType name="uuid" class="solr.UUIDField" indexed="true" />
46
+ <fieldType name="binary" class="solr.BinaryField" indexed="false" />
51
47
  </types>
52
48
  <fields>
53
- <field name="id" type="string" indexed="true" stored="true"/>
54
- <% @fields.each do |field| %>
55
- <field name="<%= field[:name] %>" type="<%= field[:type] %>" indexed="<%= field[:indexed] %>" stored="<%= field[:stored] %>" multiValued="<%= field[:multi_valued] %>"/>
49
+ <% @columns.each do |field| %>
50
+ <% if field.type == :map %>
51
+ <dynamicField name="<%= field.name %>*" type="<%= field.solr_type %>" indexed="<%= field.options[:solr_index] %>" stored="<%= field.options[:solr_store] %>" multiValued="false"/>
52
+ <% else %>
53
+ <field name="<%= field.name %>" type="<%= field.solr_type %>" indexed="<%= field.options[:solr_index] %>" stored="<%= field.options[:solr_store] %>" multiValued="<%= field.options[:multi_valued] %>"/>
54
+ <% if field.solr_type == 'text' && field.options[:sortable] %>
55
+ <field name="sort_<%= field.name %>" type="string" indexed="true" stored="fase" multiValued="false"/>
56
+ <% end %>
57
+ <% end %>
56
58
  <% end %>
57
59
  <field name="text" type="text" indexed="true" stored="false" multiValued="true"/>
58
60
  </fields>
59
61
 
60
- <defaultSearchField>text</defaultSearchField>
61
- <solrQueryParser defaultOperator="AND"/>
62
- <uniqueKey>id</uniqueKey>
63
- <% @fulltext_fields.each do |field| %>
64
- <copyField source="<%= field %>" dest="text"/>
65
- <% end %>
66
- <% @copy_fields.each do |field| %>
67
- <copyField source="<%= field[:source] %>" dest="<%= field[:dest]%>"/>
68
- <% end %>
69
-
62
+ <defaultSearchField>text</defaultSearchField>
63
+ <solrQueryParser defaultOperator="AND"/>
64
+ <uniqueKey><%= @primary_key %></uniqueKey>
65
+ <% @columns.each do |field| %>
66
+ <% if field.options[:fulltext] %>
67
+ <copyField source="<%= field.name %><%= field.type == :map ? "*" : ''%>" dest="text"/>
68
+ <% end %>
69
+ <% if field.solr_type == 'text' && field.options[:sortable] %>
70
+ <copyField source="<%= field.name %>" dest="sort_<%= field.name%>"/>
71
+ <% end %>
72
+ <% end %>
70
73
  </schema>
@@ -50,7 +50,7 @@
50
50
  <luceneMatchVersion>LUCENE_40</luceneMatchVersion>
51
51
 
52
52
  <!-- Enable DSE Search new type mappings -->
53
- <dseTypeMappingVersion><%= @legacy ? 0 : 1 %></dseTypeMappingVersion>
53
+ <dseTypeMappingVersion>2</dseTypeMappingVersion>
54
54
 
55
55
  <!-- lib directives can be used to instruct Solr to load an Jars
56
56
  identified and use them to resolve any "plugins" specified in
@@ -0,0 +1,27 @@
1
+ require 'cql'
2
+ require 'cql/client/connection_manager'
3
+
4
+ Cql::Client::ConnectionManager.class_eval do
5
+ attr_reader :current_connection
6
+
7
+ def random_connection
8
+ raise NotConnectedError unless connected?
9
+ @lock.synchronize do
10
+ @count ||= 0
11
+ @count += 1
12
+ if @count > 500
13
+ @count = 0
14
+ @current_connection = nil
15
+ end
16
+ @current_connection ||= @connections.sample
17
+ end
18
+ end
19
+ end
20
+
21
+ require 'cql/client/synchronous_client'
22
+
23
+ Cql::Client::SynchronousClient.class_eval do
24
+ def current_connection
25
+ async.instance_variable_get(:@connection_manager).current_connection
26
+ end
27
+ end
@@ -1,5 +1,4 @@
1
1
  require 'active_support/all'
2
- require 'cassandra-cql/1.2'
3
2
  require 'blankslate'
4
3
  require 'schema_migration'
5
4
 
@@ -14,11 +13,12 @@ module DatastaxRails
14
13
  autoload :Batches
15
14
  autoload :Callbacks
16
15
  autoload :CassandraOnlyModel
16
+ autoload :Column
17
17
  autoload :Collection
18
18
  autoload :Connection
19
19
  autoload :Cql
20
+ autoload :DynamicModel
20
21
  autoload :GroupedCollection
21
- autoload :Identity
22
22
  autoload :Index
23
23
  autoload :Inheritance
24
24
  autoload :PayloadModel
@@ -41,7 +41,6 @@ module DatastaxRails
41
41
  autoload :Scoping
42
42
  autoload :Serialization
43
43
  autoload :Timestamps
44
- autoload :Type
45
44
  autoload_under 'util' do
46
45
  autoload :SolrRepair
47
46
  end
@@ -53,9 +52,11 @@ module DatastaxRails
53
52
  extend ActiveSupport::Autoload
54
53
 
55
54
  eager_autoload do
56
- autoload :Definition
57
55
  autoload :Dirty
56
+ autoload :PrimaryKey
57
+ autoload :Read
58
58
  autoload :Typecasting
59
+ autoload :Write
59
60
  end
60
61
  end
61
62
 
@@ -73,26 +74,21 @@ module DatastaxRails
73
74
  autoload :Keyspace
74
75
  autoload :ColumnFamily
75
76
  end
76
-
77
+
77
78
  module Types
78
79
  extend ActiveSupport::Autoload
79
80
 
80
- autoload :BaseType
81
- autoload :BinaryType
82
- autoload :ArrayType
83
- autoload :BooleanType
84
- autoload :DateType
85
- autoload :FloatType
86
- autoload :IntegerType
87
- autoload :JsonType
88
- autoload :StringType
89
- autoload :TextType
90
- autoload :TimeType
91
- autoload :TimeWithZoneType
81
+ eager_autoload do
82
+ autoload :DirtyCollection
83
+ autoload :DynamicList
84
+ autoload :DynamicSet
85
+ autoload :DynamicMap
86
+ end
92
87
  end
93
88
  end
94
89
 
95
90
  require 'datastax_rails/railtie' if defined?(Rails)
96
91
  require 'datastax_rails/errors'
92
+ require 'cql-rb_extensions'
97
93
 
98
94
  ActiveSupport.run_load_hooks(:datastax_rails, DatastaxRails::Base)
@@ -213,10 +213,7 @@ module DatastaxRails
213
213
 
214
214
  def build_record(attributes, options)
215
215
  reflection.build_association(attributes, options) do |r|
216
- r.assign_attributes(
217
- create_scope.except(*r.changed),
218
- :without_protection => true
219
- )
216
+ r.assign_attributes(create_scope.except(*r.changed))
220
217
  end
221
218
  end
222
219
  end
@@ -61,19 +61,6 @@ module DatastaxRails
61
61
  end
62
62
 
63
63
  def method_missing(method, *args, &block)
64
- if Rails.version =~ /^3.*/
65
- match = ActiveRecord::DynamicFinderMatch.match(method)
66
- elsif Rails.version =~ /^4.*/
67
- match = ActiveRecord::DynamicMatchers::Method.match(self, method)
68
- end
69
- if match && match.instantiator?
70
- send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
71
- proxy_association.send :set_owner_attributes, r
72
- proxy_association.send :add_to_target, r
73
- yield(r) if block_given?
74
- end
75
- end
76
-
77
64
  if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
78
65
  if load_target
79
66
  if target.respond_to?(method)
@@ -3,112 +3,49 @@ require 'active_support/concern'
3
3
  module DatastaxRails
4
4
  module AttributeAssignment
5
5
  extend ActiveSupport::Concern
6
- if Rails.version =~ /^3.*/
7
- include ActiveModel::MassAssignmentSecurity
8
- elsif Rails.version =~ /^4.*/
9
- include ActiveModel::DeprecatedMassAssignmentSecurity
10
- include ActiveModel::ForbiddenAttributesProtection
11
- end
12
-
13
- module ClassMethods
14
- private
15
-
16
- # The primary key can never be set by mass-assignment for security reasons.
17
- def attributes_protected_by_default
18
- ["key"]
19
- end
20
- end
21
-
22
- # Allows you to set all the attributes at once by passing in a hash with keys
23
- # matching the attribute names (which again matches the column names).
24
- #
25
- # If any attributes are protected by either +attr_protected+ or
26
- # +attr_accessible+ then only settable attributes will be assigned.
27
- #
28
- # class User < ActiveRecord::Base
29
- # attr_protected :is_admin
30
- # end
31
- #
32
- # user = User.new
33
- # user.attributes = { :username => 'Phusion', :is_admin => true }
34
- # user.username # => "Phusion"
35
- # user.is_admin? # => false
36
- def attributes=(new_attributes)
37
- return unless new_attributes.is_a?(Hash)
38
-
39
- assign_attributes(new_attributes)
40
- end
6
+ include ActiveModel::ForbiddenAttributesProtection
41
7
 
42
- # Allows you to set all the attributes for a particular mass-assignment
43
- # security role by passing in a hash of attributes with keys matching
44
- # the attribute names (which again matches the column names) and the role
45
- # name using the :as option.
46
- #
47
- # To bypass mass-assignment security you can use the :without_protection => true
48
- # option.
49
- #
50
- # class User < ActiveRecord::Base
51
- # attr_accessible :name
52
- # attr_accessible :name, :is_admin, :as => :admin
53
- # end
54
- #
55
- # user = User.new
56
- # user.assign_attributes({ :name => 'Josh', :is_admin => true })
57
- # user.name # => "Josh"
58
- # user.is_admin? # => false
8
+ # Allows you to set all the attributes by passing in a hash of attributes with
9
+ # keys matching the attribute names (which again matches the column names).
59
10
  #
60
- # user = User.new
61
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
62
- # user.name # => "Josh"
63
- # user.is_admin? # => true
64
- #
65
- # user = User.new
66
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
67
- # user.name # => "Josh"
68
- # user.is_admin? # => true
69
- def assign_attributes(new_attributes, options = {})
11
+ # If the passed hash responds to <tt>permitted?</tt> method and the return value
12
+ # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
13
+ # exception is raised.
14
+ def assign_attributes(new_attributes)
70
15
  return if new_attributes.blank?
71
16
 
72
17
  attributes = new_attributes.stringify_keys
73
18
  nested_parameter_attributes = []
74
- @mass_assignment_options = options
75
19
 
76
- unless options[:without_protection]
77
- if Rails.version =~ /3.*/
78
- attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
20
+ attributes = sanitize_for_mass_assignment(attributes)
21
+
22
+ attributes.each do |k, v|
23
+ if v.is_a?(Hash)
24
+ nested_parameter_attributes << [ k, v ]
79
25
  else
80
- attributes = sanitize_for_mass_assignment(attributes)
26
+ _assign_attribute(k, v)
81
27
  end
82
28
  end
83
29
 
84
- attributes.each do |k, v|
30
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
31
+ end
32
+ alias attributes= assign_attributes
33
+
34
+ private
35
+
36
+ def _assign_attribute(k, v)
37
+ public_send("#{k}=", v)
38
+ rescue NoMethodError
85
39
  if respond_to?("#{k}=")
86
- if v.is_a?(Hash)
87
- nested_parameter_attributes << [ k, v ]
88
- else
89
- send("#{k}=", v)
90
- end
40
+ raise
91
41
  else
92
- raise(UnknownAttributeError, "unknown attribute: #{k}")
42
+ raise UnknownAttributeError, "unknown attribute: #{k}"
93
43
  end
94
44
  end
95
-
96
- # assign any deferred nested attributes after the base attributes have been set
97
- nested_parameter_attributes.each do |k,v|
98
- send("#{k}=", v)
45
+
46
+ # Assign any deferred nested attributes after the base attributes have been set.
47
+ def assign_nested_parameter_attributes(pairs)
48
+ pairs.each { |k, v| _assign_attribute(k, v) }
99
49
  end
100
-
101
- @mass_assignment_options = nil
102
- end
103
-
104
- protected
105
-
106
- def mass_assignment_options
107
- @mass_assignment_options ||= {}
108
- end
109
-
110
- def mass_assignment_role
111
- mass_assignment_options[:as] || :default
112
- end
113
50
  end
114
51
  end
@@ -1,24 +1,67 @@
1
+ require 'mutex_m'
2
+
1
3
  module DatastaxRails
2
4
  module AttributeMethods
3
5
  extend ActiveSupport::Concern
4
6
  include ActiveModel::AttributeMethods
5
7
 
6
8
  included do
9
+ initialize_generated_modules
10
+
11
+ include Write
12
+ include Dirty
13
+ include Read
14
+ include PrimaryKey
15
+ include Typecasting
16
+
7
17
  alias :[] :read_attribute
8
18
  alias :[]= :write_attribute
9
-
10
- attribute_method_suffix("=")
19
+ alias has_attribute? attribute_exists?
11
20
  end
12
21
 
13
22
  module ClassMethods
23
+ def inherited(child_class)
24
+ child_class.initialize_generated_modules
25
+ super
26
+ end
27
+
28
+ def initialize_generated_modules
29
+ @generated_attribute_methods = Module.new {
30
+ extend Mutex_m
31
+
32
+ const_set :AttrNames, Module.new {
33
+ def self.set_name_cache(name, value)
34
+ const_name = "ATTR_#{name}"
35
+ unless const_defined? const_name
36
+ const_set const_name, value.dup.freeze
37
+ end
38
+ end
39
+ }
40
+ }
41
+ @attribute_methods_generated = false
42
+ include @generated_attribute_methods
43
+ end
44
+
14
45
  def define_attribute_methods
15
- return if attribute_methods_generated?
16
- super(attribute_definitions.keys)
17
- # Remove setter methods from readonly attributes
18
- readonly_attributes.each do |attr|
19
- remove_method("#{attr}=".to_sym) if method_defined?("#{attr}=".to_sym)
46
+ # Use a mutex; we don't want two thread simultaneously trying to define
47
+ # attribute methods.
48
+ generated_attribute_methods.synchronize do
49
+ return false if attribute_methods_generated?
50
+ super(attribute_definitions.keys)
51
+ # Remove setter methods from readonly attributes
52
+ readonly_attributes.each do |attr|
53
+ remove_method("#{attr}=".to_sym) if method_defined?("#{attr}=".to_sym)
54
+ end
55
+ @attribute_methods_generated = true
56
+ end
57
+ true
58
+ end
59
+
60
+ def undefine_attribute_methods
61
+ generated_attribute_methods.synchronize do
62
+ super if attribute_methods_generated?
63
+ @attribute_methods_generated = false
20
64
  end
21
- @attribute_methods_generated = true
22
65
  end
23
66
 
24
67
  def attribute_methods_generated?
@@ -32,21 +75,8 @@ module DatastaxRails
32
75
  def attribute(name, options)
33
76
  type = options.delete :type
34
77
  coder = options.delete :coder
35
-
36
- if self <= CassandraOnlyModel
37
- if options[:indexed] == :both || options[:indexed] == :cassandra
38
- options[:indexed] = :cassandra
39
- else
40
- options[:indexed] = false
41
- end
42
- end
78
+ default = options.delete :default
43
79
 
44
- if type.is_a?(Symbol)
45
- coder = DatastaxRails::Type.get_coder(type) || (raise "Unknown type #{type}")
46
- elsif coder.nil?
47
- raise "Must supply a :coder for #{name}"
48
- end
49
-
50
80
  if(options[:lazy])
51
81
  lazy_attributes << name.to_sym
52
82
  end
@@ -54,31 +84,48 @@ module DatastaxRails
54
84
  if(options[:readonly])
55
85
  readonly_attributes << name.to_sym
56
86
  end
57
-
58
- attribute_definitions[name.to_sym] = AttributeMethods::Definition.new(self, name, coder, options)
59
- end
60
- end
61
-
62
- # Casts the attribute and stores it in the attribute hash.
63
- def write_attribute(name, value)
64
- if(attribute_definitions[name.to_sym].coder.is_a?(DatastaxRails::Types::BinaryType))
65
- @attributes[name.to_s] = value
66
- else
67
- @attributes[name.to_s] = self.class.typecast_attribute(self, name, value)
87
+
88
+ column = Column.new(name, default, type, options)
89
+ column.primary = (name.to_s == primary_key.to_s)
90
+ if coder
91
+ coder = coder.constantize rescue nil
92
+ if coder.class == Class && (coder.instance_methods & [:dump, :load]).size == 2
93
+ column.coder = coder.new(self)
94
+ else
95
+ raise ArgumentError, "Coder must be a class that responds the dump and load instance variables"
96
+ end
97
+ end
98
+ attribute_definitions[name.to_sym] = column
68
99
  end
69
- end
70
-
71
- # Returns the attribute out of the attribute hash. If the attribute is lazy loaded and hasn't
72
- # been loaded yet it will be done so now.
73
- def read_attribute(name)
74
- if(!loaded_attributes[name] && persisted? && !key.blank?)
75
- @attributes[name.to_s] = self.class.select(name).with_cassandra.find(self.id).read_attribute(name)
76
- loaded_attributes[name] = true
100
+
101
+ # Returns the column object for the named attribute. Returns +nil+ if the
102
+ # named attribute not exists.
103
+ #
104
+ # class Person < DatastaxRails::Base
105
+ # end
106
+ #
107
+ # person = Person.new
108
+ # person.column_for_attribute(:name)
109
+ # # => #<DatastaxRails::Base:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
110
+ #
111
+ # person.column_for_attribute(:nothing)
112
+ # # => nil
113
+ def column_for_attribute(name)
114
+ # FIXME: should this return a null object for columns that don't exist?
115
+ column = self.columns_hash[name.to_s]
116
+ unless column
117
+ # Check for a dynamic column
118
+ columns_hash.values.each do |col|
119
+ if col.type == :map && name.to_s.starts_with?("#{col.name}")
120
+ column = col
121
+ break
122
+ end
123
+ end
124
+ end
125
+ column
77
126
  end
78
-
79
- @attributes[name.to_s]
80
127
  end
81
-
128
+
82
129
  def attribute_exists?(name)
83
130
  @attributes.key?(name.to_s)
84
131
  end
@@ -96,11 +143,29 @@ module DatastaxRails
96
143
  self.class.define_attribute_methods unless self.class.attribute_methods_generated?
97
144
  super
98
145
  end
146
+
147
+ def column_for_attribute(name)
148
+ self.class.column_for_attribute(name)
149
+ end
99
150
 
100
151
  protected
101
152
  def attribute_method?(name)
102
153
  !!attribute_definitions[name.to_sym]
103
154
  end
155
+
156
+ def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
157
+ attribute_names.each do |name|
158
+ attributes[name] = clone_attribute_value(reader_method, name)
159
+ end
160
+ attributes
161
+ end
162
+
163
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
164
+ value = send(reader_method, attribute_name)
165
+ value.duplicable? ? value.clone : value
166
+ rescue TypeError, NoMethodError
167
+ value
168
+ end
104
169
 
105
170
  private
106
171
  def attribute(name)