bigrecord 0.0.5

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.
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,194 @@
1
+ module BigRecord
2
+ module BrAssociations
3
+ module CachedItemProxy #:nodoc:
4
+
5
+ CACHE_ATTRIBUTE = "attribute:associations_cache"
6
+
7
+ attr_reader :reflection
8
+ alias_method :proxy_respond_to?, :respond_to?
9
+ alias_method :proxy_extend, :extend
10
+ # delegate :to_param, :to => :proxy_target
11
+ # instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
12
+
13
+ def proxy_cache
14
+ @owner[CACHE_ATTRIBUTE] ||= {}
15
+ @owner[CACHE_ATTRIBUTE]["#{@reflection.klass.name}:#{id}"] ||= {}
16
+ end
17
+
18
+ def proxy_owner
19
+ @owner
20
+ end
21
+
22
+ def proxy_reflection
23
+ @reflection
24
+ end
25
+
26
+ def proxy_target
27
+ @target
28
+ end
29
+
30
+ def respond_to?(symbol, include_priv = false)
31
+ proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
32
+ end
33
+
34
+ # Explicitly proxy === because the instance method removal above
35
+ # doesn't catch it.
36
+ def ===(other)
37
+ load_target
38
+ other === @target
39
+ end
40
+
41
+ def aliased_table_name
42
+ @reflection.klass.table_name
43
+ end
44
+
45
+ def reset
46
+ @loaded = false
47
+ @target = nil
48
+ end
49
+
50
+ def reload
51
+ reset
52
+ load_target
53
+ end
54
+
55
+ def loaded?
56
+ @loaded
57
+ end
58
+
59
+ def loaded
60
+ @loaded = true
61
+ end
62
+
63
+ def target
64
+ @target
65
+ end
66
+
67
+ def target=(target)
68
+ @target = target
69
+ loaded
70
+ end
71
+
72
+ # # Returns the contents of the record as a nicely formatted string.
73
+ # def inspect
74
+ # if loaded?
75
+ # @target.inspect
76
+ # else
77
+ # attributes_as_nice_string = @reflection.options[:cache].collect { |name|
78
+ # column = @owner.column_for_attribute("attribute:#{name}")
79
+ # "#{name}: #{column.type_cast(proxy_cache[name])}" if column
80
+ # }.compact.join(", ")
81
+ # "#<Cached#{@reflection.klass} #{attributes_as_nice_string}>"
82
+ # end
83
+ # end
84
+
85
+ # def is_a?(klass)
86
+ # @reflection.klass <= klass
87
+ # end
88
+ #
89
+ # def kind_of?(klass)
90
+ # @reflection.klass <= klass
91
+ # end
92
+ #
93
+ # def to_param
94
+ # @id
95
+ # end
96
+
97
+ # protected
98
+ # def dependent?
99
+ # @reflection.options[:dependent] || false
100
+ # end
101
+ #
102
+ # def quoted_record_ids(records)
103
+ # records.map { |record| record.quoted_id }.join(',')
104
+ # end
105
+ #
106
+ ## def interpolate_sql_options!(options, *keys)
107
+ ## keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
108
+ ## end
109
+ ##
110
+ ## def interpolate_sql(sql, record = nil)
111
+ ## @owner.send(:interpolate_sql, sql, record)
112
+ ## end
113
+ ##
114
+ ## def sanitize_sql(sql)
115
+ ## @reflection.klass.send(:sanitize_sql, sql)
116
+ ## end
117
+ #
118
+ # def extract_options_from_args!(args)
119
+ # @owner.send(:extract_options_from_args!, args)
120
+ # end
121
+ #
122
+ # def set_belongs_to_association_for(record)
123
+ # if @reflection.options[:as]
124
+ # record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
125
+ # record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
126
+ # else
127
+ # record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
128
+ # end
129
+ # end
130
+ #
131
+ # def merge_options_from_reflection!(options)
132
+ # options.reverse_merge!(
133
+ # :group => @reflection.options[:group],
134
+ # :limit => @reflection.options[:limit],
135
+ # :offset => @reflection.options[:offset],
136
+ # :joins => @reflection.options[:joins],
137
+ # :include => @reflection.options[:include],
138
+ # :select => @reflection.options[:select]
139
+ # )
140
+ # end
141
+
142
+ # private
143
+ # def method_missing(method_id, *args, &block)
144
+ # if !loaded? and @reflection.options[:cache].include?(method_id)
145
+ # # FIXME: shouldn't be hard coded
146
+ # column = @owner.column_for_attribute("attribute:#{method_id}")
147
+ # if column
148
+ # if proxy_cache.has_key?(method_id)
149
+ # column.type_cast(proxy_cache[method_id])
150
+ # elsif load_target
151
+ # proxy_cache[method_id] = @target.send(method_id, *args, &block)
152
+ # end
153
+ # else
154
+ # @target.send(method_id, *args, &block)
155
+ # end
156
+ # elsif load_target
157
+ # value = @target.send(method_id, *args, &block)
158
+ # proxy_cache[method_id] = value if @reflection.options[:cache].include?(method_id)
159
+ # value
160
+ # end
161
+ # end
162
+
163
+ def load_target
164
+ return nil unless defined?(@loaded)
165
+
166
+ if !loaded? and (!@owner.new_record? || foreign_key_present)
167
+ @target = find_target
168
+ end
169
+
170
+ @loaded = true
171
+ @target
172
+ rescue BigRecord::RecordNotFound
173
+ reset
174
+ end
175
+
176
+ def find_target
177
+ @reflection.klass.find(self.id)
178
+ end
179
+
180
+ # # Can be overwritten by associations that might have the foreign key available for an association without
181
+ # # having the object itself (and still being a new record). Currently, only belongs_to present this scenario.
182
+ # def foreign_key_present
183
+ # false
184
+ # end
185
+ #
186
+ # def raise_on_type_mismatch(record)
187
+ # unless record.is_a?(@reflection.klass)
188
+ # raise BigRecordRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
189
+ # end
190
+ # end
191
+
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,62 @@
1
+ module BigRecord
2
+ module BrAssociations
3
+ class CachedItemProxyFactory
4
+
5
+ include Singleton
6
+
7
+ def create(id, owner, reflection)
8
+ cache = owner[CachedItemProxy::CACHE_ATTRIBUTE]
9
+ cached_attributes = cache["#{reflection.klass.name}:#{id}"] if cache
10
+ cached_attributes ||= {}
11
+ cached_attributes["id"] = id
12
+ proxy = reflection.klass.instantiate(cached_attributes)
13
+ proxy.extend CachedItemProxy
14
+ proxy.instance_variable_set(:@owner, owner)
15
+ proxy.instance_variable_set(:@reflection, reflection)
16
+ proxy.reset
17
+
18
+ # Overload the cached methods
19
+ reflection.options[:cache].each do |attribute_name|
20
+ eval "def proxy.#{attribute_name}\n"+
21
+ " proxy_cache[\"#{attribute_name}\"] ||= super\n"+
22
+ "end"
23
+ end
24
+
25
+ proxy
26
+ end
27
+
28
+ # def extended_class(reflection)
29
+ # @extended_classes ||= {}
30
+ # @extended_classes[reflection.klass.name] ||= create_extended_class(reflection)
31
+ # end
32
+ #
33
+ # def create_extended_class(reflection)
34
+ # extended_class = Class.new(reflection.klass)
35
+ # extended_class.class_eval do
36
+ # include CachedItemProxy
37
+ #
38
+ # attr_reader :reflection
39
+ # alias_method :proxy_respond_to?, :respond_to?
40
+ # alias_method :proxy_extend, :extend
41
+ ## delegate :to_param, :to => :proxy_target
42
+ #
43
+ # # Overload the methods
44
+ # instance_methods.each do |m|
45
+ # if reflection.options[:cache].include?(m.to_sym)
46
+ # define_method m do
47
+ # proxy_cache[m.to_s] ||= super
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # def class
53
+ # reflection.klass
54
+ # end
55
+ #
56
+ # end
57
+ # extended_class
58
+ # end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,168 @@
1
+ module BigRecord
2
+ module BrAssociations
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
+ # Generate the sql query. The join table has to be in mysql.
43
+ conditions = "#{@finder_sql}"
44
+
45
+ if sanitized_conditions = ActiveRecord::Base.send(:sanitize_sql, options[:conditions])
46
+ conditions << " AND (#{sanitized_conditions})"
47
+ end
48
+
49
+ if options[:order] && @reflection.options[:order]
50
+ order = "#{options[:order]}, #{@reflection.options[:order]}"
51
+ elsif @reflection.options[:order]
52
+ order = @reflection.options[:order]
53
+ end
54
+
55
+ query = "SELECT #{@reflection.association_foreign_key} FROM #{@reflection.options[:join_table]} WHERE #{conditions} #{order};"
56
+
57
+ # Execute the query
58
+ ids = []
59
+ ActiveRecord::Base.connection.execute(query).each do |result|
60
+ ids << result.first
61
+ end
62
+
63
+ # Find the big_record entries by id. Find them one at a time because duplicate entries must appear in
64
+ # the result set if it's the case. The default find by ids in big_record does the same thing anyway...
65
+ ids.collect{|id| @reflection.klass.find(id)}
66
+ end
67
+ end
68
+
69
+ protected
70
+ def count_records
71
+ load_target.size
72
+ end
73
+
74
+ def insert_record(record, force=true)
75
+ if record.new_record?
76
+ if force
77
+ record.save!
78
+ else
79
+ return false unless record.save
80
+ end
81
+ end
82
+
83
+ if @reflection.options[:insert_sql]
84
+ ar_connection.execute(interpolate_sql(@reflection.options[:insert_sql], record))
85
+ else
86
+ columns = ar_connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
87
+
88
+ attributes = columns.inject({}) do |attributes, column|
89
+ case column.name
90
+ when @reflection.primary_key_name
91
+ attributes[column.name] = @owner.quoted_id
92
+ when @reflection.association_foreign_key
93
+ attributes[column.name] = record.quoted_id
94
+ else
95
+ if record.attributes.has_key?(column.name)
96
+ value = @owner.send(:quote_value, record[column.name], column)
97
+ attributes[column.name] = value unless value.nil?
98
+ end
99
+ end
100
+ attributes
101
+ end
102
+
103
+ sql =
104
+ "INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
105
+ "VALUES (#{attributes.values.join(', ')})"
106
+
107
+ ar_connection.execute(sql)
108
+ end
109
+
110
+ return true
111
+ end
112
+
113
+ def delete_records(records)
114
+ if sql = @reflection.options[:delete_sql]
115
+ records.each { |record| ar_connection.execute(interpolate_sql(sql, record)) }
116
+ else
117
+ ids = quoted_record_ids(records)
118
+ sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
119
+ ar_connection.execute(sql)
120
+ end
121
+ end
122
+
123
+ def construct_sql
124
+ # interpolate_sql_options!(@reflection.options, :finder_sql)
125
+
126
+ if @reflection.options[:finder_sql]
127
+ @finder_sql = @reflection.options[:finder_sql]
128
+ else
129
+ @finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
130
+ # @finder_sql << " AND (#{conditions})" if conditions
131
+ end
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 && ar_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
+
163
+ def ar_connection
164
+ (@owner.class < ActiveRecord::Base) ? @owner.connection : ActiveRecord::Base.connection
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,80 @@
1
+ module BigRecord
2
+ module BrAssociations
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