Logs on production:
irb(main):036:0> People.find(7).update_attribute(:all_read,[2,3])
People Load (1.2ms) SELECT "people".* FROM "people" WHERE "people"."id" = $1 LIMIT 1 [["id", 7]]
People Load (1.2ms) SELECT "people".* FROM "people" WHERE "people"."id" = $1 LIMIT 1 [["id", 7]]
(0.7ms) BEGIN
(0.7ms) BEGIN
(0.7ms) COMMIT
(0.7ms) COMMIT
People Store (29.1ms) {"id":7}
People Store (29.1ms) {"id":7}
=> true
irb(main):037:0> People.find(7).all_read
People Load (1.0ms) SELECT "people".* FROM "people" WHERE "people"."id" = $1 LIMIT 1 [["id", 7]]
People Load (1.0ms) SELECT "people".* FROM "people" WHERE "people"."id" = $1 LIMIT 1 [["id", 7]]
=> []
This works perfectly locally but it doesn't work on heroku.
I am using sqlite3 locally, postgresql on heroku.
Other related code:
class People < ActiveRecord::Base
serialize :all_read, Array
schema.db
create_table "people", force: :cascade do |t|
...
t.string "all_read", default: "--- []\n"
end
You should check your version of your DB.
Also check version of gem in group development and production.
I did not see the update query in your log.
sqlite and postgres are very different
use same database while developing
for example varchar in postgres is specified length string and in sqlite is unlimited.
if you want to find what is the exact problem, after your update_attribute call
the .errors method (with byebug or logger) to see the problem
I have 2 simple models in a has_many relationship. A Template has_many TemplateItems. A Template has a template_type which can be one of two values ('template' or 'checklist').
For brevity I have removed non-relevant code.
template.rb
class Template < ApplicationRecord
# Relationships
belongs_to :account
has_many :template_items, -> { order('sort ASC') }, dependent: :destroy
accepts_nested_attributes_for :template_items, allow_destroy: true
# Enums
enum template_type: {template: 0, checklist: 1}
enum status: {not_started: 0, started: 1, completed: 2}
# Callbacks
before_save :set_status, unless: :is_template? # only care about status for checklists
def is_template?
return self.template_type == 'template'
end
def set_status
completed = 0
self.template_items.each do |item|
completed += 1 if item.is_completed
end
case completed
when 0
self.status = Template.statuses[:not_started]
when 1..(self.template_items.length - 1)
self.status = Template.statuses[:started]
when self.template_items.length
self.status = Template.statuses[:completed]
end
end
end
template_item.rb
class TemplateItem < ApplicationRecord
# Relationships
belongs_to :template
# Validations
validates_presence_of :template
end
When a client sends an update to Template Controller, it includes the template_items nested:
templates_controller.rb
def template_params
params.require(:template).
permit(:id, :account_id, :list_type, :name, :title, :info, :status,
template_items_attributes:
[:id, :template_id, :is_completed, :content, :item_type, :sort, :_destroy])
end
Notice that one of the attributes of an item is called sort. Notice also that the sort order is used in the Template model to sort the template_items (see the has_many line).
If a client resorts the template_items, the following update action is called:
templates_controller.rb
def update
if #template.update(template_params)
render json: #template, serializer: TemplateSerializer, status: :ok
else
render json: ErrorSerializer.serialize(#template.errors), status: :unprocessable_entity
end
end
The strange behaviour is that the database is always updated (verified in the logs and in the db) but sometimes the render does not render the new sort order but instead renders the previous sort order.
Here is the log when the action incorrectly returns the previous data:
I, [2018-02-20T20:22:55.997835 #1852] INFO -- : Processing by Api::TemplatesController#update as JSON
...parameters here...
D, [2018-02-20T20:22:56.002965 #1852] DEBUG -- : User Load (1.7ms) SELECT "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT $2 [["uid", "rmcsharry+owner#gmail.com"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.115190 #1852] DEBUG -- : Template Load (2.6ms) SELECT "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2 [["id", "f9f6bca2-cb84-4349-8546-ca38026db407"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.121995 #1852] DEBUG -- : (0.4ms) BEGIN
D, [2018-02-20T20:22:56.129177 #1852] DEBUG -- : TemplateItem Load (2.5ms) SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 AND "template_items"."id" IN ('419cb7ec-ca3f-4911-8a00-bec20f5ca89c', 'a7ac1687-8cb5-4199-a03b-d7cc975a0387', 'd7d885b6-2a75-487a-918c-6f3abaae7df1', 'b1b0277c-632f-4fe1-82e5-d020ee313d5b') ORDER BY sort ASC [["template_id", "f9f6bca2-cb84-4349-8546-ca38026db407"]]
D, [2018-02-20T20:22:56.137975 #1852] DEBUG -- : Account Load (1.4ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2 [["id", "c379e356-4cce-4de2-b1b4-984b773dd43e"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.144421 #1852] DEBUG -- : CACHE Template Load (0.0ms) SELECT "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2 [["id", "f9f6bca2-cb84-4349-8546-ca38026db407"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.148992 #1852] DEBUG -- : CACHE Template Load (0.0ms) SELECT "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2 [["id", "f9f6bca2-cb84-4349-8546-ca38026db407"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.156300 #1852] DEBUG -- : TemplateItem Load (2.4ms) SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 ORDER BY sort ASC [["template_id", "f9f6bca2-cb84-4349-8546-ca38026db407"]]
D, [2018-02-20T20:22:56.171567 #1852] DEBUG -- : SQL (1.9ms) UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3 [["sort", 2], ["updated_at", "2018-02-20 19:22:56.167142"], ["id", "d7d885b6-2a75-487a-918c-6f3abaae7df1"]]
D, [2018-02-20T20:22:56.175072 #1852] DEBUG -- : SQL (0.7ms) UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3 [["sort", 1], ["updated_at", "2018-02-20 19:22:56.172797"], ["id", "a7ac1687-8cb5-4199-a03b-d7cc975a0387"]]
D, [2018-02-20T20:22:56.176305 #1852] DEBUG -- : (0.6ms) COMMIT
I, [2018-02-20T20:22:56.183481 #1852] INFO -- : Rendered TemplateSerializer with ActiveModelSerializers::Adapter::Attributes (2.97ms)
Here is the log when the action correctly returns the new data - I have marked the differences (1) and (2):
I, [2018-02-20T20:52:47.490513 #3087] INFO -- : Processing by Api::TemplatesController#update as JSON
...parameters...
D, [2018-02-20T20:52:47.499201 #3087] DEBUG -- : User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT $2 [["uid", "rmcsharry+owner#gmail.com"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.706520 #3087] DEBUG -- : Template Load (2.3ms) SELECT "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2 [["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.727668 #3087] DEBUG -- : (0.3ms) BEGIN
D, [2018-02-20T20:52:47.777126 #3087] DEBUG -- : TemplateItem Load (2.2ms) SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 AND "template_items"."id" IN ('ff034c14-252f-4366-9b31-526b5211e92b', '4e6ec7ef-ba53-4ec2-ab2e-97dd3b2c41bc', '3628b6ca-cddb-4d65-a6c3-86dfdcaa92f4', '35e61d68-143c-4bac-ab15-fbbb2b3f13d1') ORDER BY sort ASC [["template_id", "c965c3ed-ace2-43af-9abd-f85392bdb948"]]
D, [2018-02-20T20:52:47.820226 #3087] DEBUG -- : Account Load (1.4ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2 [["id", "c379e356-4cce-4de2-b1b4-984b773dd43e"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.847928 #3087] DEBUG -- : CACHE Template Load (0.0ms) SELECT "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2 [["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.850995 #3087] DEBUG -- : CACHE Template Load (0.0ms) SELECT "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2 [["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["LIMIT", 1]]
(1) D, [2018-02-20T20:52:47.856858 #3087] DEBUG -- : Template Exists (0.9ms) SELECT 1 AS one FROM "templates" WHERE "templates"."name" = $1 AND ("templates"."id" != $2) AND "templates"."account_id" = 'c379e356-4cce-4de2-b1b4-984b773dd43e' AND "templates"."template_type" = $3 LIMIT $4 [["name", "Daffy"], ["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["template_type", 0], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.863415 #3087] DEBUG -- : SQL (1.1ms) UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3 [["sort", 2], ["updated_at", "2018-02-20 19:52:47.859495"], ["id", "3628b6ca-cddb-4d65-a6c3-86dfdcaa92f4"]]
D, [2018-02-20T20:52:47.865969 #3087] DEBUG -- : SQL (0.6ms) UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3 [["sort", 3], ["updated_at", "2018-02-20 19:52:47.864044"], ["id", "35e61d68-143c-4bac-ab15-fbbb2b3f13d1"]]
D, [2018-02-20T20:52:47.868568 #3087] DEBUG -- : (2.0ms) COMMIT
(2) D, [2018-02-20T20:52:47.918381 #3087] DEBUG -- : TemplateItem Load (1.5ms) SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 ORDER BY sort ASC [["template_id", "c965c3ed-ace2-43af-9abd-f85392bdb948"]]
I, [2018-02-20T20:52:47.930257 #3087] INFO -- : Rendered TemplateSerializer with ActiveModelSerializers::Adapter::Attributes (17.22ms)
Notice the differences:
(1) the log shows a 'Template Exists' message
(2) after the commit Rails reloads the template_items to get the updated data from the database.
I know that I can fix this and force the update action to always do (2) and reload the template_items child objects:
templates_controller.rb
def update
if #template.update(template_params)
#template.template_items.reload
render json: #template, serializer: TemplateSerializer, status: :ok
else
render json: ErrorSerializer.serialize(#template.errors), status: :unprocessable_entity
end
end
But why do I need to do that if Rails has the ability (sometimes) to figure that out on its own? Although the cache is used in both calls, in the correct second example Rails has figured out it needs to reload the child objects after the database was updated, but not in the first case.
So what I am trying to understand is what controls this behaviour. It seems to me that it must be related to the before_save action in the Template model, since that action only fires for the 2nd case (template_type is 'template') and not the 1st (template_type is 'checklist'). In other words it seems when that action fires it 'changes' the behaviour of the update action.
So my questions are:
Why the different behaviour for the same action? If it is the
before_save, then why?
Why in the correct case does the log show Template Exists (since it
does exist in both cases)?
How does Rails know to reload the updated children in the correct case
but not in the incorrect case?
** UPDATE **
Here is the template_serializer.rb
class TemplateSerializer < ActiveModel::Serializer
attributes :id,
:account_id,
:name,
:info,
:title,
:template_type,
:status
has_many :template_items, include_nested_associations: true
end
The issue here is that you are requesting the items prior changing the sort. This means that the array of items that you have will no longer be sorted since you changed the property they are sorted on. Put another way, after you modify them, there isn't another query which returns the correct order.
So, I'll say the possible solutions are:
Reload the items after you mutate the sort.
Don't pull the items until after you mutate the sort.
Mutate the order of template_items based on sort values that changed.
The tradeoffs:
You have 2 select queries as well as the updates.
You have to update the records using TemplateItem.update(id, sort: sort) with all those updates within a transaction prior to selecting the records.
If you aren't rendering all the results, or decide not to in the future, it is possible that you will be modifying an item which will no longer be on the page. And, possibly other issues.
Why the different behaviour for the same action? If it is the before_save, then why?
The before_save is requesting template_items prior to them being saved. Otherwise, template_items doesn't get called until the serializer renders them. Note, that this means your before_save callback isn't performing the way you want it to since it is modifying the status based on the previous values.
Why in the correct case does the log show Template Exists (since it does exist in both cases)?
SELECT 1 AS one FROM "templates" WHERE
"templates"."name" = 'Daffy' AND
("templates"."id" != 'c965c3ed-ace2-43af-9abd-f85392bdb948') AND
"templates"."account_id" = 'c379e356-4cce-4de2-b1b4-984b773dd43e' AND
"templates"."template_type" = 0
LIMIT 1
Looking at the SQL, this looks like a validation to ensure name is unique across templates and type.
How does Rails know to reload the updated children in the correct case but not in the incorrect case?
Rails does not know. It is only loading them once in both cases. Just, with the before_save it is running before the records are updated.
Summary:
The easiest way to fix this timing issue would using a different callback which fires after updating the children such as after_update.
I have a Rails 4 app with simple specs. When I run a spec, it takes 26 seconds. If I look at the log, I can see this :
(38.5ms) ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE .............# tons of tables
(10.3ms) SELECT schemaname || '.' || tablename
FROM pg_tables
WHERE
tablename !~ '_prt_' AND
tablename <> 'schema_migrations' AND
schemaname = ANY (current_schemas(false))
(2.3ms) select table_name from information_schema.views where table_schema = 'lap-test'
(10708.2ms) TRUNCATE TABLE "public"."bounded_facets_service_boutiques", "public"."versions", ..... # Tons of tables
(10823.0ms) TRUNCATE TABLE "public"."bounded_facets_service_boutiques", "public"."versions", .... #Tons of tables
(26.2ms) ALTER TABLE "schema_migrations" ENABLE TRIGGER ALL;ALTER TABLE "bounded_facets_service_boutiques" ENABLE TRIGGER ALL;ALTER TABLE "versions" .... #Tons of tables
(0.6ms) BEGIN
(0.3ms) COMMIT
(0.2ms) BEGIN
(0.2ms) SAVEPOINT active_record_1
(0.2ms) RELEASE SAVEPOINT active_record_1
(0.2ms) SAVEPOINT active_record_1
(0.5ms) SAVEPOINT active_record_2
Administrator Exists (1.9ms) SELECT 1 AS one FROM "administrators" WHERE "administrators"."email" = 'person1#labandprocess.com' LIMIT 1
SQL (2.3ms) INSERT INTO "administrators" .....
(0.6ms) RELEASE SAVEPOINT active_record_2
(0.4ms) ROLLBACK TO SAVEPOINT active_record_1
(0.6ms) ROLLBACK
As you can see, The truncation takes 10 seconds 2 times.
I use database cleaner with this :
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
end
I tested with other strategies without result.
There is a lot of seeds in development mod but it's on another database. There is 107 models in this project.
Is there a solution ?
I'm having a baffling problem. I have a LineItem model that accepts nested attributes for a Trackable and a Product. I am overriding the default trackable_attributes= and product_attributes= methods to allow me to find or initialize records. For example:
def trackable_attributes=(attributes)
_trackable = Project.find_or_initialize_by_id(attributes.delete("id"))
_trackable.attributes = attributes
self.trackable = _trackable
end
When I post to the LineItemController#create, even when there's only a logging statement in it, it runs all of the posted parameters as if I'm trying to build/create a new line item. I don't have any before_filters that would be doing this on the #create method.
def create
logger.info("LineItemsController#create")
end
Webserver logs:
Started POST "/line_items" for 127.0.0.1 at 2013-04-10 14:02:13 -0700
Processing by LineItemsController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MY_SECRET_TOKEN", "line_item"=>{"trackable_type"=>"Project", "tax_rate"=>"0", "price"=>"0.00", "provided_on_formatted"=>"Apr 10", "trackable_attributes"=>{"company_attributes"=>{"id"=>"87", "name"=>"Handmade Design"}, "id"=>"45", "name"=>"MEdia Blitz"}, "product_attributes"=>{"id"=>"75"}, "name"=>"Design", "notes"=>"Yellow", "quantity"=>"0.00", "unit_price"=>"205.0"}, "commit"=>"+"}
Person Load (0.3ms) SELECT "people".* FROM "people" WHERE "people"."id" = 73 LIMIT 1
(0.1ms) begin transaction
(0.4ms) UPDATE "people" SET "last_request_at" = '2013-04-10 21:02:13.861629', "perishable_token" = 'kzsB5dnCOHfykO1XMMi9', "updated_at" = '2013-04-10 21:02:13.863622' WHERE "people"."id" = 73
[paperclip] Saving attachments.
(1.2ms) commit transaction
Account Load (0.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = 369 LIMIT 1
Person Load (0.3ms) SELECT "people".* FROM "people" WHERE "people"."account_id" = 369 ORDER BY id LIMIT 1
{"company_attributes"=>{"id"=>"87", "name"=>"Handmade Design"}, "id"=>"45", "name"=>"MEdia Blitz"}
Project Load (0.2ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = 45 LIMIT 1
Company Load (0.2ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = 87 LIMIT 1
trackable_attributes=(attributes)
trackable assigned
{"id"=>"75"}
Product Load (0.2ms) SELECT "products".* FROM "products" WHERE "products"."id" = 75 LIMIT 1
product_attributes=(attributes)
product assigned
LineItemsController#create
Notice that I have many logging statements scattered through my custom accepts_nested_attributes_for setters that are being called before the #create method even begins.
Is this normal? It seems to be causing problems with when I want to instantiate a new line_item the normal way in my LineItemsController#create method.
I'd really appreciate any help as I'm lost on where to start.
I have an ajax request that is causing problems in my Rails 3.0.9 app. I can see the problem in the logs, but I don't have any idea what is triggering it between the ajax call and the render. Here's the log, and the event I don't want with ** beside it:
Started DELETE "/notifications/13" for 127.0.0.1 at 2011-06-21 22:08:39 -0500
Processing by NotificationsController#destroy as JS
Parameters: {"id"=>"13"}
SQL (0.4ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
SQL (0.3ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Slug Load (0.4ms) SELECT "slugs".* FROM "slugs" WHERE ("slugs".sluggable_id = 1 AND "slugs".sluggable_type = 'User') ORDER BY id DESC LIMIT 1
****AREL (0.3ms) UPDATE "users" SET "remember_token" = NULL, "remember_created_at" = NULL, "updated_at" = '2011-06-22 03:08:40.084049', "preferences" = '---
:email_notifications: ''true''
' WHERE "users"."id" = 1
Notification Load (0.2ms) SELECT "notifications".* FROM "notifications" WHERE "notifications"."id" = 13 LIMIT 1
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
AREL (0.3ms) UPDATE "users" SET "notifications_count" = COALESCE("notifications_count", 0) - 1 WHERE "users"."id" = 1
AREL (0.1ms) DELETE FROM "notifications" WHERE "notifications"."id" = 13
Completed 200 OK in 1334ms
I'd like to somehow step by step debug this request, sort of like the way you can step through a function in javascript using firebug.
Is there a way to debug like this so I can see how that specific AREL command is getting called??
Have you looked at ruby on rails guides - debugging?? you can debug just like in gdb
This railscast is also quite useful.