{"id"=>"asset/766a9cbbec3f2607cc447165850ccded.jpg", "storage"=>"cache", "metadata"=>{"filename"=>"f76d3177-da13-4c08-9def-f1e9a76b7211.jpg", "size"=>126528, "mime_type"=>"image/jpeg"}}
NoMethodError: undefined method `[]' for #<ImageUploader::UploadedFile:0x00007fc281662718>
{:original=> #<ImageUploader::UploadedFile:0x00007fc2831766f8 @data={"id"=>"asset/16511/nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"nature-forest-industry-rails.jpg", "size"=>5081543, "mime_type"=>"image/jpeg"}}>, :huge=> #<ImageUploader::UploadedFile:0x00007fc283176590 @data={"id"=>"asset/16511/huge_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"huge_nature-forest-industry-rails.jpg", "size"=>983053, "mime_type"=>"image/jpeg"}}>, :large=> #<ImageUploader::UploadedFile:0x00007fc283176428 @data={"id"=>"asset/16511/large_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"large_nature-forest-industry-rails.jpg", "size"=>458088, "mime_type"=>"image/jpeg"}}>, :medium=> #<ImageUploader::UploadedFile:0x00007fc2831762c0 @data={"id"=>"asset/16511/medium_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"medium_nature-forest-industry-rails.jpg", "size"=>98342, "mime_type"=>"image/jpeg"}}>, :small=> #<ImageUploader::UploadedFile:0x00007fc283176158 @data={"id"=>"asset/16511/small_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"small_nature-forest-industry-rails.jpg", "size"=>32982, "mime_type"=>"image/jpeg"}}>, :tiny=> #<ImageUploader::UploadedFile:0x00007fc283175ff0 @data={"id"=>"asset/16511/tiny_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"tiny_nature-forest-industry-rails.jpg", "size"=>14988, "mime_type"=>"image/jpeg"}}>}
Shrine::Error: {"original"=>{"id"=>"asset/16511/nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"nature-forest-industry-rails.jpg", "size"=>5081543, "mime_type"=>"image/jpeg"}}, "huge"=>{"id"=>"asset/16511/huge_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"huge_nature-forest-industry-rails.jpg", "size"=>983053, "mime_type"=>"image/jpeg"}}, "large"=>{"id"=>"asset/16511/large_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"large_nature-forest-industry-rails.jpg", "size"=>458088, "mime_type"=>"image/jpeg"}}, "medium"=>{"id"=>"asset/16511/medium_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"medium_nature-forest-industry-rails.jpg", "size"=>98342, "mime_type"=>"image/jpeg"}}, "small"=>{"id"=>"asset/16511/small_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"small_nature-forest-industry-rails.jpg", "size"=>32982, "mime_type"=>"image/jpeg"}}, "tiny"=>{"id"=>"asset/16511/tiny_nature-forest-industry-rails.jpg", "storage"=>"store", "metadata"=>{"filename"=>"tiny_nature-forest-industry-rails.jpg", "size"=>14988, "mime_type"=>"image/jpeg"}}} isn't valid uploaded file datafrom /Users/Rado/.gem/ruby/2.4.2/gems/shrine-2.8.0/lib/shrine.rb:733:in `initialize'
I think versions should extends original data format, not change it.
On the other hand Shrine versions are destructive.
Version plugin change data structure instead append/extend them. If someone adds versions plugin and start using version it completely change data structure (*_data).
Without Version plugin it creates clean and easy to maintain data. Attributes "id", "storage", "metadata" etc. for example
{"id"=>"asset/766a9cbbec3f2607cc447165850ccded.jpg", "storage"=>"cache", "metadata"=>{"filename"=>"f76d3177-da13-4c08-9def-f1e9a76b7211.jpg", "size"=>126528,"mime_type"=>"image/jpeg"}}
But after enable Version plugin, it is needed to update data again. Better solution should be create version on demand. So when data are not migrated response is:
NoMethodError: undefined method `[]' for #<ImageUploader::UploadedFile:0x00007fc281662718>
As you can see, it is ImageUploader class
After disabling Version plugin it doesn't work anymore:
...
So again, next data updating.
What i can do is write own version plugin.
--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/305e1498-32d0-441a-b6fc-b429f8913bd6%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
{ "id": "asset/766a9cbbec3f2607cc447165850ccded.jpg", "storage": "file", "metadata": { "filename": "f76d3177-da13-4c08-9def-f1e9a76b7211.jpg", "size": 126528, "mime_type": "image/jpeg" }, "versions": { "large": { "id": "asset/766a9cbbec3f2607cc447165850ccded_large.jpg", "storage": "file", "metadata": { "filename": "f76d3177-da13-4c08-9def-f1e9a76b7211_large.jpg", "size": 56528, "mime_type": "image/jpeg" } }, "another": {} }}
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.
{
"original": {
"id": "asset/766a9cbbec3f2607cc447165850ccded.jpg",
"storage": "file",
"metadata": {
"filename": "f76d3177-da13-4c08-9def-f1e9a76b7211.jpg",
"size": 126528,
"mime_type": "image/jpeg"
}
},
"versions": {
"large": {
"id": "asset/766a9cbbec3f2607cc447165850ccded_large.jpg",
"storage": "file",
"metadata": {
"filename": "f76d3177-da13-4c08-9def-f1e9a76b7211_large.jpg",
"size": 56528,
"mime_type": "image/jpeg"
}
},
"another": {}
}
}
or
{
"original": {
"id": "asset/766a9cbbec3f2607cc447165850ccded.jpg",
"storage": "file",
"metadata": {
"filename": "f76d3177-da13-4c08-9def-f1e9a76b7211.jpg",
"size": 126528,
"mime_type": "image/jpeg"
}
},
"large": {
"id": "asset/766a9cbbec3f2607cc447165850ccded_large.jpg",
"storage": "file",
"metadata": {
"filename": "f76d3177-da13-4c08-9def-f1e9a76b7211_large.jpg",
"size": 56528,
"mime_type": "image/jpeg"
}
},
"another": {}
}
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/b5da761f-e07e-4e73-b5c2-5a1d99d06336%40googlegroups.com.
photo.image_attacher.versions = {
# ...
}
photo.image_attacher.add_version(:small, small_version)
photo.image_attacher.remove_version(:small)
processed_versions = {
small: resize_to_limit(io.download),
# ...
}
versions_for_assignment = ImageUploader.new(:store).upload(processed_versions)
photo.image_attacher.versions # returns a Shrine::Versions object
photo.image_attacher.versions = { ... }
photo.image_attacher.versions.add(:small, small_version)
photo.image_attacher.versions.remove(:small)
photo.image_versions
photo.image_versions = { ... }
photo.image_versions.add(:small, small_version)
photo.image_versions.remove(:small)
photo.image_versions.replace({...})
photo.image_versions[:small] #=> Shrine::UploadedFile
photo.image_versions.update([...])
photo.image_versions[0] #=> Shrine::UploadedFile
movie.video_versions[:hls, :640_360, :segments, 0]
movie.video_versions[:hls][:640_360][:segments][0]
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/16b4527e-c800-43c4-abfc-564464172abf%40googlegroups.com.
In the mean time, will there be accommodation for method #1 in Radovan's email where the versions of an original file are uploaded to storage by a user (because it was created by designer) instead of being created in some background job?
I would like the brainstorm here what the new versions API could look like. I think that Shrine can be often a bit too implicit (some other users thought that too), so I would like the new API for generating versions to have to be called manually. So, instead of defining processing in the uploader, you would be able to call it like this:photo.image_attacher.versions = {
# ...
}
I would also like users to have the option of being more granular, that we also have Attacher#add_version and Attacher#remove_version:photo.image_attacher.add_version(:small, small_version)
photo.image_attacher.remove_version(:small)
The Attacher#versions= and Attacher#add_version methods would both accept Shrine::UploadedFile objects, which would need to be created beforehand. I think It's ok that the user has to explicitly upload them:processed_versions = {
small: resize_to_limit(io.download),
# ...
}
versions_for_assignment = ImageUploader.new(:store).upload(processed_versions)
Thinking about it more, it would probably be better to have a Versions class and define all methods on it:photo.image_attacher.versions # returns a Shrine::Versions object
photo.image_attacher.versions = { ... }
photo.image_attacher.versions.add(:small, small_version)
photo.image_attacher.versions.remove(:small)
We would probably have an #<attachment>_versions method which makes it easier to access:photo.image_versions
photo.image_versions = { ... }
photo.image_versions.add(:small, small_version)
photo.image_versions.remove(:small)
We probably wouldn't need #<attachment>_versions= actually, we could probably name it Versions#replace instead:photo.image_versions.replace({...})
The Versions class would have the #[] operator just like a Hash:photo.image_versions[:small] #=> Shrine::UploadedFile
But it wouldn't subclass Hash, because there are some downsides of that, and because I would also like it to be able to support Arrays. The ability to support an Array of versions came up a few times, e.g. when wanting to save encoded video or split a PDF into pages.photo.image_versions.update([...])
photo.image_versions[0] #=> Shrine::UploadedFile
The versions should be infinitely nestable, because the video encoding example needs this feature. For traversing through nesting we use subscript operator with multiple arguments:movie.video_versions[:hls, :640_360, :segments, 0]
or maybe it's enough to just have subscript operator be able to return hashes and arrays as well, and then we can traverse normally:movie.video_versions[:hls][:640_360][:segments][0]
movie.video_versions[:full_hd][:color][:h264]
movie.video_versions.dig(:full_hd, :color, :h264)
movie.video_attacher.versions[:full_hd].versions[:color].versions[:h264]
The Versions class should have #to_h and #to_a conversion methods.
These are some of the ideas that came to mind. What I would like is that users have complete control of managing versions. E.g. they might want to generate some versions in the foreground, and others in the background, and maybe others later in another background job. They might also want to store the original file in a different location than versions, that's why users should have the ability to choose to which storage they want to upload versions. I might be missing some more use cases that came up in the past.Kind regards,Janko
There should be 3 different ways how to get version what should be needed.
One issue is still quite open. Meta about versions will be stored inside "_data" or could be extracted outside?
Just one case. I have 5 versions. Original file will be saved and metadata about that file persisted in database. Versions will be created async in background. When version is processed, meta about version are appended. It is ok for most cases. But every-time what is version processed and meta persisted is running callback what will change "updated_at" column for Rails.
Is there any way how to persist meta about version in another table?
Anyway by reading this blogpost https://evilmartians.com/chronicles/rails-5-2-active-storage-and-beyond i have a feeling if word "versions" has right meaning. In ActiveStorage is used name "variant". Different meanings of words version and variant: https://english.stackexchange.com/questions/192032/what-is-the-make-or-break-difference-between-version-and-variant?rq=1
What do you think?
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/0c5f5389-e526-402b-82bc-73606333461e%40googlegroups.com.
i dont know why my reply message was automatically deleted.
I like separated proxy object. It allows editable/extendable interface. It also allows to very exciting things with derivatives.
Exists have 2 options:
I am ok with derivative. It is mostly used with image processing. I prefer short variation "derivates", singular "derivate". It is same root from latin word derivatus, later derivat.
Do you want to continue in Github Issue?
Hello,i dont know why my reply message was automatically deleted. So i repost it again but shorter :/
On Monday, April 2, 2018 at 12:07:39 PM UTC+2, Janko Marohnić wrote:Sorry for the late reply.There should be 3 different ways how to get version what should be needed.I agree with this.Though it now raises the question, is it good that the object returned by `movie.video_versions` and `movie.video_attacher.versions` is both a value object and an object which you can use to add/remove/update versions. That was one of the things I really minded in CarrierWave and Paperclip design, that `movie.video` returns a CarrierWave::Uploader::Base/Paperclip::Attachment object that is used both for retrieving the uploaded file data and for doing all other operations, which makes the interface bloated and unclear.I'm now thinking that the following design would be better:
- `movie.video_versions` returns a hash/array of Shrine::UploadedFile objects, just like the current `movie.video` returns when versions are used. Users could then fetch the specific version by using the native Hash#dig/Array#dig.
- `movie.video_versions` just delegates to `movie.video_attacher.versions`
- Instead of having `Shrine::Attacher#versions` return a `Shrine::Versions` proxy object (like ActiveRecord's association proxy objects), we define all the version management methods on the Shrine::Attacher object directly, i.e. `movie.video_attacher.add_version(...)`, `movie.video_attacher.remove_version(...)`, `movie.video_attacher.update_versions(...)` etc. (like Sequel's association methods). I think this would make for a simpler and more consistent API.
What do you think about this API?
I like separated proxy object. It allows editable/extendable interface. It also allows to very exciting things with derivatives.
One issue is still quite open. Meta about versions will be stored inside "_data" or could be extracted outside?
Just one case. I have 5 versions. Original file will be saved and metadata about that file persisted in database. Versions will be created async in background. When version is processed, meta about version are appended. It is ok for most cases. But every-time what is version processed and meta persisted is running callback what will change "updated_at" column for Rails.
Is there any way how to persist meta about version in another table?In #254 Jonathan and I were discussing that it would be better to store versions in a separate "<attachment>_versions_data" column than to chuck everything into the same "<attachment>_data" column. That would make versions truly an addition to the regular file upload flow, instead of potentially breaking something off when reading the "<attachment>_data" column.Saving versions in a separate table could work too, though it would be more complex because it would require ORM-specific logic. In general I would like to enable users of Shrine to be able to store attachments in a separate table like in ActiveStorage. Note that it is already possible, it just needs to be manually configured (ActiveStorage's design can be built on top of Shrine's existing design). But for versions we would need to add special functionality to allow storing each version as a separate record, so we would first need to discuss if there are any significant advantages to that. If we would store all versions in the same DB column (either in the same table or a separate table), then there should be no need for special ORM-specific logic to enable that feature.What do you think that for now we use the "<attachment>_versions_data" column (which can also be moved to a separate "attachment" table)?
Exists have 2 options:1. Separate column "<attachment>_versions_data"- isolated from original file- easy to add queue as process and create different version (linear or concurrent) together- complicated to handle multiple queue processes when every process is every version (need to lock column and merge metadata)- thin ORM integration- more logic on Shrine side2. Separate table "<model>_<attachment>_versions"- with two minimal columns "version" and "data" where "version" is name of version- easy multiprocess handling, no need merging, every "row" is isolated version- "thick" ORM integration- less logic on Shrine, more on ORM (Shrine in basic needs to create version, store it and returns "metadata" which is delegated to ORM- better auditing and regenerating what is needed (logging and audit)- just another "model" associated with original file
Anyway by reading this blogpost https://evilmartians.com/chronicles/rails-5-2-active-storage-and-beyond i have a feeling if word "versions" has right meaning. In ActiveStorage is used name "variant". Different meanings of words version and variant: https://english.stackexchange.com/questions/192032/what-is-the-make-or-break-difference-between-version-and-variant?rq=1
What do you think?I completely agree, Jonathan raised the same point in #254. I would say that using different naming is even necessary, because I would like both the existing `versions` plugin and the new plugin to be able to coexist at the same time (and later we would deprecate and remove the old plugin), which would mean the new plugin would need a different name. "Variants" is an option, but Jonathan suggested a name which I find even better – "derivatives". I think this is more descriptive and more generic, because e.g. I wouldn't call a movie screenshot a "variant" of the original movie, because the movie is a video and a screenshot is an image. What do you think about that name?
I am ok with derivative. It is mostly used with image processing. I prefer short variation "derivates", singular "derivate". It is same root from latin word derivatus, later derivat.Do you want to continue in Github Issue?Regards!Kind regards,Janko
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/0c5f5389-e526-402b-82bc-73606333461e%40googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/9ca26d6c-8c5b-4ad2-bb3b-8b03ba05e0ad%40googlegroups.com.
Thanks, do you have existing code anywhere I can look at? It might be helpful to me. If I get anywhere, I’ll try to put my code somewhere you can see, so you can advise or collaborate in any other way? maybe just a shrine fork in my GH? I definitely could use some help!
I think that is a valid use case, but I’d suggest it’s NOT the role of the versions/derivatives plugin.
I BELIEVE that can easily be done already without versions/derivatives plugin, you can for instance use the `processing` plugin without versions, to substitute an original, yes?
Similarly, I think “Have temp download links that expire and/or obfuscate file storage urls” is probably out of scope – you might want to do that with originals OR derivatives, you can already do it to some extent (say if you are using S3, and used signed urls, which you can already do in shrine). We should pay attention to architecture to make sure that it’s no harder to do this for derivatives than for originals, but I think it’s not part of a derivatives plugin. If you want an end-point in your app to obfuscate the back-end storage location, probably by proxying bytes, I think that may be out of scope for shrine as a whole, but is definitely out of scope for derivatives plugin.
One thing I mentioned that might be worth including on the list as a stretch goal, is the ability to have _different_ derivatives in different stores. Only because that may or may not effect the architecture – even if it’s not there right away, we should see a path to adding it backwards compatibly.
Another thing I forgot to mention but which is important – it should be possible to create derivatives in background jobs, with a separate background job for each derivative. And relatedly, to create some derivatives in a delayed fashion long after the others were created (sort of a very long-term async). This is tricky because of potential race conditions, and is confusing to me how to address because the solution(s) probably need to be ORM-specific, and it’s not clear to me how to handle that in a shrine plugin.
Really, gets confusing ot me when I think about, I think I need to just play with shrine code some more.
Jonathan
__________________________________
Jonathan Rochkind
Software Developer/Technical Lead
Othmer Library of Chemical History
t. +1.215.873.8239
Science History Institute
Chemistry • Engineering
• Life Sciences
315
Chestnut Street • Philadelphia, PA 19106 • U.S.A.
sciencehistory.org
--
You received this message because you are subscribed to a topic in the Google Groups "Shrine" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ruby-shrine/G-WooM709YY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ruby-shrine...@googlegroups.com.
To post to this group, send email to
ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/4bb6b06c-db81-4159-a459-6ed557e1c09b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
.
To unsubscribe from this group and all its topics, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to
To view this discussion on the web visit
However, I do not think I have a need/desire for a whole separate table for derivatives, with one line each, as proposed here. While it should be noted that that is what ActiveStorage does, I think simply a separate column (still having a hash) is sufficient, possibly preferable, and should make it a lot easier to achieve goals with relatively less new code -- and has fewer performance challenges with n+1 queries etc.
I need derivatives to have differently configured plugins than the original. For instance, you might want checksums or other metadata on the original that you simply don't need on the derivatives. (Not sure if current 'versions' easily supports that or not).
class MyUploader < Shrine
plugin :add_metadata
plugin :signature
add_metadata do |io, context|
calculate_signature(io, :md5) if context[:version].nil? || context[:version] == :original
end
end
One trick I don't totally understand is making sure the derivatives (now in this separate column/attacher) get cleared out when the original changes or is removed -- where to hook into to catch that.
Does this basic approach seem reasonable? Seem like something that might make sense in shrine core as an improved version of `versions`? Any tips on implementation, or more specific ideas for what public facing API should like?
photo.image.download do |original|
pipeline = ImageProcessing::MiniMagick.source(original)
derivates = {
large: pipeline.resize_to_limit!(800, 800),
medium: pipeline.resize_to_limit!(500, 500),
small: pipeline.resize_to_limit!(300, 300),
# ...
}
photo.image_attacher.add_derivates(derivates)
# or
derivates.each do |name, file|
photo.image_attacher.add_derivate(name, file)
end
end
Can the original be replaced like if I didn’t want to store RAW but same res JPG version instead? (TBD)
Be thread safe and avoid DB race conditions when updating file_data
record.db.transaction do
record.lock! # SELECT * WHERE id = '123' FOR UPDATE LIMIT 1;
record.update(attachment_data: "...")
end # lock is released
Is the migration path easy for existing Shrine users?
Have temp download links that expire and/or obfuscate file storage urls (TBD)
.
To unsubscribe from this group and all its topics, send an email to ruby-shrine...@googlegroups.com.
To post to this group, send email to
To view this discussion on the web visit
--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/fe1ee9a6-d9f8-4658-ae9f-ea90baa5b98e%40googlegroups.com.
The derivates plugin could override Attacher#assign, which is what gets called when someone sets the new attachment (e.g. `photo.image = file`). Whenever a new value gets assigned, the derivates column could get cleared.
I've recently found out about the SELECT ... FOR UPDATE locking mechanism that SQL databases offer, which is supported both by ActiveRecord (see pessimistic locking) and Sequel (see Locking for Update and #lock!). I think that could be an elegant solution for DB race conditions, here is how it looks in Sequel:record.db.transaction do
record.lock! # SELECT * WHERE id = '123' FOR UPDATE LIMIT 1;
record.update(attachment_data: "...")
end # lock is released
Thread safety could be achieved with a simple mutex.
Is the migration path easy for existing Shrine users?Thanks for noting that, we should definitely think about the migration path when we release it.
In your conception, does "photo.image_attacher.add_derivates" make an immediate save to the db through the ORM? I am thinking probably yes?
The derivates plugin could override Attacher#assign, which is what gets called when someone sets the new attachment (e.g. `photo.image = file`). Whenever a new value gets assigned, the derivates column could get cleared.I'm not sure about this. Not sure if the "derivatives column gets cleared" means in-memory, or already persisted to the ORM.The trick is dealing with the actual files persisted to storage. Once the new thing is for real saved (even to cache), we need to delete the derivatives from storage. But if someone does an assign, but ends up not saving the model (succesfully, or even trying), then we need the derivative files in storage to still be there, since the model still points to them (and might need them!).
I've recently found out about the SELECT ... FOR UPDATE locking mechanism that SQL databases offer, which is supported both by ActiveRecord (see pessimistic locking) and Sequel (see Locking for Update and #lock!). I think that could be an elegant solution for DB race conditions, here is how it looks in Sequel:record.db.transaction do
record.lock! # SELECT * WHERE id = '123' FOR UPDATE LIMIT 1;
record.update(attachment_data: "...")
end # lock is released
Thread safety could be achieved with a simple mutex.I'm not sure what kinds of thread-safety you are thinking about here, concurrency always twists my brain around.But don't forget that concurrency-safety using the database is not limited to "thread safety" -- there can be different _processes_, even running on different machines (which of course is a typical way to deploy a Rails app, and probably any web app under MRI).Of course the DB-level pessimistic lock is intended to handle that. If it handles it succesfully (and I am scared of db pessimistic locks, I feel like they have unpredictable and sometimes disastrous performance characteristics) -- is any additional kind of thread safety requiring a mutex required? I don't _think_ so, but am not sure.
Yeah, for me just having a separate column is the right way to go. Not having to think about N+1 queries as you said is one thing. The other thing is that having a record per derivate (like ActiveStorage has) is a big performance hit, because then query performance diminishes with more attachments and more derivates per attachment; this is unnecessary performance penalty IMO, especially if you have a lot of derivates such as when you want to store videos in HLS format.
Hiren, how does a separate column sit with you?
--
You received this message because you are subscribed to a topic in the Google Groups "Shrine" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ruby-shrine/G-WooM709YY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/674a7b04-e5b0-44db-a9cb-53835ab10ffa%40googlegroups.com.
I'm not sure about this. Not sure if the "derivatives column gets cleared" means in-memory, or already persisted to the ORM.
The trick is dealing with the actual files persisted to storage. Once the new thing is for real saved (even to cache), we need to delete the derivatives from storage. But if someone does an assign, but ends up not saving the model (succesfully, or even trying), then we need the derivative files in storage to still be there, since the model still points to them (and might need them!).
photo.image #=> #<ShrineUploadedFile: @data={"id" => "111", "storage" => "store"} >
photo.image_derivates #=> { ... hash of derivates ... }
# assigning a new image clears the derivates column in-memory
photo.image = new_image
photo.image #=> #<ShrineUploadedFile: @data={"id" => "222", "storage" => "cache"} >
photo.image_derivates #=> nil
# that change still isn't persisted
Photo.find(photo.id).image_derivates #=> { ... hash of derivates ... }
# saving the record deletes the old derivates from storage
photo.save
photo.image #=> #<ShrineUploadedFile: @data={"id" => "333", "storage" => "store"} >
I think clearing derivatives should only happen when user intent is clear i.e. they replaced original AND actually persisting it to the db by saving the record. Let's see how we can make the code do that unless others have different opinion we can discuss it then.
Of course the DB-level pessimistic lock is intended to handle that. If it handles it succesfully (and I am scared of db pessimistic locks, I feel like they have unpredictable and sometimes disastrous performance characteristics) -- is any additional kind of thread safety requiring a mutex required? I don't _think_ so, but am not sure.
pipeline = ImageProcessing::Vips.source(photo.image)
derivate_definitions = {
large: [800, 800],
medium: [500, 500],
small: [300, 300],
}
derivate_definitions.each do |name, (width, height)|
thread_pool.post do # this block executes asynchronously
derivate = pipeline.resize_to_limit!(width, height)
photo.image_attacher.add_derivate(name, derivate) # adds the derivate in-memory
end
end
photo.save
The separate column avoids cluttering the original column with versions which can be nice for select queries but downside is requires a migration and you have twice as many columns as files stored in a model and keeping each pair sync'd. The single original column is opposite.
One thought is we can also look at the other file attachment libraries (not just the ruby ones) and see how whether people liked single vs separate columns.
To unsubscribe from this group and all its topics, send an email to ruby-shrine...@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/674a7b04-e5b0-44db-a9cb-53835ab10ffa%40googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/CALZSzMGWpy9K5k_Z9-j%3DArVpTb%2BKoL71THEKhFWB7%3DS9xXX%3Ddg%40mail.gmail.com.
So, with that code, where the change to derivatives aren’t persisted until save… if they are still going to be race-condition safe… I guess you would have to somehow trigger that race condition safe update code _on_ save, rather than on set? It all gets to be a bit overwhelming for me honestly, my code isn’t going great.
__________________________________
Jonathan Rochkind
Software Developer/Technical Lead
Othmer Library of Chemical History
t. +1.215.873.8239
Science History Institute
Chemistry • Engineering
• Life Sciences
315
Chestnut Street • Philadelphia, PA 19106 • U.S.A.
sciencehistory.org
From: <ruby-...@googlegroups.com> on behalf of Janko Marohnić <janko.m...@gmail.com>
Date: Monday, September 17, 2018 at 7:16 PM
To: "jona...@dnil.net" <jona...@dnil.net>
Cc: ruby-shrine <ruby-...@googlegroups.com>
Subject: Re: Uploaded versions separation
.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/CAJ8a-RMSTGXfJkWCnmjad0%3DsDcHgDkipUcRDCT%3D874pCm%3D7ThQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.