Related
acts-as-taggable-on gem
In Heroku Console,
=> Photo.find(106).taggings
Photo Load (1.7ms) SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 ORDER BY "photos"."created_at" DESC LIMIT 1 [["id", 106]]
Photo Load (1.7ms) SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 ORDER BY "photos"."created_at" DESC LIMIT 1 [["id", 106]]
ActsAsTaggableOn::Tagging Load (1.1ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 [["taggable_id", 106], ["taggable_type", "Photo"]]
ActsAsTaggableOn::Tagging Load (1.1ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 [["taggable_id", 106], ["taggable_type", "Photo"]]
=> #<ActiveRecord::Associations::CollectionProxy [#<ActsAsTaggableOn::Tagging id: 78, tag_id: 32, taggable_id: 106, taggable_type: "Photo", tagger_id: nil, tagger_type: nil, context: "tags", created_at: "2016-08-11 04:09:55">
You can see that there is no :name column in ActsAsTaggableOn::Tagging but pay attention to tag_id: 32
=> ActsAsTaggableOn::Tag.find(32)
ActsAsTaggableOn::Tag Load (1.0ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = $1 LIMIT 1 [["id", 32]]
ActsAsTaggableOn::Tag Load (1.0ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = $1 LIMIT 1 [["id", 32]]
=> #<ActsAsTaggableOn::Tag id: 32, name: "pickled", taggings_count: 2>
The following code works in the controller:
#photos = Photo.approved.tagged_with(params[:tag]).paginate(page: params[:page], per_page: 20) if params[:tag].present?
but it does not allow for tagged_with to be searched with ILIKE. The tag banana pudding will be returned only if banana pudding is queried.
Photo.find(106).tag_list
Photo Load (1.0ms) SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 ORDER BY "photos"."created_at" DESC LIMIT 1 [["id", 106]]
ActsAsTaggableOn::Tag Load (1.0ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL) [["taggable_id", 106], ["taggable_type", "Photo"]]
=> ["pickled"]
Edit
Alternatively, how can I setup my new photo form so that a hidden field :tags copies the tag_list column upon creation?
Edit for models
=> ActsAsTaggableOn::Tagging(id: integer, tag_id: integer, taggable_id: integer, taggable_type: string, tagger_id: integer, tagger_type: string, context: string, created_at: datetime)
=> ActsAsTaggableOn::Tag(id: integer, name: string, taggings_count: integer)
=> Photo(id: integer, user_id: integer, picture: string, title: string, description: string, photo_type: integer, location_type: integer, remote_picture_url: string, created_at: datetime, updated_at: datetime, approved: boolean, flags_count: integer)
tagging.rb
module ActsAsTaggableOn
class Tagging < ::ActiveRecord::Base #:nodoc:
belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
belongs_to :taggable, polymorphic: true
belongs_to :tagger, {polymorphic: true}.tap {|o| o.merge!(optional: true) if ActsAsTaggableOn::Utils.active_record5? }
scope :owned_by, ->(owner) { where(tagger: owner) }
scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) }
scope :by_contexts, ->(contexts) { where(context: (contexts || 'tags')) }
scope :by_context, ->(context = 'tags') { by_contexts(context.to_s) }
validates_presence_of :context
validates_presence_of :tag_id
validates_uniqueness_of :tag_id, scope: [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
after_destroy :remove_unused_tags
private
def remove_unused_tags
if ActsAsTaggableOn.remove_unused_tags
if ActsAsTaggableOn.tags_counter
tag.destroy if tag.reload.taggings_count.zero?
else
tag.destroy if tag.reload.taggings.count.zero?
end
end
end
end
end
tag.rb
#encoding: utf-8
module ActsAsTaggableOn
class Tag < ::ActiveRecord::Base
### ASSOCIATIONS:
has_many :taggings, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
### VALIDATIONS:
validates_presence_of :name
validates_uniqueness_of :name, if: :validates_name_uniqueness?
validates_length_of :name, maximum: 255
# monkey patch this method if don't need name uniqueness validation
def validates_name_uniqueness?
true
end
### SCOPES:
scope :most_used, ->(limit = 20) { order('taggings_count desc').limit(limit) }
scope :least_used, ->(limit = 20) { order('taggings_count asc').limit(limit) }
def self.named(name)
if ActsAsTaggableOn.strict_case_match
where(["name = #{binary}?", as_8bit_ascii(name)])
else
where(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(name))])
end
end
def self.named_any(list)
clause = list.map { |tag|
sanitize_sql_for_named_any(tag).force_encoding('BINARY')
}.join(' OR ')
where(clause)
end
def self.named_like(name)
clause = ["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(name)}%"]
where(clause)
end
def self.named_like_any(list)
clause = list.map { |tag|
sanitize_sql(["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(tag.to_s)}%"])
}.join(' OR ')
where(clause)
end
def self.for_context(context)
joins(:taggings).
where(["taggings.context = ?", context]).
select("DISTINCT tags.*")
end
### CLASS METHODS:
def self.find_or_create_with_like_by_name(name)
if ActsAsTaggableOn.strict_case_match
self.find_or_create_all_with_like_by_name([name]).first
else
named_like(name).first || create(name: name)
end
end
def self.find_or_create_all_with_like_by_name(*list)
list = Array(list).flatten
return [] if list.empty?
existing_tags = named_any(list)
list.map do |tag_name|
comparable_tag_name = comparable_name(tag_name)
existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
begin
existing_tag || create(name: tag_name)
rescue ActiveRecord::RecordNotUnique
# Postgres aborts the current transaction with
# PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block
# so we have to rollback this transaction
raise DuplicateTagError.new("'#{tag_name}' has already been taken")
end
end
end
### INSTANCE METHODS:
def ==(object)
super || (object.is_a?(Tag) && name == object.name)
end
def to_s
name
end
def count
read_attribute(:count).to_i
end
class << self
private
def comparable_name(str)
if ActsAsTaggableOn.strict_case_match
str
else
unicode_downcase(str.to_s)
end
end
def binary
ActsAsTaggableOn::Utils.using_mysql? ? 'BINARY ' : nil
end
def unicode_downcase(string)
if ActiveSupport::Multibyte::Unicode.respond_to?(:downcase)
ActiveSupport::Multibyte::Unicode.downcase(string)
else
ActiveSupport::Multibyte::Chars.new(string).downcase.to_s
end
end
def as_8bit_ascii(string)
if defined?(Encoding)
string.to_s.dup.force_encoding('BINARY')
else
string.to_s.mb_chars
end
end
def sanitize_sql_for_named_any(tag)
if ActsAsTaggableOn.strict_case_match
sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)])
else
sanitize_sql(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(tag))])
end
end
end
end
end
This question already has an answer here:
Why can't I create active records that count for something in rspec?
(1 answer)
Closed 8 years ago.
I can create users in the console find with:
2.0.0-p247 :020 > User.create(username: 'user30', password: 'abc123', admin: true)
(0.3ms) BEGIN
User Exists (0.6ms) SELECT 1 AS one FROM `users` WHERE `users`.`username` = BINARY 'user30' LIMIT 1
SQL (0.5ms) INSERT INTO `users` (`admin`, `created_at`, `pwd_hashed`, `salt`, `updated_at`, `username`) VALUES (NULL, '2014-07-17 01:34:41', 'd3272da7e3fc0f6e1b035dfb10e6f412ae84fac8', '415066400.06787292634781616', '2014-07-17 01:34:41', 'user30')
(75.6ms) COMMIT
=> #<User id: 14, username: "user30", pwd_hashed: "d3272da7e3fc0f6e1b035dfb10e6f412ae84fac8", salt: "415066400.06787292634781616", created_at: "2014-07-17 01:34:41", updated_at: "2014-07-17 01:34:41", admin: nil>
2.0.0-p247 :021 > User.create(username: 'user31', password: 'abc123', admin: true)
(0.3ms) BEGIN
User Exists (1.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`username` = BINARY 'user31' LIMIT 1
SQL (0.4ms) INSERT INTO `users` (`admin`, `created_at`, `pwd_hashed`, `salt`, `updated_at`, `username`) VALUES (NULL, '2014-07-17 01:34:46', '1ea5630a3c28b36e714625c4b44f3f951ef16c7a', '416177200.3102826047096757', '2014-07-17 01:34:46', 'user31')
(77.5ms) COMMIT
=> #<User id: 15, username: "user31", pwd_hashed: "1ea5630a3c28b36e714625c4b44f3f951ef16c7a", salt: "416177200.3102826047096757", created_at: "2014-07-17 01:34:46", updated_at: "2014-07-17 01:34:46", admin: nil>
2.0.0-p247 :022 > User.create(username: 'user32', password: 'abc123', admin: true)
(0.3ms) BEGIN
User Exists (0.6ms) SELECT 1 AS one FROM `users` WHERE `users`.`username` = BINARY 'user32' LIMIT 1
SQL (0.6ms) INSERT INTO `users` (`admin`, `created_at`, `pwd_hashed`, `salt`, `updated_at`, `username`) VALUES (NULL, '2014-07-17 01:34:50', '9aeb6cfcb80f57a08c421999dcfb5ad332317789', '417559200.14647833904112617', '2014-07-17 01:34:50', 'user32')
(12.0ms) COMMIT
=> #<User id: 16, username: "user32", pwd_hashed: "9aeb6cfcb80f57a08c421999dcfb5ad332317789", salt: "417559200.14647833904112617", created_at: "2014-07-17 01:34:50", updated_at: "2014-07-17 01:34:50", admin: nil>
2.0.0-p247 :023 >
2.0.0-p247 :005 > User.count
(0.6ms) SELECT COUNT(*) FROM `users`
=> 3
but in rspec when I have
describe "keep admin" do
its "Can't delete the only admin" do
user1 = User.create(username: 'user1', password: 'abc123', admin: true)
user2 = User.create(username: 'user2', password: 'def456', admin: true)
user3 = User.create(username: 'user3', password: 'ghi798', admin: true)
expect(User.where(admin: true).count).to eq 3
end
end
why do I get 0 instead of 3:
Failures:
1) keep admin Can't delete the only admin should eq 3
Failure/Error: expect(User.where(admin: true).count).to eq 3
expected: 3
got: 0
(compared using ==)
# ./spec/models/user_spec.rb:24:in `block (2 levels) in <top (required)>'
Finished in 0.37602 seconds
5 examples, 1 failure
User model:
$ cat app/models/user.rb
class User < ActiveRecord::Base
require 'digest/sha1'
attr_accessor :password_confirmation
attr_accessor :admin
validates_presence_of :username
validates_uniqueness_of :username
validates_confirmation_of :password
validate :password_non_blank
def self.delete_me(user)
how_many_admins = User.where(admin: true).count
if how_many_admins > 1
puts "delete ok!"
user.delete
else
puts "delete not ok!"
end
end
def self.authenticate(name, password)
user = self.find_by_username(name)
if user
expected_password = encrypted_password(password, user.salt)
if user.pwd_hashed != expected_password
user = nil
end
end
user
end
def password
#password
end
def password=(pwd)
#password = pwd
return if pwd.blank?
create_new_salt
self.pwd_hashed = User.encrypted_password(self.password, self.salt)
end
def is_admin
admin ? 'Yes' : 'No'
end
private
def password_non_blank
errors.add(:password, "Missing password") if pwd_hashed.blank?
end
def create_new_salt
self.salt = self.object_id.to_s + rand.to_s
end
def self.encrypted_password(password, salt)
string_to_hash = password + "wibble" + salt
Digest::SHA1.hexdigest(string_to_hash)
end
end
It appears your admin assignment in your create call isn't working. In your console you set admin: true in all of your users, yet look at the log:
User.create(username: 'user30', password: 'abc123', admin: true)
#<User id: 14, username: "user30", pwd_hashed: "d3272da7e3fc0f6e1b035dfb10e6f412ae84fac8", salt: "415066400.06787292634781616", created_at: "2014-07-17 01:34:41", updated_at: "2014-07-17 01:34:41", admin: nil>
admin: nil is what is being stored.
You're trying to set a ruby boolean value into what I'm guessing is a "string" field in the database? If so, try this:
User.create(username: 'user30', password: 'abc123', admin: "true")
Then change your rspec test to:
expect(User.where(admin: "true").count).to eq(3)
My model:
class User
has_many :items
end
class Item
belongs_to :user
def self.with_color(color, is_loaded)
if is_loaded
self.select { |i| i.color == color }
else
self.where(:color => color)
end
end
end
There are 2 types of code in my controllers.
## this line loads 'items' of the user and selects with the red color ##
#colored_items = current_user.items.with_color('red', false)
## this code should not load items twice ##
#user_with_items = User.includes(:items).find(params[:user_id])
(...)
colored_items = #user_with_items.with_color('red', true)
If I only write "select" version, it doesnt work with 1 example, and 'where' version loads items again even if they are already loaded.
How can I check is object already loaded or not?
How about using scopes?
Taken from example:
class Shirt < ActiveRecord::Base
scope :colored, lambda { |color| where(:color => color) }
end
More to read here: http://apidock.com/rails/ActiveRecord/Scoping/Named/ClassMethods/scope
With your example, this should be (not tested)
class User
has_many :items
end
class Item
belongs_to :user
scope :colored, lambda { |color| where(:color => color) }
end
## this line loads 'items' of the user and selects with the red color ##
#colored_items = current_user.items.colored('red')
## use of 'includes' here to solve (n+1) issue
#user_with_items = User.includes(:items).find(params[:user_id])
colored_items = #user_with_items.colored('red')
Update:
class User < ActiveRecord::Base
has_many :items
def colored_items(color)
self.items.to_a.select { |i| i.color == color }
end
end
user = User.includes(:items).find(1)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
Item Load (0.4ms) SELECT "items".* FROM "items" WHERE "items"."user_id" IN (1)
=> #<User id: 1, name: nil, created_at: "2012-03-25 00:39:52", updated_at: "2012-03-25 00:39:52">
user.colored_items "red"
=> [#<Item id: 1, color: "red", user_id: 1, created_at: "2012-03-25 00:41:00", updated_at: "2012-03-25 00:41:00">]
user.colored_items "blue"
=> [#<Item id: 2, color: "blue", user_id: 1, created_at: "2012-03-25 00:41:04", updated_at: "2012-03-25 00:41:04">]
So only 2 SQLs here.
I'm having a hard time trying to find out why a test is failing:
describe User, "Instance Methods" do
describe "leave_group!" do
it "should set group_id to nil" do
#user = Factory(:user)
#group_2 = Factory(:group, :owner => #user)
#user.leave_group!
#user.reload.group_id.should be_nil # THIS LINE IS FAILING!!!
end
it "should assign another owner to group, if owner was user" do
#user = Factory(:user)
#group = Factory(:group, :owner => #user)
1.upto(4) { #group.add_user Factory(:user) }
#user.leave_group!
#group.reload.owner.should_not eql(#user)
end
end
end
These are the models I'm using:
class User < ActiveRecord::Base
# Associations
has_one :own_group, :class_name => "Group", :foreign_key => "owner_id"
belongs_to :group
def leave_group!
current_group_id, current_group_owner = self.group.id, self.group.owner
self.group_id = nil
save!
Group.find(current_group_id).randomize_owner! if current_group_owner == self
end
end
class Group < ActiveRecord::Base
# Associations
has_many :users
belongs_to :owner, class_name: "User"
def randomize_owner!
current_users = self.users
return false unless current_users.length > 1
begin
new_user = current_users.sort_by { rand }.first
end while self.owner == new_user
self.owner_id = new_user.id
save!
end
end
Am I doing something wrong here? Could I improve it? And more importantly, why is that single test failing?
Here's the log output for runing that single test:
SQL (0.2ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
AREL (0.3ms) INSERT INTO "users" ("name", "uid", "provider", "email", "image_url", "group_id", "created_at", "updated_at", "timezone", "public_readings", "is_visible_on_leaderboards", "phone_number") VALUES ('John Doe', '314159265', 'facebook', 'john#does.com', NULL, NULL, '2011-07-18 02:02:08.455229', '2011-07-18 02:02:08.455229', NULL, 't', 't', NULL)
Group Load (0.1ms) SELECT "groups".* FROM "groups" WHERE "groups"."key" = 'SNYEMJ' LIMIT 1
AREL (0.1ms) INSERT INTO "groups" ("name", "key", "score", "owner_id", "created_at", "updated_at") VALUES ('John''s Group', 'SNYEMJ', 0, 1, '2011-07-18 02:02:08.522442', '2011-07-18 02:02:08.522442')
AREL (0.0ms) UPDATE "users" SET "group_id" = 1, "updated_at" = '2011-07-18 02:02:08.524342' WHERE "users"."id" = 1
Group Load (0.1ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = 1 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE ("users".group_id = 1)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
The last 3 lines are all selects, notice rails doesn't even try to remove the group_id from the user. (There are 2 inserts, 1 for the test user and 1 for the test group and 1 update which assigns group_id to the test user).
Try adding a #user.reload call before #user.leave_group in the test.
Even though the user record is updated with it's group in the DB when you create #group_2 from the factory, I suspect the #user object is not. Then you call leave_group! with a #user with a group ID of nil, so the save won't do anything because the object is unchanged. Then in the next line of your test you reload the #user, which now has the group_id from the DB assigned earlier.
try to chance the model class as following:
class User < ActiveRecord::Base
# Associations
has_one :own_group, :class_name => "Group", :foreign_key => "owner_id"
belongs_to :group
def leave_group!
group_randomize_owner
self.group.clear
end
private
def group_randomize_owner
if group.owner == self
group.randomize_owner!
end
end
end
I can't seem to get the state_machine gem (http://github.com/pluginaweek/state_machine/) to work on existing records (it works correctly on new records).
Here's my model:
class Comment < ActiveRecord::Base
state_machine :state, :initial => :pending do
event :publish do
transition all => :published
end
end
end
and here's an IRB session that demonstrates the issue (I did ActiveRecord::Base.logger = Logger.new(STDOUT) to make it easier to read):
>> c = Comment.new
=> #<Comment id: nil, song_id: nil, author: nil, body: nil, created_at: nil, updated_at: nil, state: "pending">
>> c.state
=> "pending"
>> c.publish
Comment Create (0.6ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-02 02:44:37', NULL, NULL, NULL, '2009-11-02 02:44:37', 'published')
=> true
>> Comment.last.state
Comment Load (0.4ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "published"
>> c = Comment.create
Comment Create (0.5ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-02 02:44:47', NULL, NULL, NULL, '2009-11-02 02:44:47', 'pending')
=> #<Comment id: 4, song_id: nil, author: nil, body: nil, created_at: "2009-11-02 02:44:47", updated_at: "2009-11-02 02:44:47", state: "pending">
>> c.publish
=> true
>> c.save
=> true
>> Comment.last.state
Comment Load (0.4ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "pending"
I.e., everything works fine when I publish an unsaved comment, but when I try to publish a comment that's already saved, nothing happens.
Another Edit: Perhaps the root of the problem?
=> true
>> a = Comment.last
Comment Load (1.3ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> #<Comment id: 3, song_id: nil, author: nil, body: nil, created_at: "2009-11-03 03:03:54", updated_at: "2009-11-03 03:03:54", state: "pending">
>> a.state
=> "pending"
>> a.publish
=> true
>> a.state
=> "published"
>> a.state_changed?
=> false
I.e., even though the state has actually changed, state_changed? is returning false and therefore Rails won't update the corresponding database row when I call save.
It works when I turn off partial updates, but not when I try state_will_change!:
>> Comment.partial_updates = false
=> false
>> c = Comment.create
Comment Create (0.5ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-07 05:06:49', NULL, NULL, NULL, '2009-11-07 05:06:49', 'pending')
=> #<Comment id: 7, song_id: nil, author: nil, body: nil, created_at: "2009-11-07 05:06:49", updated_at: "2009-11-07 05:06:49", state: "pending">
>> c.publish
Comment Update (0.9ms) UPDATE "comments" SET "created_at" = '2009-11-07 05:06:49', "author" = NULL, "state" = 'published', "body" = NULL, "song_id" = NULL, "updated_at" = '2009-11-07 05:06:53' WHERE "id" = 7
=> true
>> Comment.last.state
Comment Load (0.5ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "published"
>> Comment.partial_updates = true
=> true
>> c = Comment.create
Comment Create (0.8ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-07 05:07:21', NULL, NULL, NULL, '2009-11-07 05:07:21', 'pending')
=> #<Comment id: 8, song_id: nil, author: nil, body: nil, created_at: "2009-11-07 05:07:21", updated_at: "2009-11-07 05:07:21", state: "pending">
>> c.state_will_change!
=> "pending"
>> c.publish
=> true
>> c.save
=> true
>> Comment.last.state
Comment Load (0.5ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "pending"
EDIT:
More weirdness:
>> a = Comment.last
Comment Load (1.2ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> #<Comment id: 5, song_id: nil, author: nil, body: nil, created_at: "2009-11-02 06:33:19", updated_at: "2009-11-02 06:33:19", state: "pending">
>> a.state
=> "pending"
>> a.publish
=> true
>> a.state
=> "published"
>> a.save
=> true
>> a.id
=> 5
>> Comment.find(5).state
Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments"."id" = 5)
=> "pending"
Compare to:
>> a = Comment.last
Comment Load (0.3ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> #<Comment id: 5, song_id: nil, author: nil, body: nil, created_at: "2009-11-02 06:33:19", updated_at: "2009-11-02 06:33:19", state: "pending">
>> a.state = "published"
=> "published"
>> a.save
Comment Update (0.6ms) UPDATE "comments" SET "state" = 'published', "updated_at" = '2009-11-02 08:29:34' WHERE "id" = 5
=> true
>> a.id
=> 5
>> Comment.find(5).state
Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments"."id" = 5)
=> "published"
I came over the same issue 3 years after so it worth answering here to save other folks's time.
You need to have column called 'state' in your table, so state_machine will able to make the state persistant.
Just add it to your migration - t.string :state
Can you please retry your state transitions with publish**!** instead of publish
Not contributing anything useful, but I just wanted to say I'm struggling with this error as well, in multiple state_machines throughout my application. And I can't switch to AASM, because I need to have more than one state_machine in the same model... So frustrating!
Anyway, you're not alone, it definitely still needs a solution.
Does this still happen with partial updates turned off? Comment.partial_updates = false
If so, then we know the issue is with identifying dirty objects. You should be able to call c.state_will_change! before you call c.publish
Does the model call super when it's initialized?
The state_machine documentation says it's required for states to get initialized
def initialize
#seatbelt_on = false
super() # NOTE: This *must* be called, otherwise states won't get initialized
end
Again, not a real answer to your question, but here I tried to simulate your session:
>> c = Comment.new
=> #<Comment id: nil, body: nil, created_at: nil, updated_at: nil, state: "pending">
>> c.state
=> "pending"
>> c.publish
=> true
>> Comment.last.state
=> "published"
>> c = Comment.create
=> #<Comment id: 4, body: nil, created_at: "2009-11-05 07:12:53", updated_at: "2009-11-05 07:12:53", state: "pending">
>> c.publish
=> true
>> c.save
=> true
>> Comment.last.state
=> "published"
As you can see, it works as expected for me. Checked it twice.
(I created a model with body and state attributes and put your code in it.)
Try to remove :state from definition:
FROM:
state_machine :state , :initial => :pending do
TO
state_machine :initial => :pending do