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.
Related
I am trying to create a validation in a model (I think it would be better to validate it at a database level actually) so an user can not purchase and item that has already bought in a interval of 2 days (after 2 days it's ok if he/she buys it again).
I have a model for the "purchase" action, and every purchase action has an item associated called "purchasable". Also every purchase action has a user_id associated.
I am trying it like this in the purchase model (but my problem is that I don't know how to correctly write this validation, or even how to retrieve "purchasable.id" or the "user.id" when writing the method for the validation)
class Purchase < ApplicationRecord
belongs_to :purchasable, polymorphic: true
belongs_to :user
validates :price, presence: true
validate :no_purchase_overlap
def no_purchase_overlap
allPur = Purchase.includes(:purchasable).all
if allPur.where("created_at >= ? AND user_id = ? AND purchase_id = ?", (Time.now-2.days), user_id, purchasable.id)
errors.add(:date_end, 'it overlaps another purchase')
end
end
end
A purchase and its purchasable look like:
irb(main):006:0> Purchase.first
Purchase Load (0.1ms) SELECT "purchases".* FROM "purchases" ORDER BY "purchases"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Purchase id: 1, price: 0.299e1, quality: "SD", purchasable_type: "Season", purchasable_id: 9, user_id: 1, created_at: "2020-02-25 09:34:59", updated_at: "2020-02-25 09:34:59">
irb(main):007:0> Purchase.first.purchasable
Purchase Load (0.1ms) SELECT "purchases".* FROM "purchases" ORDER BY "purchases"."id" ASC LIMIT ? [["LIMIT", 1]]
Season Load (0.2ms) SELECT "seasons".* FROM "seasons" WHERE "seasons"."id" = ? LIMIT ? [["id", 9], ["LIMIT", 1]]
=> #<Season id: 9, title: "Brandy of the Damned", plot: "Here's looking at you, kid.", number: 1, created_at: "2020-02-25 09:34:17", updated_at: "2020-02-25 09:34:17">
Right now it's failing my tests, it seems like it is not allowing any purchase to be saved:
it 'a movie can not be purchased by the same user more than once' do
purchase = Purchase.new(price: 2.99, quality: 'HD', purchasable: movie, user: user)
purchase_rep = Purchase.new(created_at: Time.now + 1.day ,price: 1.99, quality: 'SD', purchasable: movie, user: user)
expect { purchase.save }.to change { user.purchases.count }.by(1)
expect { purchase_rep.save }.to raise_error
end
failure message:
Failures:
1) Purchase When creating a purchase a movie can not be purchased by the same user more than once
Failure/Error: expect { purchase.save }.to change { user.purchases.count }.by(1)
expected `user.purchases.count` to have changed by 1, but was changed by 0
# ./spec/models/purchase_spec.rb:14:in `block (3 levels) in <main>'
Solved with
def no_purchase_overlap
if Purchase.where("created_at >= ? AND user_id = ?", (Time.now-2.days), self.user.id).any?{|purchase| purchase.purchasable == self.purchasable}
errors.add(:date_end, 'it overlaps another purchase')
end
end
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.
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 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
course.rb
has_many :current_users, :through => :user_statuses, :source => :user, :conditions => ['user_statuses.updated_at > ?', 1.hour.ago]
console
Loading development environment (Rails 3.2.2)
>> course = Course.find(1)
Course Load (0.3ms) SELECT `courses`.* FROM `courses` WHERE `courses`.`id` = 1 LIMIT 1
=> #<Course id: 1, title: "Course 1", created_at: "2012-04-17 19:17:15", updated_at: "2012-04-17 19:17:15">
>> Time.now
=> 2012-04-23 08:29:45 -0400
>> course.current_users.count
(0.4ms) SELECT COUNT(*) FROM `users` INNER JOIN `user_statuses` ON `users`.`id` = `user_statuses`.`user_id` WHERE `user_statuses`.`user_id` = 1 AND (user_statuses.updated_at > '2012-04-23 12:28:40')
=> 0
>> Time.now
=> 2012-04-23 08:30:07 -0400
>> course.current_users.count
(0.4ms) SELECT COUNT(*) FROM `users` INNER JOIN `user_statuses` ON `users`.`id` = `user_statuses`.`user_id` WHERE `user_statuses`.`user_id` = 1 AND (user_statuses.updated_at > '2012-04-23 12:28:40')
=> 0
>>
Notice when checking the 1.hour.ago condition it uses the same time as a starting point despite the 30 second difference between the times when I made the request. Exiting console and restarting it clears it out, but it happens again with a new time. This behavior exists in testing and a browser as well. How do I get a model to use a time based condition for a has_many :through find?
I believe you want to use a dynamic condition on your models relation.
Have a look at this SO question
Basically when your model loads, 1.hour.ago is evaluated only once. If I understand your question, you want it to be evaluated on each request.
Something like this (rails 3.1+) :
:conditions => lambda { |course| "user_statuses.updated_at > '#{1.hour.ago}'" }
Putting the query in the model at all didn't work, either in a has_many :through setup or in a method. So I ended up removing the association and putting the query in the controller. This allows the current time to be calculated when the request is made.
model:
has_many :user_statuses
controller:
#course = Course.find(params[:id])
#current_users = #course.user_statuses.where('updated_at > ?', 1.hour.ago)