bigrecord 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +44 -0
  3. data/Rakefile +17 -0
  4. data/VERSION +1 -0
  5. data/doc/bigrecord_specs.rdoc +36 -0
  6. data/doc/getting_started.rdoc +157 -0
  7. data/examples/bigrecord.yml +25 -0
  8. data/generators/bigrecord/bigrecord_generator.rb +17 -0
  9. data/generators/bigrecord/templates/bigrecord.rake +47 -0
  10. data/generators/bigrecord_migration/bigrecord_migration_generator.rb +13 -0
  11. data/generators/bigrecord_migration/templates/migration.rb +9 -0
  12. data/generators/bigrecord_model/bigrecord_model_generator.rb +28 -0
  13. data/generators/bigrecord_model/templates/migration.rb +13 -0
  14. data/generators/bigrecord_model/templates/model.rb +7 -0
  15. data/generators/bigrecord_model/templates/model_spec.rb +12 -0
  16. data/init.rb +9 -0
  17. data/install.rb +22 -0
  18. data/lib/big_record/abstract_base.rb +1088 -0
  19. data/lib/big_record/action_view_extensions.rb +266 -0
  20. data/lib/big_record/ar_associations/association_collection.rb +194 -0
  21. data/lib/big_record/ar_associations/association_proxy.rb +158 -0
  22. data/lib/big_record/ar_associations/belongs_to_association.rb +57 -0
  23. data/lib/big_record/ar_associations/belongs_to_many_association.rb +57 -0
  24. data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +164 -0
  25. data/lib/big_record/ar_associations/has_many_association.rb +191 -0
  26. data/lib/big_record/ar_associations/has_one_association.rb +80 -0
  27. data/lib/big_record/ar_associations.rb +1608 -0
  28. data/lib/big_record/ar_reflection.rb +223 -0
  29. data/lib/big_record/attribute_methods.rb +75 -0
  30. data/lib/big_record/base.rb +618 -0
  31. data/lib/big_record/br_associations/association_collection.rb +194 -0
  32. data/lib/big_record/br_associations/association_proxy.rb +153 -0
  33. data/lib/big_record/br_associations/belongs_to_association.rb +52 -0
  34. data/lib/big_record/br_associations/belongs_to_many_association.rb +293 -0
  35. data/lib/big_record/br_associations/cached_item_proxy.rb +194 -0
  36. data/lib/big_record/br_associations/cached_item_proxy_factory.rb +62 -0
  37. data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +168 -0
  38. data/lib/big_record/br_associations/has_one_association.rb +80 -0
  39. data/lib/big_record/br_associations.rb +978 -0
  40. data/lib/big_record/br_reflection.rb +151 -0
  41. data/lib/big_record/callbacks.rb +367 -0
  42. data/lib/big_record/connection_adapters/abstract/connection_specification.rb +279 -0
  43. data/lib/big_record/connection_adapters/abstract/database_statements.rb +175 -0
  44. data/lib/big_record/connection_adapters/abstract/quoting.rb +58 -0
  45. data/lib/big_record/connection_adapters/abstract_adapter.rb +190 -0
  46. data/lib/big_record/connection_adapters/column.rb +491 -0
  47. data/lib/big_record/connection_adapters/hbase_adapter.rb +432 -0
  48. data/lib/big_record/connection_adapters/view.rb +27 -0
  49. data/lib/big_record/connection_adapters.rb +10 -0
  50. data/lib/big_record/deletion.rb +73 -0
  51. data/lib/big_record/dynamic_schema.rb +92 -0
  52. data/lib/big_record/embedded.rb +71 -0
  53. data/lib/big_record/embedded_associations/association_proxy.rb +148 -0
  54. data/lib/big_record/family_span_columns.rb +89 -0
  55. data/lib/big_record/fixtures.rb +1025 -0
  56. data/lib/big_record/migration.rb +380 -0
  57. data/lib/big_record/routing_ext.rb +65 -0
  58. data/lib/big_record/timestamp.rb +51 -0
  59. data/lib/big_record/validations.rb +830 -0
  60. data/lib/big_record.rb +125 -0
  61. data/lib/bigrecord.rb +1 -0
  62. data/rails/init.rb +9 -0
  63. data/spec/connections/bigrecord.yml +13 -0
  64. data/spec/connections/cassandra/connection.rb +2 -0
  65. data/spec/connections/hbase/connection.rb +2 -0
  66. data/spec/debug.log +281 -0
  67. data/spec/integration/br_associations_spec.rb +80 -0
  68. data/spec/lib/animal.rb +12 -0
  69. data/spec/lib/book.rb +10 -0
  70. data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +14 -0
  71. data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +9 -0
  72. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +9 -0
  73. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +9 -0
  74. data/spec/lib/company.rb +14 -0
  75. data/spec/lib/embedded/web_link.rb +12 -0
  76. data/spec/lib/employee.rb +33 -0
  77. data/spec/lib/migrations/20090706182535_add_animals_table.rb +13 -0
  78. data/spec/lib/migrations/20090706190623_add_books_table.rb +15 -0
  79. data/spec/lib/migrations/20090706193019_add_companies_table.rb +14 -0
  80. data/spec/lib/migrations/20090706194512_add_employees_table.rb +13 -0
  81. data/spec/lib/migrations/20090706195741_add_zoos_table.rb +13 -0
  82. data/spec/lib/novel.rb +5 -0
  83. data/spec/lib/zoo.rb +17 -0
  84. data/spec/spec.opts +4 -0
  85. data/spec/spec_helper.rb +55 -0
  86. data/spec/unit/abstract_base_spec.rb +287 -0
  87. data/spec/unit/adapters/abstract_adapter_spec.rb +56 -0
  88. data/spec/unit/adapters/adapter_shared_spec.rb +51 -0
  89. data/spec/unit/adapters/hbase_adapter_spec.rb +15 -0
  90. data/spec/unit/ar_associations_spec.rb +8 -0
  91. data/spec/unit/base_spec.rb +6 -0
  92. data/spec/unit/br_associations_spec.rb +58 -0
  93. data/spec/unit/embedded_spec.rb +43 -0
  94. data/spec/unit/find_spec.rb +34 -0
  95. data/spec/unit/hash_helper_spec.rb +44 -0
  96. data/spec/unit/migration_spec.rb +144 -0
  97. data/spec/unit/model_spec.rb +315 -0
  98. data/spec/unit/validations_spec.rb +182 -0
  99. data/tasks/bigrecord_tasks.rake +47 -0
  100. data/tasks/data_store.rb +46 -0
  101. data/tasks/gem.rb +22 -0
  102. data/tasks/rdoc.rb +8 -0
  103. data/tasks/spec.rb +34 -0
  104. metadata +189 -0
@@ -0,0 +1,57 @@
1
+ module BigRecord
2
+ module ArAssociations
3
+ class BelongsToManyAssociation < AssociationProxy #:nodoc:
4
+
5
+ def create(attributes = {})
6
+ replace(@reflection.klass.create(attributes))
7
+ end
8
+
9
+ def build(attributes = {})
10
+ replace(@reflection.klass.new(attributes))
11
+ end
12
+
13
+ def replace(record)
14
+ counter_cache_name = @reflection.counter_cache_column
15
+
16
+ if record.nil?
17
+ if counter_cache_name && @owner[counter_cache_name] && !@owner.new_record?
18
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
19
+ end
20
+
21
+ @target = @owner[@reflection.primary_key_name] = nil
22
+ else
23
+ raise_on_type_mismatch(record)
24
+
25
+ if counter_cache_name && !@owner.new_record?
26
+ @reflection.klass.increment_counter(counter_cache_name, record.id)
27
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
28
+ end
29
+
30
+ @target = (AssociationProxy === record ? record.target : record)
31
+ @owner[@reflection.primary_key_name] = record.id unless record.new_record?
32
+ @updated = true
33
+ end
34
+
35
+ loaded
36
+ record
37
+ end
38
+
39
+ def updated?
40
+ @updated
41
+ end
42
+
43
+ private
44
+ def find_target
45
+ @reflection.klass.find(
46
+ @owner[@reflection.primary_key_name],
47
+ :conditions => conditions,
48
+ :include => @reflection.options[:include]
49
+ )
50
+ end
51
+
52
+ def foreign_key_present
53
+ !@owner[@reflection.primary_key_name].nil?
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,164 @@
1
+ module BigRecord
2
+ module ArAssociations
3
+ class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ construct_sql
7
+ end
8
+
9
+ def build(attributes = {})
10
+ load_target
11
+ build_record(attributes)
12
+ end
13
+
14
+ def create(attributes = {})
15
+ create_record(attributes) { |record| insert_record(record) }
16
+ end
17
+
18
+ def create!(attributes = {})
19
+ create_record(attributes) { |record| insert_record(record, true) }
20
+ end
21
+
22
+ def find_first
23
+ load_target.first
24
+ end
25
+
26
+ def find(*args)
27
+ options = args.extract_options!
28
+
29
+ # If using a custom finder_sql, scan the entire collection.
30
+ if @reflection.options[:finder_sql]
31
+ expects_array = args.first.kind_of?(Array)
32
+ ids = args.flatten.compact.uniq
33
+
34
+ if ids.size == 1
35
+ id = ids.first.to_i
36
+ record = load_target.detect { |record| id == record.id }
37
+ expects_array ? [record] : record
38
+ else
39
+ load_target.select { |record| ids.include?(record.id) }
40
+ end
41
+ else
42
+ conditions = "#{@finder_sql}"
43
+
44
+ if sanitized_conditions = sanitize_sql(options[:conditions])
45
+ conditions << " AND (#{sanitized_conditions})"
46
+ end
47
+
48
+ options[:conditions] = conditions
49
+ options[:joins] = @join_sql
50
+ options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
51
+
52
+ if options[:order] && @reflection.options[:order]
53
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
54
+ elsif @reflection.options[:order]
55
+ options[:order] = @reflection.options[:order]
56
+ end
57
+
58
+ merge_options_from_reflection!(options)
59
+
60
+ options[:select] ||= (@reflection.options[:select] || '*')
61
+
62
+ # Pass through args exactly as we received them.
63
+ args << options
64
+ @reflection.klass.find(*args)
65
+ end
66
+ end
67
+
68
+ protected
69
+ def count_records
70
+ load_target.size rescue 0
71
+ end
72
+
73
+ def insert_record(record, force=true)
74
+ if record.new_record?
75
+ if force
76
+ record.save!
77
+ else
78
+ return false unless record.save
79
+ end
80
+ end
81
+
82
+ if @reflection.options[:insert_sql]
83
+ @reflection.klass.connection.execute(interpolate_sql(@reflection.options[:insert_sql], record))
84
+ else
85
+ columns = @reflection.klass.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
86
+
87
+ attributes = columns.inject({}) do |attributes, column|
88
+ case column.name
89
+ when @reflection.primary_key_name
90
+ attributes[column.name] = @owner.quoted_id
91
+ when @reflection.association_foreign_key
92
+ attributes[column.name] = record.quoted_id
93
+ else
94
+ if record.attributes.has_key?(column.name)
95
+ value = @owner.send(:quote_value, record[column.name], column)
96
+ attributes[column.name] = value unless value.nil?
97
+ end
98
+ end
99
+ attributes
100
+ end
101
+
102
+ sql =
103
+ "INSERT INTO #{@reflection.options[:join_table]} (#{record.send(:quoted_column_names, attributes).join(', ')}) " +
104
+ "VALUES (#{attributes.values.join(', ')})"
105
+
106
+ @reflection.klass.connection.execute(sql)
107
+ end
108
+
109
+ return true
110
+ end
111
+
112
+ def delete_records(records)
113
+ if sql = @reflection.options[:delete_sql]
114
+ records.each { |record| @owner.connection.execute(interpolate_sql(sql, record)) }
115
+ else
116
+ ids = quoted_record_ids(records)
117
+ sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
118
+ @reflection.klass.connection.execute(sql)
119
+ end
120
+ end
121
+
122
+ def construct_sql
123
+ interpolate_sql_options!(@reflection.options, :finder_sql)
124
+
125
+ if @reflection.options[:finder_sql]
126
+ @finder_sql = @reflection.options[:finder_sql]
127
+ else
128
+ @finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
129
+ @finder_sql << " AND (#{conditions})" if conditions
130
+ end
131
+
132
+ @join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
133
+ end
134
+
135
+ def construct_scope
136
+ { :find => { :conditions => @finder_sql,
137
+ :joins => @join_sql,
138
+ :readonly => false,
139
+ :order => @reflection.options[:order],
140
+ :limit => @reflection.options[:limit] } }
141
+ end
142
+
143
+ # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
144
+ # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
145
+ # an id column. This will then overwrite the id column of the records coming back.
146
+ def finding_with_ambiguous_select?(select_clause)
147
+ !select_clause && @reflection.klass.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
148
+ end
149
+
150
+ private
151
+ def create_record(attributes)
152
+ # Can't use Base.create because the foreign key may be a protected attribute.
153
+ ensure_owner_is_not_new
154
+ if attributes.is_a?(Array)
155
+ attributes.collect { |attr| create(attr) }
156
+ else
157
+ record = build(attributes)
158
+ yield(record)
159
+ record
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,191 @@
1
+ module BigRecord
2
+ module ArAssociations
3
+ class HasManyAssociation < AssociationCollection #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ construct_sql
7
+ end
8
+
9
+ def build(attributes = {})
10
+ if attributes.is_a?(Array)
11
+ attributes.collect { |attr| build(attr) }
12
+ else
13
+ record = @reflection.klass.new(attributes)
14
+ set_belongs_to_association_for(record)
15
+
16
+ @target ||= [] unless loaded?
17
+ @target << record
18
+
19
+ record
20
+ end
21
+ end
22
+
23
+ # Count the number of associated records. All arguments are optional.
24
+ def count(*args)
25
+ if @reflection.options[:counter_sql]
26
+ @reflection.klass.count_by_sql(@counter_sql)
27
+ elsif @reflection.options[:finder_sql]
28
+ @reflection.klass.count_by_sql(@finder_sql)
29
+ else
30
+ column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args)
31
+ options[:conditions] = options[:conditions].nil? ?
32
+ @finder_sql :
33
+ @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
34
+ options[:include] = @reflection.options[:include]
35
+
36
+ @reflection.klass.count(column_name, options)
37
+ end
38
+ end
39
+
40
+ def find(*args)
41
+ options = Base.send(:extract_options_from_args!, args)
42
+
43
+ # If using a custom finder_sql, scan the entire collection.
44
+ if @reflection.options[:finder_sql]
45
+ expects_array = args.first.kind_of?(Array)
46
+ ids = args.flatten.compact.uniq
47
+
48
+ if ids.size == 1
49
+ id = ids.first
50
+ record = load_target.detect { |record| id == record.id }
51
+ expects_array ? [ record ] : record
52
+ else
53
+ load_target.select { |record| ids.include?(record.id) }
54
+ end
55
+ else
56
+ conditions = "#{@finder_sql}"
57
+ if sanitized_conditions = sanitize_sql(options[:conditions])
58
+ conditions << " AND (#{sanitized_conditions})"
59
+ end
60
+ options[:conditions] = conditions
61
+
62
+ if options[:order] && @reflection.options[:order]
63
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
64
+ elsif @reflection.options[:order]
65
+ options[:order] = @reflection.options[:order]
66
+ end
67
+
68
+ merge_options_from_reflection!(options)
69
+
70
+ # Pass through args exactly as we received them.
71
+ args << options
72
+ @reflection.klass.find(*args)
73
+ end
74
+ end
75
+
76
+ protected
77
+ def method_missing(method, *args, &block)
78
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
79
+ super
80
+ else
81
+ create_scoping = {}
82
+ set_belongs_to_association_for(create_scoping)
83
+
84
+ @reflection.klass.with_scope(
85
+ :create => create_scoping,
86
+ :find => {
87
+ :conditions => @finder_sql,
88
+ :joins => @join_sql,
89
+ :readonly => false
90
+ }
91
+ ) do
92
+ @reflection.klass.send(method, *args, &block)
93
+ end
94
+ end
95
+ end
96
+
97
+ def load_target
98
+ if !@owner.new_record? || foreign_key_present
99
+ begin
100
+ if !loaded?
101
+ if @target.is_a?(Array) && @target.any?
102
+ @target = (find_target + @target).uniq
103
+ else
104
+ @target = find_target
105
+ end
106
+ end
107
+ rescue ActiveRecord::RecordNotFound
108
+ reset
109
+ end
110
+ end
111
+
112
+ loaded if target
113
+ target
114
+ end
115
+
116
+ def count_records
117
+ count = if has_cached_counter?
118
+ @owner.send(:read_attribute, cached_counter_attribute_name)
119
+ elsif @reflection.options[:counter_sql]
120
+ @reflection.klass.count_by_sql(@counter_sql)
121
+ else
122
+ @reflection.klass.count(:conditions => @counter_sql)
123
+ end
124
+
125
+ @target = [] and loaded if count == 0
126
+
127
+ if @reflection.options[:limit]
128
+ count = [ @reflection.options[:limit], count ].min
129
+ end
130
+
131
+ return count
132
+ end
133
+
134
+ def has_cached_counter?
135
+ @owner.attribute_present?(cached_counter_attribute_name)
136
+ end
137
+
138
+ def cached_counter_attribute_name
139
+ "#{@reflection.name}_count"
140
+ end
141
+
142
+ def insert_record(record)
143
+ set_belongs_to_association_for(record)
144
+ record.save
145
+ end
146
+
147
+ def delete_records(records)
148
+ if @reflection.options[:dependent]
149
+ records.each { |r| r.destroy }
150
+ else
151
+ ids = quoted_record_ids(records)
152
+ @reflection.klass.update_all(
153
+ "#{@reflection.primary_key_name} = NULL",
154
+ "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
155
+ )
156
+ end
157
+ end
158
+
159
+ def target_obsolete?
160
+ false
161
+ end
162
+
163
+ def construct_sql
164
+ case
165
+ when @reflection.options[:finder_sql]
166
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
167
+
168
+ when @reflection.options[:as]
169
+ @finder_sql =
170
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
171
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
172
+ @finder_sql << " AND (#{conditions})" if conditions
173
+
174
+ else
175
+ @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
176
+ @finder_sql << " AND (#{conditions})" if conditions
177
+ end
178
+
179
+ if @reflection.options[:counter_sql]
180
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
181
+ elsif @reflection.options[:finder_sql]
182
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
183
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
184
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
185
+ else
186
+ @counter_sql = @finder_sql
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,80 @@
1
+ module BigRecord
2
+ module ArAssociations
3
+ class HasOneAssociation < BelongsToAssociation #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ construct_sql
7
+ end
8
+
9
+ def create(attributes = {}, replace_existing = true)
10
+ record = build(attributes, replace_existing)
11
+ record.save
12
+ record
13
+ end
14
+
15
+ def build(attributes = {}, replace_existing = true)
16
+ record = @reflection.klass.new(attributes)
17
+
18
+ if replace_existing
19
+ replace(record, true)
20
+ else
21
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
22
+ self.target = record
23
+ end
24
+
25
+ record
26
+ end
27
+
28
+ def replace(obj, dont_save = false)
29
+ load_target
30
+
31
+ unless @target.nil?
32
+ if dependent? && !dont_save && @target != obj
33
+ @target.destroy unless @target.new_record?
34
+ @owner.clear_association_cache
35
+ else
36
+ @target[@reflection.primary_key_name] = nil
37
+ @target.save unless @owner.new_record? || @target.new_record?
38
+ end
39
+ end
40
+
41
+ if obj.nil?
42
+ @target = nil
43
+ else
44
+ raise_on_type_mismatch(obj)
45
+ set_belongs_to_association_for(obj)
46
+ @target = (AssociationProxy === obj ? obj.target : obj)
47
+ end
48
+
49
+ @loaded = true
50
+
51
+ unless @owner.new_record? or obj.nil? or dont_save
52
+ return (obj.save ? self : false)
53
+ else
54
+ return (obj.nil? ? nil : self)
55
+ end
56
+ end
57
+
58
+ private
59
+ def find_target
60
+ @reflection.klass.find(:first,
61
+ :conditions => @finder_sql,
62
+ :order => @reflection.options[:order],
63
+ :include => @reflection.options[:include]
64
+ )
65
+ end
66
+
67
+ def construct_sql
68
+ case
69
+ when @reflection.options[:as]
70
+ @finder_sql =
71
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
72
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
73
+ else
74
+ @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
75
+ end
76
+ @finder_sql << " AND (#{conditions})" if conditions
77
+ end
78
+ end
79
+ end
80
+ end