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.
Related
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!
I have an annoying problem: I want to mark users as deleted, so I created a boolean db column named "deleted" and flagged a User as deleted. Now, when I search it:
ds = User.where(deleted: 1)
User Load (0.8ms) SELECT `users`.* FROM `users` WHERE `users`.`deleted` = 1 LIMIT 11
=> #<ActiveRecord::Relation [#<
User id: 4,
vname: "Max",
nname: "Muster",
...
emailverified: true,
deleted: false>]>
Why does Rails interpret 1 as false? One is supposed to be true, as the following search shows:
ds = User.where(deleted: true)
User Load (0.8ms) SELECT `users`.* FROM `users` WHERE `users`.`deleted` = 1 LIMIT 11
=> #<ActiveRecord::Relation [#<
User id: 4,
vname: "Max",
nname: "Muster",
...
emailverified: true,
deleted: false>]>
So I search for deleted = true and get a record where deleted = false ???
I need this for a test I am writing:
expect(user_after_deletion.deleted).to be true
But user.deleted is always false, even when 1 is stored in the db.
With the field "emailverified" everything works as expected. The columns are both tinyint(1).
I tried to rename the column, but still no luck.
Can anyone please point me to the right direction?
Thanks!
P.S.:
user.rb
class User < ApplicationRecord
attr_accessor :passwd_upd
belongs_to :entitlement
after_initialize :set_uid
has_secure_password
has_many :user_histories
def self.automatic_deletion
User.where("created_at > ?", (Time.now - 24.hours)).each do |u|
u.deleted = true
u.save(validate: false)
end
end
def undelete
self.deleted = false
end
end
and the corresponding migration.
class AddDeletedToUsers < ActiveRecord::Migration[5.1]
def change
add_column :users, :deleted, :boolean
end
end
I "solved" it by updating Rails from 5.1.4 to 5.2.
Now I get:
ds = User.where(deleted: true)
User Load (0.8ms) SELECT `users`.* FROM `users` WHERE `users`.`deleted` = 1 LIMIT 11
=> #<ActiveRecord::Relation [#<
User id: 4,
vname: "Max",
nname: "Muster",
...
emailverified: true,
deleted: true>]>
Sorry for stealing Your time.
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.
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
I Having an issue when trying to update a model that has has_and_belongs_to_many association.
Let's say that Post has_and_belongs_to_many Tag, and Post validates the presence of title and Tags.
If I update Post, removing its title and tags, I get validation error in title and tags, ok.
But ActiveAdmin already removed the records that make association between Post and Tag, so, if I leave Post edit page, the post is left invalid on database, without tags.
Here my models:
class Tag < ActiveRecord::Base
attr_accessible :label
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
attr_accessible :content, :title, :tag_ids
has_and_belongs_to_many :tags
validates_presence_of :content, :title, :tags
end
ActiveAdmin.register Post do
form do |f|
f.inputs do
f.input :title
f.input :content
f.input :image
f.input :tags
end
f.buttons
end
end
I usign chosen-rails gem and it allows user to unselect all tags of post.
Summarizing, my problem is: ActiveAdmin updates relationships on database before perform model validations.
There a solution for this behavior or I doing something wrong?
Edit:
Here the request log when I trying to update post without title and tags:
Started PUT "/admin/posts/8" for 127.0.0.1 at 2013-04-01 10:32:07 -0300
Processing by Admin::PostsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"amSbLlP/rgDrNn/N8lgq/KEaRXK1fMPShZDwpZ0QIJ4=", "post"=>{"title"=>"", "content"=>"content", "tag_ids"=>["", ""]}, "commit"=>"Update Post", "id"=>"8"}
AdminUser Load (0.2ms) SELECT `admin_users`.* FROM `admin_users` WHERE `admin_users`.`id` = 1 LIMIT 1
Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
(0.1ms) BEGIN
SQL (12.3ms) DELETE FROM `posts_tags` WHERE `posts_tags`.`post_id` = 8 AND `posts_tags`.`tag_id` IN (1, 2)
(49.6ms) COMMIT
(0.1ms) BEGIN
(0.2ms) ROLLBACK
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
Tag Load (0.2ms) SELECT `tags`.* FROM `tags`
Rendered /home/rodrigo/.rvm/gems/ruby-1.9.3-p125#blog/gems/activeadmin-0.5.1/app/views/active_admin/resource/edit.html.arb (192.3ms)
Completed 200 OK in 276ms (Views: 194.8ms | ActiveRecord: 63.3ms)
EDIT 2:
Ok, I sure that ActiveAdmin has this bug.
Looking at ActiveRecord behaviour, I think that validation flow is broken using only model class. See this example:
1.9.3p125 :064 > post = Post.find(8)
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
=> #<Post id: 8, title: "title", content: "content", created_at: "2013-03-27 13:13:20", updated_at: "2013-03-27 13:13:20", image: "extrato.bmp">
1.9.3p125 :065 > post.tags
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
=> [#<Tag id: 1, label: "tag", created_at: "2013-02-25 18:32:45", updated_at: "2013-02-25 18:32:45">, #<Tag id: 2, label: "new", created_at: "2013-02-25 18:32:50", updated_at: "2013-02-25 18:32:50">]
1.9.3p125 :066 > post.title = ""
=> ""
1.9.3p125 :067 > post.save #<<<<<<< It's invalid on title
=> false
1.9.3p125 :068 > post.tags = [] #<<<<<<< This shouldnt trigger database update
(0.3ms) BEGIN
SQL (0.5ms) DELETE FROM `posts_tags` WHERE `posts_tags`.`post_id` = 8 AND `posts_tags`.`tag_id` IN (1, 2)
(55.5ms) COMMIT
=> []
1.9.3p125 :069 > post.save #<<<<<<< It's invalid on title AND TAGS
(0.2ms) BEGIN
(0.2ms) ROLLBACK
=> false
1.9.3p125 :070 > post.reload
Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
=> #<Post id: 8, title: "title", content: "content", created_at: "2013-03-27 13:13:20", updated_at: "2013-03-27 13:13:20", image: "extrato.bmp">
1.9.3p125 :071 > post.valid? #<<<<<<< Now, I have this model in invalid state
Tag Load (0.6ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
=> false
Has any way to update post attributes(including tags) and validate the model before doing any database update?
You can use this gem: https://github.com/MartinKoerner/deferred_associations
The deferred associations will fix this bug.
#Rodrigo I was able to reproduce your issue locally without active admin, the issue is actually that is one of the default operations when using HABTM relationships if you see [here][1]
[1]: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many it says:
collection=objects
Replaces the collection’s content by deleting and adding objects as appropriate.
So apparently you will need to override this operation
Here is an example:
Override ActiveRecord << operator on has_many :through relationship, to accept data for the join model
Let me know how can I help you
I'd propose to add a tmp variable and store values in it. Then you should move them to database if validation passed.
Here is an example:
Your models/article.rb:
class Article < ActiveRecord::Base
validate :author_presence
has_and_belongs_to_many :authors
attr_writer :tmp_author_ids
def tmp_author_ids
#tmp_author_ids || author_ids
end
def author_presence
if tmp_author_ids.reject(&:blank?).empty?
errors.add(:tmp_author_ids, 'Author is missing')
else
self.author_ids = tmp_author_ids
end
end
end
Your admin/article.rb, block form:
f.input :tmp_author_ids, as: :select, multiple: true, collection: Author.all, label: 'Authors'
That's it
So, for anyone having the same issue, I found a workaround:
You can define a before_remove in the has_and_belongs_to_many association and raise an exception that then you can catch in ActiveAdmin.
For this it would be
class Post < ActiveRecord::Base
attr_accessible :content, :title, :tag_ids
has_and_belongs_to_many :tags, before_remove: :check_something
validates_presence_of :content, :title, :tags
end
def check_something(agent)
if self.tags.size == 1
raise ActiveModel::MissingAttributeError.new 'something'
end
end
more info: https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
if you want, you can redefine you update action in active_admin to prevent saving empty tags, in something like this style
ActiveAdmin.register Post do
controller do
def update
if params[:post][:tag_ids] == ["", ""]
flash.now[:alert] = "You can't remove all tags"
render :edit
else
super
end
end
end
...
end
and i think this stuff from model can be deleted
attr_accessor :new_tag_ids
validate :validate_new_tags_ids
after_save :update_tags
def update_tags
self.tag_ids = #new_tag_ids if defined?(#new_tag_ids)
#new_tag_ids = nil
end
private
def validate_new_tags_ids
errors[:tags] << "can't be blank (2)" if #new_tag_ids.blank?
end