Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added that Base#find takes an optional options hash, including :condi…

…tions. Base#find_on_conditions deprecated in favor of #find with :conditions #407 [bitsweat]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@305 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 6bd672eb0d50fcf3437d7fa244245397747bf7a7 1 parent 86df396
@dhh dhh authored
View
2  activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Added that Base#find takes an optional options hash, including :conditions. Base#find_on_conditions deprecated in favor of #find with :conditions #407 [bitsweat]
+
* Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 [Maik Schmidt]
* Added the final touches to the Microsoft SQL Server adapter by DeLynn Berry that makes it suitable for actual use #394 [DeLynn Barry]
View
39 activerecord/lib/active_record/associations/association_collection.rb
@@ -100,13 +100,25 @@ def interpolate_sql_options!(options, *keys)
def interpolate_sql(sql, record = nil)
@owner.send(:interpolate_sql, sql, record)
end
-
+
+ def sanitize_sql(sql)
+ @association_class.send(:sanitize_sql, sql)
+ end
+
+ def extract_options_from_args!(args)
+ @owner.send(:extract_options_from_args!, args)
+ end
+
private
def load_collection
- begin
- @collection = find_all_records unless loaded?
- rescue ActiveRecord::RecordNotFound
- @collection = []
+ if loaded?
+ @collection
+ else
+ begin
+ @collection = find_all_records
+ rescue ActiveRecord::RecordNotFound
+ @collection = []
+ end
end
end
@@ -114,25 +126,10 @@ def raise_on_type_mismatch(record)
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
end
-
- def load_collection_to_array
- return unless @collection_array.nil?
- begin
- @collection_array = find_all_records
- rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotFound
- @collection_array = []
- end
- end
-
- def duplicated_records_array(records)
- records = [records] unless records.is_a?(Array) || records.is_a?(ActiveRecord::Associations::AssociationCollection)
- records.dup
- end
-
# Array#flatten has problems with rescursive arrays. Going one level deeper solves the majority of the problems.
def flatten_deeper(array)
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
end
end
end
-end
+end
View
46 activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -38,15 +38,42 @@ def clear
self
end
- def find(association_id = nil, &block)
- if block_given? || @options[:finder_sql]
- load_collection
- @collection.find(&block)
+ def find_first
+ load_collection.first
+ end
+
+ def find(*args)
+ # Return an Array if multiple ids are given.
+ expects_array = args.first.kind_of?(Array)
+
+ ids = args.flatten.compact.uniq
+
+ # If no block is given, raise RecordNotFound.
+ if ids.empty?
+ raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID#{conditions}"
+
+ # If using a custom finder_sql, scan the entire collection.
+ elsif @options[:finder_sql]
+ if ids.size == 1
+ id = ids.first
+ record = load_collection.detect { |record| id == record.id }
+ expects_array? ? [record] : record
+ else
+ load_collection.select { |record| ids.include?(record.id) }
+ end
+
+ # Otherwise, construct a query.
else
- if loaded?
- find_all { |record| record.id == association_id.to_i }.first
+ ids_list = ids.map { |id| @owner.send(:quote, id) }.join(',')
+ records = find_all_records(@finder_sql.sub(/ORDER BY/, "AND j.#{@association_foreign_key} IN (#{ids_list}) ORDER BY"))
+ if records.size == ids.size
+ if ids.size == 1 and !expects_array
+ records.first
+ else
+ records
+ end
else
- find_all_records(@finder_sql.sub(/ORDER BY/, "AND j.#{@association_foreign_key} = #{@owner.send(:quote, association_id)} ORDER BY")).first
+ raise RecordNotFound, "Couldn't find #{@association_class.name} with ID in (#{ids_list})"
end
end
end
@@ -70,10 +97,9 @@ def find_all_records(sql = @finder_sql)
records = @association_class.find_by_sql(sql)
@options[:uniq] ? uniq(records) : records
end
-
+
def count_records
- load_collection
- @collection.size
+ load_collection.size
end
def insert_record(record)
View
64 activerecord/lib/active_record/associations/has_many_association.rb
@@ -3,12 +3,13 @@ module Associations
class HasManyAssociation < AssociationCollection #:nodoc:
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
super(owner, association_name, association_class_name, association_class_primary_key_name, options)
- @conditions = @association_class.send(:sanitize_conditions, options[:conditions])
+ @conditions = sanitize_sql(options[:conditions])
if options[:finder_sql]
@finder_sql = interpolate_sql(options[:finder_sql])
else
- @finder_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id} #{@conditions ? " AND " + interpolate_sql(@conditions) : ""}"
+ @finder_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql << " AND #{@conditions}" if @conditions
end
if options[:counter_sql]
@@ -35,29 +36,46 @@ def build(attributes = {})
record
end
- def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil, &block)
- if block_given? || @options[:finder_sql]
- load_collection
- @collection.find_all(&block)
+ def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
+ if @options[:finder_sql]
+ records = @association_class.find_by_sql(@finder_sql)
else
- @association_class.find_all(
- "#{@association_class_primary_key_name} = #{@owner.quoted_id}" +
- "#{@conditions ? " AND " + @conditions : ""}#{runtime_conditions ? " AND " + @association_class.send(:sanitize_conditions, runtime_conditions) : ""}",
- orderings,
- limit,
- joins
- )
+ sql = @finder_sql
+ sql << " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
+ orderings ||= @options[:order]
+ records = @association_class.find_all(sql, orderings, limit, joins)
end
end
- def find(association_id = nil, &block)
- if block_given? || @options[:finder_sql]
- load_collection
- @collection.find(&block)
+ # Find the first associated record. All arguments are optional.
+ def find_first(conditions = nil, orderings = nil)
+ find_all(conditions, orderings, 1).first
+ end
+
+ def find(*args)
+ # Return an Array if multiple ids are given.
+ expects_array = args.first.kind_of?(Array)
+
+ ids = args.flatten.compact.uniq
+
+ # If no ids given, raise RecordNotFound.
+ if ids.empty?
+ raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID"
+
+ # If using a custom finder_sql, scan the entire collection.
+ elsif @options[:finder_sql]
+ if ids.size == 1
+ id = ids.first
+ record = load_collection.detect { |record| id == record.id }
+ expects_array? ? [record] : record
+ else
+ load_collection.select { |record| ids.include?(record.id) }
+ end
+
+ # Otherwise, delegate to association class with conditions.
else
- @association_class.find_on_conditions(association_id,
- "#{@association_class_primary_key_name} = #{@owner.quoted_id}#{@conditions ? " AND " + @conditions : ""}"
- )
+ args << { :conditions => "#{@association_class_primary_key_name} = '#{@owner.id}' #{@conditions ? " AND " + @conditions : ""}" }
+ @association_class.find(*args)
end
end
@@ -71,11 +89,7 @@ def clear
protected
def find_all_records
- if @options[:finder_sql]
- @association_class.find_by_sql(@finder_sql)
- else
- @association_class.find_all(@finder_sql, @options[:order] ? @options[:order] : nil)
- end
+ find_all
end
def count_records
View
132 activerecord/lib/active_record/base.rb
@@ -236,44 +236,58 @@ class << self # Class methods
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
# Person.find([1]) # returns an array for objects the object with ID = 1
+ #
+ # The last argument may be a Hash of find options. Currently, +conditions+ is the only option, behaving the same as with +find_all+.
+ # Person.find(1, :conditions => "associate_id='5'"
+ # Person.find(1, 2, 6, :conditions => "status='active'"
+ # Person.find([7, 17], :conditions => ["sanitize_me='%s'", "bare'quote"]
+ #
# +RecordNotFound+ is raised if no record can be found.
- def find(*ids)
- expects_array = ids.first.kind_of?(Array)
- ids = ids.flatten.compact.uniq
+ def find(*args)
+ # Return an Array if ids are passed in an Array.
+ expects_array = args.first.kind_of?(Array)
+
+ # Extract options hash from argument list.
+ options = extract_options_from_args!(args)
+ conditions = " AND #{sanitize_sql(options[:conditions])}" if options[:conditions]
+
+ ids = args.flatten.compact.uniq
+ case ids.size
+
+ # Raise if no ids passed.
+ when 0
+ raise RecordNotFound, "Couldn't find #{name} without an ID#{conditions}"
+
+ # Find a single id.
+ when 1
+ unless result = find_first("#{primary_key} = #{sanitize(ids.first)}#{conditions}")
+ raise RecordNotFound, "Couldn't find #{name} with ID=#{ids.first}#{conditions}"
+ end
- if ids.length > 1
- ids_list = ids.map{ |id| "#{sanitize(id)}" }.join(", ")
- objects = find_all("#{primary_key} IN (#{ids_list})", primary_key)
+ # Box result if expecting array.
+ expects_array ? [result] : result
- if objects.length == ids.length
- return objects
+ # Find multiple ids.
else
- raise RecordNotFound, "Couldn't find #{name} with ID in (#{ids_list})"
- end
- elsif ids.length == 1
- id = ids.first
- sql = "SELECT * FROM #{table_name} WHERE #{primary_key} = #{sanitize(id)}"
- sql << " AND #{type_condition}" unless descends_from_active_record?
-
- if record = connection.select_one(sql, "#{name} Find")
- expects_array ? [instantiate(record)] : instantiate(record)
- else
- raise RecordNotFound, "Couldn't find #{name} with ID = #{id}"
- end
- else
- raise RecordNotFound, "Couldn't find #{name} without an ID"
+ ids_list = ids.map { |id| sanitize(id) }.join(',')
+ result = find_all("#{primary_key} IN (#{ids_list})#{conditions}", primary_key)
+ if result.size == ids.size
+ result
+ else
+ raise RecordNotFound, "Couldn't find #{name} with ID in (#{ids_list})#{conditions}"
+ end
end
end
+ # This method is deprecated in favor of find with the :conditions option.
# Works like find, but the record matching +id+ must also meet the +conditions+.
# +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
# Example:
# Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
- def find_on_conditions(id, conditions)
- find_first("#{primary_key} = #{sanitize(id)} AND #{sanitize_conditions(conditions)}") ||
- raise(RecordNotFound, "Couldn't find #{name} with #{primary_key} = #{id} on the condition of #{conditions}")
+ def find_on_conditions(ids, conditions)
+ find(ids, :conditions => conditions)
end
-
+
# Returns an array of all the objects that could be instantiated from the associated
# table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
# such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
@@ -287,7 +301,7 @@ def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)
add_conditions!(sql, conditions)
sql << "ORDER BY #{orderings} " unless orderings.nil?
- connection.add_limit!(sql, sanitize_conditions(limit)) unless limit.nil?
+ connection.add_limit!(sql, sanitize_sql(limit)) unless limit.nil?
find_by_sql(sql)
end
@@ -296,8 +310,7 @@ def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)
# Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
# Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
def find_by_sql(sql)
- sql = sanitize_conditions(sql)
- connection.select_all(sql, "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
+ connection.select_all(sanitize_sql(sql), "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
end
# Returns the object for the first record responding to the conditions in +conditions+,
@@ -306,14 +319,7 @@ def find_by_sql(sql)
# +orderings+, like "income DESC, name", to control exactly which record is to be used. Example:
# Employee.find_first "income > 50000", "income DESC, name"
def find_first(conditions = nil, orderings = nil)
- sql = "SELECT * FROM #{table_name} "
- add_conditions!(sql, conditions)
- sql << "ORDER BY #{orderings} " unless orderings.nil?
-
- connection.add_limit!(sql, 1)
-
- record = connection.select_one(sql, "#{name} Load First")
- instantiate(record) unless record.nil?
+ find_all(conditions, orderings, 1).first
end
# Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
@@ -613,7 +619,7 @@ def type_name_with_module(type_name)
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
def add_conditions!(sql, conditions)
- sql << "WHERE #{sanitize_conditions(conditions)} " unless conditions.nil?
+ sql << "WHERE #{sanitize_sql(conditions)} " unless conditions.nil?
sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descends_from_active_record?
end
@@ -656,51 +662,49 @@ def class_name_of_active_record_descendant(klass)
end
end
- # Accepts either a condition array or string. The string is returned untouched, but the array has each of
- # the condition values sanitized.
- def sanitize_conditions(conditions)
- return conditions unless conditions.is_a?(Array)
+ # Accepts an array or string. The string is returned untouched, but the array has each value
+ # sanitized and interpolated into the sql statement.
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
+ def sanitize_sql(ary)
+ return ary unless ary.is_a?(Array)
- statement, *values = conditions
-
- if values[0].is_a?(Hash) && statement =~ /:\w+/
- replace_named_bind_variables(statement, values[0])
- elsif statement =~ /\?/
+ statement, *values = ary
+ if values.first.is_a?(Hash) and statement =~ /:\w+/
+ replace_named_bind_variables(statement, values.first)
+ elsif statement.include?('?')
replace_bind_variables(statement, values)
else
statement % values.collect { |value| connection.quote_string(value.to_s) }
end
end
+ alias_method :sanitize_conditions, :sanitize_sql
+
def replace_bind_variables(statement, values)
- orig_statement = statement.clone
expected_number_of_variables = statement.count('?')
provided_number_of_variables = values.size
unless expected_number_of_variables == provided_number_of_variables
- raise PreparedStatementInvalid, "wrong number of bind variables (#{provided_number_of_variables} for #{expected_number_of_variables})"
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided_number_of_variables} for #{expected_number_of_variables}) in: #{statement}"
end
- until values.empty?
- statement.sub!(/\?/, encode_quoted_value(values.shift))
- end
-
- statement.gsub('?') { |all, match| connection.quote(values.shift) }
+ bound = values.dup
+ statement.gsub('?') { connection.quote(bound.shift) }
end
- def replace_named_bind_variables(statement, values_hash)
- orig_statement = statement.clone
- values_hash.keys.each do |k|
- if statement.sub!(/:#{k.id2name}/, encode_quoted_value(values_hash.delete(k))).nil?
- raise PreparedStatementInvalid, ":#{k} is not a variable in [#{orig_statement}]"
+ def replace_named_bind_variables(statement, bind_vars)
+ statement.gsub(/:(\w+)/) do
+ match = $1.to_sym
+ if bind_vars.has_key?(match)
+ connection.quote(bind_vars[match])
+ else
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
end
end
+ end
- if statement =~ /(:\w+)/
- raise PreparedStatementInvalid, "No value provided for #{$1} in [#{orig_statement}]"
- end
-
- return statement
+ def extract_options_from_args!(args)
+ if args.last.is_a?(Hash) then args.pop else {} end
end
def encode_quoted_value(value)
View
37 activerecord/test/associations_test.rb
@@ -183,9 +183,31 @@ def test_counting_using_sql
assert_equal 0, Firm.find_first.clients_using_zero_counter_sql.size
end
+ def test_find_ids
+ firm = Firm.find_first
+
+ assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find }
+
+ client = firm.clients.find(2)
+ assert_kind_of Client, client
+
+ client_ary = firm.clients.find([2])
+ assert_kind_of Array, client_ary
+ assert_equal client, client_ary.first
+
+ client_ary = firm.clients.find(2, 3)
+ assert_kind_of Array, client_ary
+ assert_equal 2, client_ary.size
+ assert_equal client, client_ary.first
+
+ assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) }
+ end
+
def test_find_all
- assert_equal 2, Firm.find_first.clients.find_all("type = 'Client'").length
- assert_equal 1, Firm.find_first.clients.find_all("name = 'Summit'").length
+ firm = Firm.find_first
+ assert_equal firm.clients, firm.clients.find_all
+ assert_equal 2, firm.clients.find_all("type = 'Client'").length
+ assert_equal 1, firm.clients.find_all("name = 'Summit'").length
end
def test_find_all_sanitized
@@ -193,9 +215,18 @@ def test_find_all_sanitized
assert_equal firm.clients.find_all("name = 'Summit'"), firm.clients.find_all(["name = '%s'", "Summit"])
end
+ def test_find_first
+ firm = Firm.find_first
+ assert_equal firm.clients.first, firm.clients.find_first
+ assert_equal Client.find(2), firm.clients.find_first("type = 'Client'")
+ end
+
+ def test_find_first_sanitized
+ assert_equal Client.find(2), Firm.find_first.clients.find_first(["type = ?", "Client"])
+ end
+
def test_find_in_collection
assert_equal Client.find(2).name, @signals37.clients.find(2).name
- assert_equal Client.find(2).name, @signals37.clients.find {|c| c.name == @signals37.clients.find(2).name }.name
assert_raises(ActiveRecord::RecordNotFound) { @signals37.clients.find(6) }
end
View
26 activerecord/test/deprecated_associations_test.rb
@@ -286,9 +286,11 @@ def test_natural_assignment_of_has_many
natural = Client.create("name" => "Natural Company")
apple.clients << natural
assert_equal apple.id, natural.firm_id
- assert_equal Client.find(natural.id), Firm.find(apple.id).clients.find { |c| c.id == natural.id }
+ assert_equal Client.find(natural.id), Firm.find(apple.id).clients.find(natural.id)
apple.clients.delete natural
- assert_nil Firm.find(apple.id).clients.find { |c| c.id == natural.id }
+ assert_raises(ActiveRecord::RecordNotFound) {
+ Firm.find(apple.id).clients.find(natural.id)
+ }
end
def test_natural_adding_of_has_and_belongs_to_many
@@ -299,17 +301,21 @@ def test_natural_adding_of_has_and_belongs_to_many
rails.developers << john
rails.developers << mike
- assert_equal Developer.find(john.id), Project.find(rails.id).developers.find { |d| d.id == john.id }
- assert_equal Developer.find(mike.id), Project.find(rails.id).developers.find { |d| d.id == mike.id }
- assert_equal Project.find(rails.id), Developer.find(mike.id).projects.find { |p| p.id == rails.id }
- assert_equal Project.find(rails.id), Developer.find(john.id).projects.find { |p| p.id == rails.id }
+ assert_equal Developer.find(john.id), Project.find(rails.id).developers.find(john.id)
+ assert_equal Developer.find(mike.id), Project.find(rails.id).developers.find(mike.id)
+ assert_equal Project.find(rails.id), Developer.find(mike.id).projects.find(rails.id)
+ assert_equal Project.find(rails.id), Developer.find(john.id).projects.find(rails.id)
ap.developers << john
- assert_equal Developer.find(john.id), Project.find(ap.id).developers.find { |d| d.id == john.id }
- assert_equal Project.find(ap.id), Developer.find(john.id).projects.find { |p| p.id == ap.id }
+ assert_equal Developer.find(john.id), Project.find(ap.id).developers.find(john.id)
+ assert_equal Project.find(ap.id), Developer.find(john.id).projects.find(ap.id)
ap.developers.delete john
- assert_nil Project.find(ap.id).developers.find { |d| d.id == john.id }
- assert_nil Developer.find(john.id).projects.find { |p| p.id == ap.id }
+ assert_raises(ActiveRecord::RecordNotFound) {
+ Project.find(ap.id).developers.find(john.id)
+ }
+ assert_raises(ActiveRecord::RecordNotFound) {
+ Developer.find(john.id).projects.find(ap.id)
+ }
end
def test_storing_in_pstore
View
34 activerecord/test/finder_test.rb
@@ -77,6 +77,11 @@ def test_unexisting_record_exception_handling
end
def test_find_on_conditions
+ assert Topic.find(1, :conditions => "approved = 0")
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => "approved = 1") }
+ end
+
+ def test_deprecated_find_on_conditions
assert Topic.find_on_conditions(1, "approved = 0")
assert_raises(ActiveRecord::RecordNotFound) { Topic.find_on_conditions(1, "approved = 1") }
end
@@ -111,7 +116,19 @@ def test_named_bind_variables_with_quotes
assert Company.find_first(["name = :name", {:name => "37signals' go'es agains"}])
end
+ def test_bind_arity
+ assert_nothing_raised { bind '' }
+ assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
+
+ assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
+ assert_nothing_raised { bind '?', 1 }
+ assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
+ end
+
def test_named_bind_variables
+ assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
+ assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
+
assert_kind_of Firm, Company.find_first(["name = :name", { :name => "37signals" }])
assert_nil Company.find_first(["name = :name", { :name => "37signals!" }])
assert_nil Company.find_first(["name = :name", { :name => "37signals!' OR 1=1" }])
@@ -124,7 +141,13 @@ def test_named_bind_variables
}
end
-
+ def test_named_bind_arity
+ assert_nothing_raised { bind '', {} }
+ assert_nothing_raised { bind '', :a => 1 }
+ assert_raises(ActiveRecord::PreparedStatementInvalid) { bind ':a', {} } # ' ruby-mode
+ assert_nothing_raised { bind ':a', :a => 1 } # ' ruby-mode
+ assert_nothing_raised { bind ':a', :a => 1, :b => 2 } # ' ruby-mode
+ end
def test_string_sanitation
assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
@@ -142,4 +165,13 @@ def test_count_by_sql
assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
end
+
+ protected
+ def bind(statement, *vars)
+ if vars.first.is_a?(Hash)
+ ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
+ else
+ ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
+ end
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.