georgepalmer-couch_foo 0.7.1

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.
@@ -0,0 +1,57 @@
1
+ module CouchFoo
2
+ module Associations
3
+ class BelongsToAssociation < AssociationProxy #:nodoc:
4
+ def create(attributes = {})
5
+ replace(@reflection.klass.create(attributes))
6
+ end
7
+
8
+ def build(attributes = {})
9
+ replace(@reflection.klass.new(attributes))
10
+ end
11
+
12
+ def replace(record)
13
+ counter_cache_name = @reflection.counter_cache_property
14
+
15
+ if record.nil?
16
+ if counter_cache_name && !@owner.new_record?
17
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
18
+ end
19
+
20
+ @target = @owner[@reflection.primary_key_name] = nil
21
+ else
22
+ raise_on_type_mismatch(record)
23
+
24
+ if counter_cache_name && !@owner.new_record?
25
+ @reflection.klass.increment_counter(counter_cache_name, record.id)
26
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
27
+ end
28
+
29
+ @target = (AssociationProxy === record ? record.target : record)
30
+ @owner[@reflection.primary_key_name] = record.id unless record.new_record?
31
+ @updated = true
32
+ end
33
+
34
+ loaded
35
+ record
36
+ end
37
+
38
+ def updated?
39
+ @updated
40
+ end
41
+
42
+ private
43
+ def find_target
44
+ @reflection.klass.find(
45
+ @owner[@reflection.primary_key_name],
46
+ :conditions => conditions,
47
+ :include => @reflection.options[:include],
48
+ :readonly => @reflection.options[:readonly]
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,48 @@
1
+ module CouchFoo
2
+ module Associations
3
+ class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
4
+ def replace(record)
5
+ if record.nil?
6
+ @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
7
+ else
8
+ @target = (AssociationProxy === record ? record.target : record)
9
+
10
+ @owner[@reflection.primary_key_name] = record.id
11
+ @owner[@reflection.options[:foreign_type]] = record.class.name.to_s
12
+
13
+ @updated = true
14
+ end
15
+
16
+ loaded
17
+ record
18
+ end
19
+
20
+ def updated?
21
+ @updated
22
+ end
23
+
24
+ private
25
+ def find_target
26
+ return nil if association_class.nil?
27
+
28
+ if @reflection.options[:conditions]
29
+ association_class.find(
30
+ @owner[@reflection.primary_key_name],
31
+ :conditions => conditions,
32
+ :include => @reflection.options[:include]
33
+ )
34
+ else
35
+ association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
36
+ end
37
+ end
38
+
39
+ def foreign_key_present
40
+ !@owner[@reflection.primary_key_name].nil?
41
+ end
42
+
43
+ def association_class
44
+ @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,111 @@
1
+ #TODO column, sql, record and table search
2
+ module CouchFoo
3
+ module Associations
4
+ class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
5
+ def create(attributes = {})
6
+ create_record(attributes) { |record| insert_record(record, true) }
7
+ end
8
+
9
+ def create!(attributes = {})
10
+ create_record(attributes) { |record| insert_record(record, true) }
11
+ end
12
+
13
+ protected
14
+ def construct_find_options!(options)
15
+ options[:joins] = @join_sql
16
+ options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
17
+ end
18
+
19
+ def count_records
20
+ load_target.size
21
+ end
22
+
23
+ def insert_record(record, force=true)
24
+ if record.new_record?
25
+ if force
26
+ record.save!
27
+ else
28
+ return false unless record.save
29
+ end
30
+ end
31
+
32
+ if @reflection.options[:insert_sql]
33
+ @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
34
+ else
35
+ columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
36
+
37
+ attributes = columns.inject({}) do |attrs, column|
38
+ case column.name.to_s
39
+ when @reflection.primary_key_name.to_s
40
+ attrs[column.name] = @owner.quoted_id
41
+ when @reflection.association_foreign_key.to_s
42
+ attrs[column.name] = record.quoted_id
43
+ else
44
+ if record.has_attribute?(column.name)
45
+ value = @owner.send(:quote_value, record[column.name], column)
46
+ attrs[column.name] = value unless value.nil?
47
+ end
48
+ end
49
+ attrs
50
+ end
51
+
52
+ sql =
53
+ "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
54
+ "VALUES (#{attributes.values.join(', ')})"
55
+
56
+ @owner.connection.insert(sql)
57
+ end
58
+
59
+ return true
60
+ end
61
+
62
+ def delete_records(records)
63
+ if sql = @reflection.options[:delete_sql]
64
+ records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
65
+ else
66
+ ids = quoted_record_ids(records)
67
+ sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
68
+ @owner.connection.delete(sql)
69
+ end
70
+ end
71
+
72
+ def construct_sql
73
+ if @reflection.options[:finder_sql]
74
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
75
+ else
76
+ @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
77
+ @finder_sql << " AND (#{conditions})" if conditions
78
+ end
79
+
80
+ @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
81
+ end
82
+
83
+ def construct_scope
84
+ { :find => { :conditions => @finder_sql,
85
+ :joins => @join_sql,
86
+ :readonly => false,
87
+ :order => @reflection.options[:order],
88
+ :include => @reflection.options[:include],
89
+ :limit => @reflection.options[:limit] } }
90
+ end
91
+
92
+ # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
93
+ # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
94
+ # an id column. This will then overwrite the id column of the records coming back.
95
+ def finding_with_ambiguous_select?(select_clause)
96
+ !select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
97
+ end
98
+
99
+ private
100
+ def create_record(attributes, &block)
101
+ # Can't use Base.create because the foreign key may be a protected attribute.
102
+ ensure_owner_is_not_new
103
+ if attributes.is_a?(Array)
104
+ attributes.collect { |attr| create(attr) }
105
+ else
106
+ build_record(attributes, &block)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,97 @@
1
+ module CouchFoo
2
+ module Associations
3
+ class HasManyAssociation < AssociationCollection #:nodoc:
4
+ # Count the number of associated records.
5
+ # With CouchDB it does not make sense to have a second view for this count as it is likely
6
+ # that at some point the developer will access the objects themselves via find and thus create
7
+ # a suitable view. With CouchDB > 0.8 we can use the reduce function but for earlier
8
+ # versions we just do a find and count the results
9
+ def count(*args)
10
+ options = args.extract_options!
11
+ options[:conditions] = @association_conditions.merge(options[:conditions] || {})
12
+ if database.version > 0.8
13
+ value = count_view(options)
14
+ else
15
+ value = find(:all, options).size
16
+ end
17
+
18
+ limit = @reflection.options[:limit]
19
+ offset = @reflection.options[:offset]
20
+
21
+ if limit || offset
22
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
23
+ else
24
+ value
25
+ end
26
+ end
27
+
28
+ protected
29
+ def count_records
30
+ count = if has_cached_counter?
31
+ @owner.send(:read_attribute, cached_counter_attribute_name)
32
+ else
33
+ @reflection.klass.count(:conditions => @association_conditions, :include => @reflection.options[:include])
34
+ end
35
+
36
+ # If there's nothing in the database and @target has no new records
37
+ # we are certain the current target is an empty array. This is a
38
+ # documented side-effect of the method that may avoid an extra SELECT.
39
+ @target ||= [] and loaded if count == 0
40
+
41
+ if @reflection.options[:limit]
42
+ count = [ @reflection.options[:limit], count ].min
43
+ end
44
+
45
+ return count
46
+ end
47
+
48
+ def has_cached_counter?
49
+ @owner.attribute_present?(cached_counter_attribute_name)
50
+ end
51
+
52
+ def cached_counter_attribute_name
53
+ "#{@reflection.name}_count"
54
+ end
55
+
56
+ def insert_record(record)
57
+ set_belongs_to_association_for(record)
58
+ record.save
59
+ end
60
+
61
+ def delete_records(documents)
62
+ case @reflection.options[:dependent]
63
+ when :destroy
64
+ documents.each(&:destroy)
65
+ when :delete_all
66
+ @reflection.klass.delete(documents.map(&:id))
67
+ else
68
+ find(documents.map{|d| d.id }, :conditions => @association_conditions).each {|doc| doc.update_attributes({@reflection.primary_key_name => nil}, true)}
69
+ database.commit
70
+ end
71
+ end
72
+
73
+ def target_obsolete?
74
+ false
75
+ end
76
+
77
+ def construct_conditions
78
+ if @reflection.options[:as]
79
+ @association_conditions = {"#{@reflection.options[:as]}_id".to_sym => @owner.id,
80
+ "#{@reflection.options[:as]}_type".to_sym => @owner.class.name.to_s}
81
+ else
82
+ @association_conditions = {@reflection.primary_key_name => @owner.id}
83
+ end
84
+ @association_conditions.merge!(conditions) if conditions
85
+ end
86
+
87
+ def construct_scope
88
+ create_scoping = {}
89
+ set_belongs_to_association_for(create_scoping)
90
+ {
91
+ :find => { :conditions => @association_conditions, :readonly => false, :order => @reflection.options[:order], :offset => @reflection.options[:offset], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
92
+ :create => create_scoping
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,95 @@
1
+ module CouchFoo
2
+ module Associations
3
+ class HasOneAssociation < BelongsToAssociation #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ construct_conditions
7
+ end
8
+
9
+ def create(attrs = {}, replace_existing = true)
10
+ new_record(replace_existing) { |klass| klass.create(attrs) }
11
+ end
12
+
13
+ def create!(attrs = {}, replace_existing = true)
14
+ new_record(replace_existing) { |klass| klass.create!(attrs) }
15
+ end
16
+
17
+ def build(attrs = {}, replace_existing = true)
18
+ new_record(replace_existing) { |klass| klass.new(attrs) }
19
+ end
20
+
21
+ def replace(obj, dont_save = false)
22
+ load_target
23
+
24
+ unless @target.nil? || @target == obj
25
+ if dependent? && !dont_save
26
+ @target.destroy unless @target.new_record?
27
+ @owner.clear_association_cache
28
+ else
29
+ @target[@reflection.primary_key_name] = nil
30
+ @target.save unless @owner.new_record? || @target.new_record?
31
+ end
32
+ end
33
+
34
+ if obj.nil?
35
+ @target = nil
36
+ else
37
+ raise_on_type_mismatch(obj)
38
+ set_belongs_to_association_for(obj)
39
+ @target = (AssociationProxy === obj ? obj.target : obj)
40
+ end
41
+
42
+ @loaded = true
43
+
44
+ unless @owner.new_record? or obj.nil? or dont_save
45
+ return (obj.save ? self : false)
46
+ else
47
+ return (obj.nil? ? nil : self)
48
+ end
49
+ end
50
+
51
+ private
52
+ def find_target
53
+ @reflection.klass.find(:first,
54
+ :conditions => @association_conditions,
55
+ :order => @reflection.options[:order],
56
+ :include => @reflection.options[:include],
57
+ :readonly => @reflection.options[:readonly]
58
+ )
59
+ end
60
+
61
+ def construct_conditions
62
+ if @reflection.options[:as]
63
+ @association_conditions = {"#{@reflection.options[:as]}_id".to_sym => @owner.id,
64
+ "#{@reflection.options[:as]}_type".to_sym => @owner.class.name.to_s}
65
+ else
66
+ @association_conditions = {@reflection.primary_key_name => @owner.id}
67
+ end
68
+ @association_conditions.merge!(conditions) if conditions
69
+ end
70
+
71
+ def construct_scope
72
+ create_scoping = {}
73
+ set_belongs_to_association_for(create_scoping)
74
+ { :create => create_scoping }
75
+ end
76
+
77
+ def new_record(replace_existing)
78
+ # Make sure we load the target first, if we plan on replacing the existing
79
+ # instance. Otherwise, if the target has not previously been loaded
80
+ # elsewhere, the instance we create will get orphaned.
81
+ load_target if replace_existing
82
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
83
+
84
+ if replace_existing
85
+ replace(record, true)
86
+ else
87
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
88
+ self.target = record
89
+ end
90
+
91
+ record
92
+ end
93
+ end
94
+ end
95
+ end