Here's a fun ActiveStorage issue which has been causing me to scratch my head in confusion for a couple of days; ActiveStorage seems to not be playing well with either fixture_file_upload & Rack::Test::UploadedFile as both methods are throwing the following error when saving a file to a model attachment using ActiveStorage:
Loading development environment (Rails 6.0.3.4)
irb(main):001:0> Attachment.create({file: Rack::Test::UploadedFile.new("#{Rails.root}/test/fixtures/files/tracer1.jpg", 'tracer1.jpg')})
D, [2021-01-22T16:20:03.695894 #498] DEBUG -- : (0.3ms) BEGIN
D, [2021-01-22T16:20:03.730325 #498] DEBUG -- : Attachment Create (0.8ms) INSERT INTO "attachments" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2021-01-22 16:20:03.726078"], ["updated_at", "2021-01-22 16:20:03.726078"]]
D, [2021-01-22T16:20:03.735233 #498] DEBUG -- : ActiveStorage::Attachment Load (0.4ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4 [["record_id", 1], ["record_type", "Attachment"], ["name", "file"], ["LIMIT", 1]]
D, [2021-01-22T16:20:03.736695 #498] DEBUG -- : ActiveStorage::Blob Create (0.3ms) INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["key", "uk44hg6aeqgkijnoti7625kmymn5"], ["filename", "tracer1.jpg"], ["content_type", "image/jpeg"], ["metadata", "{\"identified\":true}"], ["byte_size", 71843], ["checksum", "fleS+FPvNcLEHaUysUgaGQ=="], ["created_at", "2021-01-22 16:20:03.735850"]]
D, [2021-01-22T16:20:03.737593 #498] DEBUG -- : ActiveStorage::Attachment Create (0.4ms) INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["name", "file"], ["record_type", "Attachment"], ["record_id", 1], ["blob_id", 4], ["created_at", "2021-01-22 16:20:03.736870"]]
D, [2021-01-22T16:20:03.739022 #498] DEBUG -- : Attachment Update (0.3ms) UPDATE "attachments" SET "updated_at" = $1 WHERE "attachments"."id" = $2 [["updated_at", "2021-01-22 16:20:03.737884"], ["id", 1]]
D, [2021-01-22T16:20:03.740233 #498] DEBUG -- : (1.1ms) COMMIT
I, [2021-01-22T16:20:03.740753 #498] INFO -- : Disk Storage (0.0ms) Deleted file from key: uk44hg6aeqgkijnoti7625kmymn5
I, [2021-01-22T16:20:03.740868 #498] INFO -- : Disk Storage (0.5ms) Uploaded file to key: uk44hg6aeqgkijnoti7625kmymn5 (checksum: fleS+FPvNcLEHaUysUgaGQ==)
Traceback (most recent call last):
1: from (irb):1
ActiveStorage::IntegrityError (ActiveStorage::IntegrityError)
This issue, however, isn't seen when using the following method of attaching a file.
irb(main):001:0> attachment = Attachment.new
=> #<Attachment id: nil, created_at: nil, updated_at: nil, parent_type: nil, parent_id: nil, frame: "{}", user_id: nil>
irb(main):002:0> attachment.file.attach(io: File.open(Rails.root + 'test/fixtures/files/tracer1.jpg'), filename: 'tracer1.jpg', content_type: 'image/jpg')
=> #<ActiveStorage::Attached::Changes::CreateOne:0x0000563b16d6bdc8 #name="file", #record=#<Attachment id: nil, created_at: nil, updated_at: nil, parent_type: nil, parent_id: nil, frame: "{}", user_id: nil>, #attachable={:io=>#<File:/app/test/fixtures/files/tracer1.jpg>, :filename=>"tracer1.jpg", :content_type=>"image/jpg"}>
irb(main):003:0> attachment.save
D, [2021-01-22T16:21:55.564371 #516] DEBUG -- : (0.2ms) BEGIN
D, [2021-01-22T16:21:55.610210 #516] DEBUG -- : Attachment Create (0.5ms) INSERT INTO "attachments" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2021-01-22 16:21:55.605710"], ["updated_at", "2021-01-22 16:21:55.605710"]]
D, [2021-01-22T16:21:55.615088 #516] DEBUG -- : ActiveStorage::Attachment Load (0.4ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4 [["record_id", 2], ["record_type", "Attachment"], ["name", "file"], ["LIMIT", 1]]
D, [2021-01-22T16:21:55.617341 #516] DEBUG -- : ActiveStorage::Blob Create (0.6ms) INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["key", "hjmhqkujbe2uxk6f9jfgmq5j4nn7"], ["filename", "tracer1.jpg"], ["content_type", "image/jpeg"], ["metadata", "{\"identified\":true}"], ["byte_size", 71843], ["checksum", "fleS+FPvNcLEHaUysUgaGQ=="], ["created_at", "2021-01-22 16:21:55.615787"]]
D, [2021-01-22T16:21:55.618972 #516] DEBUG -- : ActiveStorage::Attachment Create (0.7ms) INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["name", "file"], ["record_type", "Attachment"], ["record_id", 2], ["blob_id", 5], ["created_at", "2021-01-22 16:21:55.617781"]]
D, [2021-01-22T16:21:55.621752 #516] DEBUG -- : Attachment Update (0.5ms) UPDATE "attachments" SET "updated_at" = $1 WHERE "attachments"."id" = $2 [["updated_at", "2021-01-22 16:21:55.619356"], ["id", 2]]
D, [2021-01-22T16:21:55.627906 #516] DEBUG -- : (6.0ms) COMMIT
I, [2021-01-22T16:21:55.628651 #516] INFO -- : Disk Storage (0.4ms) Uploaded file to key: hjmhqkujbe2uxk6f9jfgmq5j4nn7 (checksum: fleS+FPvNcLEHaUysUgaGQ==)
I, [2021-01-22T16:21:55.632122 #516] INFO -- : Enqueued ActiveStorage::AnalyzeJob (Job ID: 5e869e72-e190-4a73-8938-14db177822a0) to Async(active_storage_analysis) with arguments: #<GlobalID:0x0000563b18c40938 #uri=#<URI::GID gid://cosplay/ActiveStorage::Blob/5>>
=> true
irb(main):004:0> D, [2021-01-22T16:21:55.691199 #516] DEBUG -- : ActiveStorage::Blob Load (0.3ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
I, [2021-01-22T16:21:55.691873 #516] INFO -- : Performing ActiveStorage::AnalyzeJob (Job ID: 5e869e72-e190-4a73-8938-14db177822a0) from Async(active_storage_analysis) enqueued at 2021-01-22T16:21:55Z with arguments: #<GlobalID:0x0000563b1947abf8 #uri=#<URI::GID gid://cosplay/ActiveStorage::Blob/5>>
I, [2021-01-22T16:21:55.797208 #516] INFO -- : Disk Storage (0.1ms) Downloaded file from key: hjmhqkujbe2uxk6f9jfgmq5j4nn7
D, [2021-01-22T16:21:55.845133 #516] DEBUG -- : (0.4ms) BEGIN
D, [2021-01-22T16:21:55.845853 #516] DEBUG -- : ActiveStorage::Blob Update (0.5ms) UPDATE "active_storage_blobs" SET "metadata" = $1 WHERE "active_storage_blobs"."id" = $2 [["metadata", "{\"identified\":true,\"width\":575,\"height\":862,\"analyzed\":true}"], ["id", 5]]
D, [2021-01-22T16:21:55.847209 #516] DEBUG -- : (0.9ms) COMMIT
I, [2021-01-22T16:21:55.847513 #516] INFO -- : Performed ActiveStorage::AnalyzeJob (Job ID: 5e869e72-e190-4a73-8938-14db177822a0) from Async(active_storage_analysis) in 155.55ms
Here's the model used in the above 2 examples.
class Attachment < ApplicationRecord
has_one_attached :file, dependent: :destroy
end
I really appreciate any help or insight anyone out there has on this issue; I essentially have a very large test suite which uses fixture_file_upload & Rack::Test::UploadedFile pretty much everywhere as they are very useful, however, as you can see they are not working anymore in combination with ActiveStorage.
Here are my Gemfile & Gemfile.lock if it helps debug what's going on; I've also included a minitest test file on that gist which has 2 failing and 1 succeeding test. I am happy to provide any additional details if it helps debug the issue.
Additional details which could be helpful:
Docker version 20.10.2, build 2291f61
docker-compose version 1.25.5, build unknown
I have the same problem, I know it's not the best solution, but it may work for you:
FactoryBot.define do
factory :user do
full_name { FFaker::NameBR.name }
email { FFaker::Internet.unique.email }
trait :with_avatar_image do
after(:build) do |user|
user.avatar_image.attach(io: File.open('public/examples/my_image.jpg'), filename: 'my_image.jpg')
end
end
end
end
So create this way:
let!(:user) { create(:user, :with_avatar_image) }
In my case, with params like this works:
Attachment.create(file: Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'files', 'test.jpg'), 'image/jpg'))
For tests, you may try this:
# Step 1, test_helper.rb
FactoryBot::SyntaxRunner.class_eval do
# If you want to use `fixture_file_upload` with Rails 6.0
include ActionDispatch::TestProcess
# You may need this to use `fixture_file_upload` with Rails 6.1
include ActiveSupport::Testing::FileFixtures
end
# Step 2, attachments.rb
factory :attachment do
name { 'name' }
# As a must have attribute
file {
fixture_file_upload(
Rails.root.join('test', 'fixtures', 'files', 'test.jpg'), 'image/jpg')
}
# As an optional attribute
trait :with_file do
file {
fixture_file_upload(
Rails.root.join('test', 'fixtures', 'files', 'test.jpg'), 'image/jpg')
}
end
end
# You could replace `fixture_file_upload` with `Rack::Test::UploadedFile.new`
# Step 3, now you can use:
# As a must have attribute
attachment1 = create(:attachment)
attributes1 = attributes_for(:attachment)
# As an optional attribute
attachment2 = create(:attachment, :with_file)
attributes2 = attributes_for(:attachment, :with_file)
I have an instance of a model which I'm playing with in my console. The instance is called mz.
irb(main):002:0> mz.updated_at
=> Fri, 24 Apr 2020 14:01:38 UTC +00:00
irb(main):003:0> mz.created_at
=> Fri, 24 Apr 2020 14:01:38 UTC +00:00
irb(main):004:0> mz.created_at == mz.updated_at
=> false
Why is this the case?
It looks to me like it should return true.
(I am trying to solve this problem for a non abstract coding problem, but best to describe it this way I think.)
Update #1...
irb(main):003:0> m.updated_at.to_i
=> 1587741257
irb(main):004:0> m.created_at.to_i
=> 1587741257
irb(main):005:0> m.updated_at == m.created_at
=> false
Confused? I still am.
Update #2 re #DennisvandeHoef
irb(main):002:0> m.updated_at.to_i
=> 1587741257
irb(main):003:0> m.created_at.to_i
=> 1587741257
irb(main):004:0> m.updated_at = m.created_at
=> Fri, 24 Apr 2020 15:14:17 UTC +00:00
irb(main):005:0> m.save(touch: false)
(0.2ms) BEGIN
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 14], ["LIMIT", 1]]
Tlk Load (0.4ms) SELECT "tlks".* FROM "tlks" WHERE "tlks"."id" = $1 LIMIT $2 [["id", 48], ["LIMIT", 1]]
Spkr Load (0.4ms) SELECT "spkrs".* FROM "spkrs" WHERE "spkrs"."id" = $1 LIMIT $2 [["id", 65], ["LIMIT", 1]]
ActionText::RichText Load (0.5ms) SELECT "action_text_rich_texts".* FROM "action_text_rich_texts" WHERE "action_text_rich_texts"."record_id" = $1 AND "action_text_rich_texts"."record_type" = $2 AND "action_text_rich_texts"."name" = $3 LIMIT $4 [["record_id", 74], ["record_type", "Msg"], ["name", "safe_content"], ["LIMIT", 1]]
Msg Update (0.6ms) UPDATE "msgs" SET "updated_at" = $1 WHERE "msgs"."id" = $2 [["updated_at", "2020-04-24 15:14:17.833380"], ["id", 74]]
(6.4ms) COMMIT
=> true
irb(main):006:0> m.updated_at == m.created_at
=> true
Interesting that it works, but I have no idea why it would be different! Any thoughts?
Here I have 2 models Branch and Subject which have many to many relationship through subjectgroup
The console shows the following result
~/workspace (master) $ rails c
Running via Spring preloader in process 4541
Loading development environment (Rails 4.2.7.1)
irb: warn: can't alias context from irb_context.
2.3.0 :001 > s=Subject.first
Subject Load (0.6ms) SELECT "subjects".* FROM "subjects" ORDER BY "subjects"."id" ASC LIMIT 1
=> #<Subject id: 1, idx_id: nil, fullname: "\tSubject1\t", created_at: "2017-05-06 23:06:32", updated_at: "2017-05-06 23:06:32">
2.3.0 :002 > s.branches.first
Branch Load (0.9ms) SELECT "branches".* FROM "branches" INNER JOIN"subjectgroups" ON "branches"."id" = "subjectgroups"."branch_id" WHERE"subjectgroups"."subject_id" = $1 ORDER BY "branches"."id" ASC LIMIT 1 [["subject_id", 1]]
=> #<Branch id: 1, name: "\Branch1\t", created_at: "2017-05-06 23:06:32", updated_at: "2017-05-06 23:06:32">
2.3.0 :003 > s.branches.find(2)
Branch Load (0.6ms) SELECT "branches".* FROM "branches" INNER JOIN "subjectgroups" ON "branches"."id" = "subjectgroups"."branch_id" WHERE "subjectgroups"."subject_id" = $1 AND "branches"."id" = $2 LIMIT 1 [["subject_id", 1], ["id", 2]]
=> #<Branch id: 2, name: "\tBranch2\t", created_at: "2017-05-06 23:06:32", updated_at: "2017-05-06 23:06:32">
2.3.0 :004 > s.branches.first.name
Branch Load (0.7ms) SELECT "branches".* FROM "branches" INNER JOIN "subjectgroups" ON "branches"."id" = "subjectgroups"."branch_id" WHERE "subjectgroups"."subject_id" = $1 ORDER BY "branches"."id" ASC LIMIT 1 [["subject_id", 1]]
=> "\tBranch1\t"
2.3.0 :005 > s.branches.find(2).name
Branch Load (0.7ms) SELECT "branches".* FROM "branches" INNER JOIN "subjectgroups" ON "branches"."id" = "subjectgroups"."branch_id" WHERE "subjectgroups"."subject_id" = $1 AND "branches"."id" = $2 LIMIT 1 [["subject_id", 1], ["id", 2]]
=> "\tBranch2\t"
2.3.0 :006 > s.branches.each do |branch|
2.3.0 :007 > branch.name
2.3.0 :008?> end
Branch Load (0.5ms) SELECT "branches".* FROM "branches" INNER JOIN "subjectgroups" ON "branches"."id" = "subjectgroups"."branch_id" WHERE "subjectgroups"."subject_id" = $1 [["subject_id", 1]]
=> [#<Branch id: 1, name: "\tBranch1\t", created_at: "2017-05-06 23:06:32", updated_at: "2017-05-06 23:06:32">, #<Branch id: 2, name: "\tBranch2\t", created_at: "2017-05-06 23:06:32", updated_at: "2017-05-06 23:06:32">]
2.3.0 :009 >
What should I do to make the result like
=> "\tBranch1\t" "\tBranch2\t"
you can try following code:
-It will print all branch names
s.branches.each do |branch|
puts branch.name
end
-It will return the names in array
s.branches.map(&:name)
The following throws a LocalJumpError
records.find_each.each_cons(3)
Is there a built-in way to use each_cons in memory-friendly batches?
Edit:
Ideally the overlap would work across the batch limit. each_cons interates over overlapping groups, so records with ids (0, 1, 2), (1, 2, 3), (2, 3, 4) etc.
If the batch size is 1000 (which I think it is for Rails), it would be less than optimal to have the overlap limited within the batch group. For example the records with indexes (997, 998, 999) then next iteration to (1000, 1001, 1002) is undesirable.
Here's a custom method to accomplish it:
def batched_each_cons(batch_size:, each_cons:, model:)
end_of_previous = []
model.find_in_batches(batch_size: batch_size) do |batch|
rebuilt_batch = end_of_previous + batch
end_of_previous = batch[(-(each_cons-1))..-1]
rebuilt_batch.each_cons(each_cons) do |cons|
yield cons
end
end
end
batched_each_cons(batch_size: 5, each_cons: 3, model: User) do | users |
# puts users.map(&:name)
puts users.map(&:id).inspect
end
This gives:
User Load (0.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 5]]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" > $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 5], ["LIMIT", 5]]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" > $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 10], ["LIMIT", 5]]
[9, 10, 11]
[10, 11, 12]
[11, 12, 13]
[12, 13, 14]
[13, 14, 15]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" > $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 15], ["LIMIT", 5]]
[14, 15, 16]
[15, 16, 17]
[16, 17, 18]
[17, 18, 19]
[18, 19, 20]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" > $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 20], ["LIMIT", 5]]
(This is in my local console, normally you would not see 'User Load...', that is there to show the batching is working)
yeah, like this:
User.all.find_in_batches do |group|
group.each_cons(3) { |users| puts users.map(&:name) }
end
I'm using:
ranking = Ranking.create()
ranking.send("#{month}=", rank)
ranking.save!
I'd like to append whatever value is in the #{month} column, not replace it. For example, if I am performing:
month = 'january'
ranking.send("#{month}=", 500)
ranking.save!
And then again later on:
month = 'january'
ranking.send("#{month}=", 250)
ranking.save!
The value for the column january for that particular ranking should be 750.
Is this possible with the current ActiveRecord API?
You could do this with increment! method
month = 'january'
ranking.increment!(month, 250)
updated:
to proof comments question (e.g. month = 'jan'):
irb(main):011:0> p.increment!(month, 70)
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "products" SET "jan" = ?, "updated_at" = ? WHERE "product
"."id" = 1 [["jan", 171], ["updated_at", Sun, 06 Oct 2013 04:23:54 UTC +00:00]
(0.0ms) commit transaction
=> true
irb(main):012:0> p
=> #<Product id: 1, name: nil, description: nil, jan: 171, created_at: "2013-10-
06 04:22:50", updated_at: "2013-10-06 04:23:54">
and another case
irb(main):013:0> p.increment!("#{month}", 70)
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "products" SET "jan" = ?, "updated_at" = ? WHERE "products
"."id" = 1 [["jan", 241], ["updated_at", Sun, 06 Oct 2013 04:24:10 UTC +00:00]]
(0.0ms) commit transaction
=> true
irb(main):014:0> p
=> #<Product id: 1, name: nil, description: nil, jan: 241, created_at: "2013-10-
06 04:22:50", updated_at: "2013-10-06 04:24:10">