Checking / rescuing sql query in rails 5.2 raises and error - ruby-on-rails

The DeployedSkill object references a ValuesList object which provides a limited value domain to users answers. An additional "filter" field contains an expression to filter even more the values of the domain. Values are instances of the Value class, belonging to the ValuesList object.
The model looks like this:
class DeployedSkill < Skill
### before filter
before_validation :set_code
# Custom validation of the filter attribute
validate :values_filter
validates :code, presence: true, uniqueness: {scope: :business_object_id, case_sensitive: false}, length: { maximum: 32 }
belongs_to :template_skill, :class_name => "DefinedSkill", :foreign_key => "template_skill_id" # helps retrieving the skill template
belongs_to :parent, :class_name => "DeployedObject", :foreign_key => "business_object_id"
belongs_to :values_list, inverse_of: :deployed_skills
### private functions definitions
private
### format code
def set_code
if self.code[0, 3] == "DV_"
self.code = self.code[3 .. -1]
end
self.code = "#{code.gsub(/[^0-9A-Za-z]/, '_')}".upcase[0,32]
end
def values_filter
begin
puts "----- Test the filter -----"
Value.where("#{filter}").first
rescue
puts "----- Filter test failed -----"
self.errors.add(:filter, "is not correctly specified")
puts self.errors.map {|error, message| [error, message]}.join(', ')
end
end
end
Before saving the updated DeployedSkill, I validate that the filter expression is compatible with a database query.
Using ByeBug, I can see that the test works fine, and an error is added to ActiveModel::Errors list when filter's syntax is not correct:
#<ActiveModel::Errors:0x0000000012bf03a8 #base=#<DeployedSkill id: 638, playground_id: 0,
business_object_id: 632, code: "SAKO1_METHODE", blablabla ... ,
type: "DeployedSkill", filter: "'xxx'", global_identifier: nil, sort_code: "SAKO1_METHODE",
physical_name: nil>,
#messages={:filter=>["is not correctly specified"]},
#details={:filter=>[{:error=>"is not correctly specified"}]}>
The console echoes it:
----- Test the filter -----
Value Load (10.2ms) SELECT "values".* FROM "values" WHERE (code like 'S%) ORDER BY "values"."id" ASC LIMIT $1 [["LIMIT", 1]]
----- Filter test failed -----
filter, is not correctly specified
(0.8ms) ROLLBACK
Nevertheless, the update method instruction:
if #deployed_skill.update_attributes(deployed_skill_params)
breaks into an error:
PG::InFailedSqlTransaction: ERROR: current transaction was aborted, commands are ignored until the end of the transaction :
SELECT exists(
SELECT * FROM pg_proc
WHERE proname = 'lower' AND proargtypes = ARRAY['character varying(255)'::regtype]::oidvector )
OR exists( SELECT * FROM pg_proc INNER JOIN pg_cast ON ARRAY[casttarget]::oidvector = proargtypes
WHERE proname = 'lower' AND castsource = 'character varying(255)'::regtype )
At this point, I have no idea of what's going on, and I don't know where to search deeper.
I hope you can help me to understand the issue and find the solution!
Thanks!

Related

calling self.class.where() in a transaction, but the records i'm trying to select are ignored

I'm using an ActiveRecord::Base.transaction to make a whole bunch of calls pertaining to a grouping of objects that must update/create simultaneously or not at all. One method in this transaction is supposed to use where to find and delete all Trades that match certain parameters.
class Trade < ActiveRecord::Base
include Discard::Model
belongs_to :trade_requester, class_name: "User"
belongs_to :wanted_share, class_name: "Share"
validates :trade_requester, :wanted_share, presence: true
validates :amount, presence: true
def new_wanted_total
wanted_share.amount - amount
end
def update_wanted_share_amount(new_wanted_total)
wanted_share.update_attribute(:amount, new_wanted_total)
end
def delete_extraneous_wanted_trades(wanted_share)
self.class.where("wanted_share_id = ? AND amount > ? AND approved_at = ? AND discarded_at = ?", wanted_share.id, new_wanted_total, nil, nil).delete_all
end
def accept
ActiveRecord::Base.transaction do
delete_extraneous_wanted_trades(wanted_share)
update_wanted_share_amount(new_wanted_total) if new_wanted_total >= 0
Share.create(user_id: self.trade_requester.id, item_id: self.wanted_share.item.id, amount: self.amount, active: false)
self.touch(:approved_at)
end
end
end
When I accept and check the output in my terminal, one line I get says this:
SQL (0.3ms) DELETE FROM "trades" WHERE (wanted_share_id = 8 AND amount > 25 AND approved_at = NULL AND discarded_at = NULL).
I am passing the correct information to the method, and the rest of the terminal output shows that the related records have been updated with the appropriate attributes (one Share set to amount:25 and another Share created with amount:50). But then I check my database, and it says that there is still one Trade for amount: 60. This record exceeds the available total, which is now 50 (it was previously 75), and should have been deleted. But according to the terminal output, it was ignored. Why did this record go untouched?
approved_at = ? AND discarded_at = ? in the where clause should be approved_at IS NULL AND discarded_at IS NULL

Record Exists error when duplicating records

I'm in the process of trying to duplicate a record and all of its subsequent child associations which are deeply nested. Below is a simplification of the problem.
I have the following Foo model:
class Foo < ActiveRecord::Base
belongs_to :project,
inverse_of::foos
validates :name,presence:true,uniqueness:{ scope: :project_id },
format:{ with: /[0-9a-zA-Z\s\/_:\-.|]*/ }
end
I'm duplicating the project like this:
origin = Project.first
clone = origin.dup
clone.foos << clone_records(origin.foos)
clone.save
def clone_records(records)
clones = []
records.each do |record|
cloned_record = record.dup
cloned_record.project_id = nil
cloned_record.name = "Copy of " + record.name
# Which Generates:
#<Foo:0x007f94353fc200> {
# :id => nil,
# :name => "Copy of Some Foo",
# :project_id => nil
#
# }
clones.push(cloned_record)
end
return clones
end
The problem is, when I duplicate the project and assign the newly generated, renamed, foos, upon saving I get the error:
Foo Exists (0.4ms) SELECT 1 AS one FROM "foos" WHERE ("foos"."name" = 'Copy of Some Foo' AND "foos"."project_id" = 1) LIMIT 1
However, no foo with that name exists:
Foo.where(name: "Copy of Some Foo")
# Foo Load (0.6ms) SELECT "foos".* FROM "foos" WHERE "foos"."name" = $1 ORDER BY "foos"."id" ASC [["name", "Copy of Some Foo"]]
# []
Can anyone tell me what might be going on here? I gather that it has something to do with the validation, but I don't understand why: 1) It thinks this new record exists, and 2) why the new record has a project_id set to the origin project when I've explicitly nullified that field.
The problem is that uniqueness: { scope: :project_id } also applies to nil values. So if you have two records and they both have project_id == nil that would violate the uniqueness constraint.
Here is one way to allow nil values while still keeping the uniqueness constraint:
uniqueness: { scope: :project_id }, unless: Proc.new { |record| record.project_id.blank? }
When ActiveRecord complains that Foo Exists, it then rolls back the commit, so your cloned Foos do not persist to the database.
First, this line:
Foo Exists (0.4ms) SELECT 1 AS one FROM "foos" WHERE ("foos"."name" = 'Copy of Some Foo' AND "foos"."project_id" = 1) LIMIT 1
is not a error at all, it's just a log indicated Rails is running your uniqueness validation. And,
2) why the new record has a project_id set to the origin project when I've explicitly nullified that field.
Are you sure? it's project_id did set to the origin project? It's seems to me, the project_id is just set to the new created project's id.

Record exists when it doesn't

Given this simple model:
class Article < ActiveRecord::Base
validates :title, presence: true
validates :url, presence: true, uniqueness: true
validates :topic, presence: true
before_create :some_filter
private
def some_filter
self.noframe = false
end
end
And the fact that there are:
no observers
no indexes on the "noframe" column
How is this possible?
attrs = { title: "a", url: "http://www.example.com", topic: "test" }
Article.where(attrs).count
=> 0
Article.where(url:"http://www.example.com").count
=> 0
article = Article.new(attrs)
article.save
(0.2ms) BEGIN
Article Exists (0.6ms) SELECT 1 AS one FROM "articles" WHERE "articles"."url" = 'http://www.example.com' LIMIT 1
(0.3ms) ROLLBACK
article.errors.full_messages
[]
The debugger
When putting the debugger inside of "some_filter" method, this is what's happening.
[12] pry(#<Article>)> self.noframe = nil
=> nil
[13] pry(#<Article>)> self.valid?
Article Exists (0.5ms) SELECT 1 AS one FROM "articles" WHERE "articles"."url" = 'http://www.example.com' LIMIT 1
=> true
[14] pry(#<Article>)> self.noframe = false
=> false
[15] pry(#<Article>)> self.valid?
Article Exists (0.5ms) SELECT 1 AS one FROM "articles" WHERE "articles"."url" = 'http://www.example.com' LIMIT 1
=> true
[16] pry(#<Article>)> self.save
Article Exists (0.5ms) SELECT 1 AS one FROM "articles" WHERE "articles"."url" = 'http://www.example.com' LIMIT 1
=> false
More information
To make it more interesting, when I change "some_filter" to set noframe to either nil or to true, I can create as many records as I want without "Article Exists" error.
It also works when I set noframe attribute directly and not inside of before_create filter.
Why is this workaround working?
I'm able to "fix" it by replacing "before_create" with "after_create" and updating the noframe attribute additionally with update_attributes(noframe:false). But why is this a solution? update_attributes also calls all callbacks and validation so why is this not working in before_create?
before_create has one not that obvious property - if it returns false value it terminates whole saving chain. Same applies to all before-* callbacks. You can find it in callback documentation. Your method returns false, so whole transaction rolls back. Change it to:
def some_filter
self.noframe = false
true
end
And all will work.

Rails single table inheritance: type coumn is not populated automatically

I want to use STI in Rails 4. I already had a model Boilerplate and now I want to inherit a BoilerplateOriginal and a BoilerplateCopy model from it. So I added a column type to my table:
class AddTypeToBoilerplates < ActiveRecord::Migration
def up
add_column :boilerplates, :type, :string
Boilerplate.update_all type: 'BoilerplateOriginal'
change_column :boilerplates, :type, :string, null: false
end
def down
remove_column :boilerplates, :type
end
end
Sadly, this column doesn't seem to be filled by Rails automatically:
[1] a4aa2 » x = Boilerplate.new
=> #<Boilerplate:0x00000101609580> {
:id => nil,
:title => nil,
:type => nil
}
[2] a4aa2 » x.valid?
Boilerplate Exists (0.4ms) SELECT 1 AS one FROM "boilerplates" WHERE "boilerplates"."title" IS NULL LIMIT 1
=> false
[3] a4aa2 » x.errors
=> {
:title => [
[0] "must not be empty"
]
}
[4] a4aa2 » x.title = 'test'
=> "test"
[5] a4aa2 » x.valid?
Boilerplate Exists (0.1ms) SELECT 1 AS one FROM "boilerplates" WHERE "boilerplates"."title" = 'test' LIMIT 1
=> true
[6] a4aa2 » x.save
(0.1ms) begin transaction
Boilerplate Exists (0.2ms) SELECT 1 AS one FROM "boilerplates" WHERE "boilerplates"."title" = 'test' LIMIT 1
SQL (1.4ms) INSERT INTO "boilerplates" ("title") VALUES (?) [["title", "test"]]
SQLite3::ConstraintException: NOT NULL constraint failed: boilerplates.type: INSERT INTO "boilerplates" ("title") VALUES (?)
(0.1ms) rollback transaction
from /Users/josh/.rvm/gems/ruby-2.1.0#a4aa2/gems/sqlite3-1.3.10/lib/sqlite3/statement.rb:108:in `step'
Did I miss something important? I thought Rails simply fills the column :type when it's available?
Well. You are doing it wrong of-course. The type column will be set, only when you will be creating the objects by using any of the child class of the parent Boilerplate. But if you use Boilerplate.new, you need to pass the type value manually. On the other hand, when you will do BoilerplateCopy.new or BoilerplateOriginal.new, the type will be set by ActiveRecord by default for you to the class name of the child class.
Read the official documentation of Single table inheritance.
Active Record allows inheritance by storing the name of the class in a column that by default is named type (can be changed by overwriting Base.inheritance_column). This means that an inheritance looking like this:
class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end
When you do Firm.create(name: "37signals"), this record will be saved in the companies table with type = “Firm”. You can then fetch this row again using Company.where(name: '37signals').first and it will return a Firm object.

Validating only one attribute using ActiveRecord::Base#valid?

I'm looking for a way to use ActiveRecord::Base#valid? on only one attribute. Consider the following example, inspired by RailsGuides:
class Product < ActiveRecord::Base
belongs_to :company
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/ }
validates :name, uniqueness: { scope: :company }
end
Calling Product#valid? will trigger a SQL query because of the name validation:
Product.new(legacy_code: "helloworld", company: c).valid?
Product Exists (0.3ms) SELECT 1 AS one FROM "products" WHERE ("products"."name" IS NULL AND "products"."company_id" = 1) LIMIT 1
=> true
Now, I'm only looking to validate the legacy_code attribute:
Product.new(legacy_code: "helloworld").valid?
Product Exists (0.2ms) SELECT 1 AS one FROM "products" WHERE ("products"."name" IS NULL AND "products"."company_id" IS NULL) LIMIT 1
=> true
See the issue? I'd love to use something like Product#valid?(:legacy_code) and avoid the query that's trying to validate an attribute I don't care about.
So far, my solution is to use a Product#valid_legacy_code? method that I can call independently, and call this method as well in the validation declarations on my model.
I ended up writing a gem for that: https://github.com/KevinBongart/valid_attribute
# Test only one attribute:
product.valid_attribute?(:company) # => true
product.valid_attribute?(:name) # => true
product.valid_attribute?(:legacy_code) # => false
# Test several attributes at once, like a boss:
product.valid_attribute?(:company, :name) # => true
product.valid_attribute?(:company, :name, :legacy_code) # => false
# Wow, you can even filter down to a specific validator:
product.valid_attribute?(name: :format) # => true
product.valid_attribute?(name: [:format, :uniqueness]) # => true
product.valid_attribute?(name: :format, company: :presence) # => true

Resources