Models
Defining accessors
Any class can be extended to add Dragonfly attachment functionality.
Let’s say you have a model with methods image_uid
and image_uid=
class Photo
attr_accessor :image_uid
end
Then you can add the Dragonfly attachment methods image
and image=
with
class Photo
extend Dragonfly::Model
dragonfly_accessor :image
end
To define an accessor using a named (i.e. non-default) Dragonfly app such as
Dragonfly.app(:assets)
you can pass a the app name as an option
dragonfly_accessor :image, :app => :assets
If the model class responds to before_save
and before_destroy
(like with ActiveRecord) then
the attachment will be stored on save (when changed) and destroyed on destroy.
To extend all ActiveRecord models, you can do
ActiveRecord::Base.extend Dragonfly::Model
Using the accessors
We can use the attribute much like other other model attributes:
@photo = Photo.new
@photo.image = "\377???JFIF\000\..." # can assign as a string...
@photo.image = File.new('path/to/my_image.png') # ... or as a file...
@photo.image = some_tempfile # ... or as a tempfile...
@photo.image = Pathname.new('some/path.gif') # ... or as a pathname...
@photo.image = @photo.band_photo # ... or as another Dragonfly attachment
@photo.image # => <Dragonfly Attachment uid=nil, app=:default>
When setting with a string you should also set a name so that Dragonfly knows its mime-type
@photo.image = "\377???JFIF\000\..."
@photo.image.name = "file.png"
@photo.image.mime_type # ===> "image/png"
Set to nil
to remove it
@photo.image = nil
@photo.image # => nil
We can inspect properties of the attribute
@photo.image.mime_type # => 'image/png'
@photo.image.size # => 63425 (size in bytes)
We can add analyser methods (see Analysers), e.g. the ImageMagick plugin adds
@photo.image.width # => 280
@photo.image.height # => 140
We can play around with the data
@photo.image.data # => "\377???JFIF\000\..."
@photo.image.to_file('out.png')
see How does it work? - Using the content for more examples (Attachment
objects work in a similar way to Job
objects).
We can add processor methods (see Processors), e.g. the ImageMagick plugin adds
image = @photo.image.thumb('20x20') # returns a 'Job' object
image.width # => 20
image = @photo.image.encode('gif')
image.format # => 'gif'
Assigning from a URL
Dragonfly provides an accessor for assigning directly from a url:
@photo.image_url = 'http://some.url/file.jpg'
You can put this in a form view, e.g. in rails erb:
<%= f.text_field :image_url %>
It also works for data uris
@photo.image_url = "data:image/jpeg;base64,GEGseg42g3g..."
Removing an attachment via a form
Normally unassignment of an attachment is done like any other attribute, by setting to nil
@photo.image = nil
but this can’t be done via a form - instead remove_<attachment_name>
is provided, which can be used with a checkbox:
<%= f.check_box :remove_image %>
Retaining across form redisplays
When a model fails validation, you don’t normally want to have to upload your attachment again, so you can avoid having to do this by
including a hidden field in your form retained_<attribute_name>
, e.g.
<% form_for @photo, :html => {:multipart => true} do |f| %>
...
<%= f.file_field :image %>
<%= f.hidden_field :retained_image %>
...
<% end %>
Persisting
When the model is saved, a before_save callback persists the data to the Dragonfly app’s configured datastore. The uid column is then filled in.
@photo = Photo.new
@photo.image_uid # => nil
@photo.image = File.new('path/to/my_image.png')
@photo.image_uid # => nil
@photo.save
@photo.image_uid # => '2009/12/05/file.png' (some unique uid, used by the datastore)
URLs
Once the model is saved, we can get a url for the image which will be served by Dragonfly, and for its processed versions.
@photo.image.url # => '/media/W1siZyIsInRleHQ...'
@photo.image.thumb('300x200#nw').url # => '/media/WgsdoicjdslliZy...'
@photo.image.encode('gif').url # => '/media/Wiflkpiubppndsh...'
Because the processing methods are lazy, no processing is actually done in the above code.
Reflection methods
xxx_stored?
tells you if the content has been stored in the data store.
It’s useful for deciding whether the url is available or not
<%= image_tag @photo.image.thumb('20x30').url if @photo.image_stored? %>
xxx_changed?
tells you if the content has been changed since saving.
It’s useful for deciding whether to validate or not
@photo.image_changed? # ===> false
@photo.image = some_file
@photo.image_changed? # ===> true
Validations
validates_presence_of
and validates_size_of
work out of the box, and Dragonfly also provides validates_property
.
class Photo
extend Dragonfly::Model::Validations
validates_presence_of :image
validates_size_of :image, maximum: 500.kilobytes
# Check the file extension
validates_property :ext, of: :image, as: 'jpg'
# ..or..
validates_property :mime_type, of: :image, as: 'image/jpeg'
# ..or actually analyse the format with imagemagick..
validates_property :format, of: :image, in: ['jpeg', 'png', 'gif']
validates_property :width, of: :image, in: (0..400), message: "é demais cara!"
# ..or you might want to use image_changed? method..
validates_property :format, of: :image, as: 'png', if: :image_changed?
# ...
end
validates_property
will work for any property, not just Dragonfly analysers, because internally it simply calls send
on the attribute.
It does nothing if the accessor is not set (i.e. returns nil
).
It can also take a proc for the message
validates_property :width, of: :image, in: (0..400),
message: proc{|actual, model| "Unlucky #{model.title} - was #{actual}" }
Name and extension
If the object assigned is a file, or responds to original_filename
(as is the case with file uploads in Rails, etc.), then name
will be set.
@photo.image = File.new('path/to/my_image.png')
@photo.image.name # => 'my_image.png'
@photo.image.ext # => 'png'
Meta data
You can store metadata along with the content data of your attachment:
@photo.image = File.new('path/to/my_image.png')
@photo.image.meta['taken'] = Date.yesterday.to_s
@photo.save!
@photo.reload.image.meta['taken'] # ==> '2018-04-05'
NOTE meta must be serializable to/from JSON, i.e. consist of strings, numbers, booleans, NOT symbols, dates, etc.
Meta data can be useful because at the time that Dragonfly serves content, it doesn’t have access to your model, but it does have access to the meta data that was stored alongside the content, so you could use it to provide custom response headers, etc.
Default content
Normally if an accessor is not set it returns nil
@photo.image # ===> nil
However we can get it to return something by default by giving a path
class Photo
dragonfly_accessor :image do
default 'public/images/default.png'
end
end
@photo.image # ===> Job object
@photo.image.url # ===> "/WsDf32g...."
the Job object returned is equivalent to
Dragonfly.app.fetch_url('public/images/default.png')
Callbacks
after_assign
after_assign
can be used to do something every time content is assigned:
class Person
dragonfly_accessor :mugshot do
after_assign{|a| a.rotate!(90) } # 'a' is the attachment itself
end
end
person.mugshot = Pathname.new('some/path.png') # after_assign callback is called
person.mugshot = nil # after_assign callback is NOT called
Inside the block, you can call methods on the model instance directly (self
is the model):
class Person
dragonfly_accessor :mugshot do
after_assign{|a| a.rotate!(angle) }
end
def angle
90
end
end
Alternatively you can pass in a symbol, corresponding to a model instance method:
class Person
dragonfly_accessor :mugshot do
after_assign :rotate_it
end
def rotate_it
mugshot.rotate!(90)
end
end
You can register more than one after_assign
callback.
after_unassign
after_unassign
is similar to after_assign
, but is only called when the attachment is unassigned
person.mugshot = Pathname.new('some/path.png') # after_unassign callback is NOT called
person.mugshot = nil # after_unassign callback is called
Up-front processing
The best way to create different versions of content such as thumbnails is generally on-the-fly, however if you must
create another version on-upload, then you could create another accessor and automatically copy to it using copy_to
.
class Person
dragonfly_accessor :mugshot do
copy_to(:smaller_mugshot){|a| a.thumb('200x200#') }
end
dragonfly_accessor :smaller_mugshot
end
person.mugshot = Pathname.new('some/400x300/image.png')
person.mugshot # ---> 400x300 image
person.smaller_mugshot # ---> 200x200 image
In the above example you would need both a mugshot_uid
field and a smaller_mugshot_uid
field on your model.
You can also do this manually (e.g. in a background task) using
person.smaller_mugshot = person.mugshot.thumb('200x200#')
person.save
Storage options
Some datastores take options when calling store
- you can pass these through with storage_options
.
For example, the file datastore takes a :path
option to specify where to store the content (which will also become the uid for that content).
class Person
dragonfly_accessor :mugshot do
storage_options do |a|
# self is the model and a is the attachment
{ path: "some/path/#{self.category}-#{a.width}" }
end
end
end
or
class Person
dragonfly_accessor :mugshot do
storage_options :opts_for_storage
end
def opts_for_storage
{ path: "some/path/#{category}/#{rand(100)}" }
end
end
BEWARE!!!! you must make sure the path (which will become the uid for the content) is unique and changes each time the content is changed, otherwise you could have caching problems, as the generated urls will be the same for the same uid.
BEWARE No. 2!!!! using id
in the storage_path
won’t generally work on create, because Dragonfly stores the content in a call to before_save
,
at which point the id
won’t yet exist.
"Magic" Attributes
An accessor like image
only relies on the accessor image_uid
to work.
However, in some cases you may want to record some other properties, whether it be for using in queries, or
for caching an attribute for performance reasons, etc.
For the properties name
, size
and any of the registered analysis methods (e.g. width
),
this is done automatically for you, if the corresponding accessor exists.
For example - with ActiveRecord, given the migration:
add_column :photos, :image_width, :integer
This will automatically be set when assigned:
@photo.image = File.new('path/to/my_image.png')
@photo.image_width # => 280
They can be used to avoid retrieving data from the datastore for analysis
@photo = Photo.first
@photo.image.width # => 280 - no need to retrieve data - takes it from `image_width`
@photo.image.size # => 134507 - but this needs to retrieve data from the data store, then analyse
Furthermore, any magic attributes you add a field for will be added to the meta data for that attachment, so can be used to set custom response headers when Dragonfly serves the content.
Derived from theme by orderedlist