Nov 6

What I’m showing here is how to make a multiple file attachment for one or more models (it’s DRY) that integrate with active scaffold. I build on the great contribution http://www.practicalecommerce.com/blogs/post/432-Multiple-Attachments-in-Rails by Brian Getting.

What I did is this:

- integrated the multiple attachment handling with active scaffold
- DRYed up the code
- removed the post limit

And it looks something like this:

Which is quite a change in the end. Be sure to have attachment_fu and active_scaffold in your plugins-forlder. Let’s go through the parts:

First some obvious prerequisites: generate the models and get the db going. Remember to configure your config/database.yml or your rake will chocke. ;-)

script/generate model post title:string description:text
script/generate model attachment size:integer height:integer width:integer parent_id:integer attachable_id:integer position:integer content_type:string filename:string thumbnail:string attachable_type:string
rake db:create
rake db:migrate

Since I’m using local filestorage here, make sure there’s an uploads directory in public.

Now for the models themselves, set up the relations as follows

app/models/post.rb

has_many  :attachments, :as => :attachable, :dependent => :destroy

app/models/attachment.rb

belongs_to :attachable, :polymorphic => true
 
has_attachment :storage => :file_system,
                 :path_prefix => 'public/uploads',
                 :max_size => 1.megabyte

app/controllers/application.rb

protected
 
def process_file_uploads(owner)
  params[:attachment].each do |key, value|
  # only process files that got a size (and thus are not nil or empty etc)
  next unless value.size > 0
    attachment = Attachment.create({:uploaded_data => params[:attachment][key]})
    owner.attachments << attachment
  end
  owner.save
end

I moved process_file_uploads into the ApplicationController, so that it’s accessible from every model and controller that needs access to it. The checks for the valid params have been a bit bogus, and actually it did not work for the update in some cases. So I just test for size. If there’s a size, there’s a file, easy as pie. In the end I save the owner, something rails can’t live without, it seems.

app/controllers/posts_controller.rb I created this by hand (no need for a rails scaffold here)

active_scaffold :post do |config|
  config.columns = [:title, :description, :attachments]
  config.create.multipart = true
  config.update.multipart = true
end
 
def before_create_save(record)
  process_file_uploads(record)
end
 
def before_update_save(record)
  process_file_uploads(record)
end

Well this code got pretty concise. I basically removed 30 lines of code and wrote it so active scaffold can take care of almost everything. I just used before_create_save and before_update_save to hook into the saving process and take care of the attachments. Like before, I had to tell the forms that they are multipart/formdata.

app/controllers/attachments_controller.rb

active_scaffold :attachments do |config|
 config.action_links.add 'send_myfile', :label => 'Download', :type => :record, :popup => true
end
 
def send_myfile
  attachment = Attachment.find(params[:id])
  send_file(attachment.full_filename, :type => attachment.content_type, :filename => attachment.filename)
end
 
def destroy
  @attachment = Attachment.find(params[:id])
  @attachment.destroy
  asset = @attachment.attachable
end

Here the destroy is important. It’s the old way to do it, but works pretty swell.

app/views/attachments/_attachment.html.erb

	<li id="attachment_&lt;%= attachment.id %&gt;">&lt;%= attachment.filename %&gt;
&lt;%= link_to_remote "Remove", :url  =&gt; attachment_path(:id =&gt; attachment), :method =&gt; :delete, :html =&gt; { :title  =&gt; "Remove this attachment" } %&gt;</li>

So here I have a file with again DRYed up content. Actually it’s mostly Brian’s code, but here it doesn’t need to be copied to each and every view that uses the multiple attachment functionality.

app/views/attachments/destroy.rjs

page.hide "attachment_#{@attachment.id.to_s}"
page.remove "attachment_#{@attachment.id.to_s}"
page &lt;&lt; "if ($('attachment_data').disabled) { $('attachment_data').disabled = false };"

This also got a bit smaller, mainly because I don’t check for a max. amount of allowed uploads.

app/views/active_scaffold_overrides/_attachments_form_column.rhtml

&lt;% fields_for Attachment.new do |attachment| -%&gt;
 
	<label for="attachment_data">Attach Files:</label>
	&lt;%= attachment.file_field :data %&gt;
 
&lt;% end -%&gt;
<ul id="pending_files">
	&lt;% if @record.attachments.size &gt; 0 -%&gt;
	&lt;%= render :partial =&gt; "attachments/attachment", :collection =&gt; @record.attachments %&gt;
	&lt;% end -%&gt;</ul>
<script type="text/javascript"><!--mce:0--></script>

I created the active_scaffold_overrides folder, just like it’s recommended in the neat documentation of active scaffold. Here I provide the code (again, it’s mostly Brian’s, I just DRYd it up and moved it to a DRYer place) that active scaffold needs to display the upload functionality. I also just copied the MultSelector code, so there’s this “max” parameter left, I just feed it a high enough number, like 100.

Well that’s basically all, remember to put in the config/routes.rb:

map.resources :posts, :active_scaffold =&gt; true do |post|
  post.resources :attachments, :active_scaffold =&gt; true
end
map.resources :attachments

I use this funky new nested routing stuff here. :-)

remember to load the ajax and active_scaffold javascripts in app/views/layouts/application.html.erb (delete index.html from public)

 &lt;%= javascript_include_tag :defaults %&gt;
 &lt;%= active_scaffold_includes %&gt;

and into public/javascripts/application.js append the following:

// -------------------------
// Multiple File Upload
// -------------------------
function MultiSelector(list_target, max) {
  this.list_target = list_target;this.count = 0;this.id = 0;if( max ){this.max = max;} else {this.max = -1;};this.addElement = function( element ){if( element.tagName == 'INPUT' && element.type == 'file' ){element.name = 'attachment[file_' + (this.id++) + ']';element.multi_selector = this;element.onchange = function(){var new_element = document.createElement( 'input' );new_element.type = 'file';this.parentNode.insertBefore( new_element, this );this.multi_selector.addElement( new_element );this.multi_selector.addListRow( this );this.style.position = 'absolute';this.style.left = '-1000px';};if( this.max != -1 && this.count >= this.max ){element.disabled = true;};this.count++;this.current_element = element;} else {alert( 'Error: not a file input element' );};};this.addListRow = function( element ){var new_row = document.createElement('li');var new_row_button = document.createElement( 'a' );new_row_button.title = 'Remove This Image';new_row_button.href = '#';new_row_button.innerHTML = 'Remove';new_row.element = element;new_row_button.onclick= function(){this.parentNode.element.parentNode.removeChild( this.parentNode.element );this.parentNode.parentNode.removeChild( this.parentNode );this.parentNode.element.multi_selector.count--;this.parentNode.element.multi_selector.current_element.disabled = false;return false;};new_row.innerHTML = element.value.split('/')[element.value.split('/').length - 1];new_row.appendChild( new_row_button );this.list_target.appendChild( new_row );};
}

I have a WORKING example for download :-)

Okt 30
Windows Welt
admin, 30. Oktober 2008

Bin grad bei meiner Mutter. Sie hat mich gefragt, ob ich ihr mit ihrem Notebook helfen könnte. Kein Problem. Jetzt sitz ich hier und bin ganz erstaunt, was ich mir früher angetan habe: hatte schon ganz vergessen, mit was für Plagen man in Windows konfrontiert wird.

Erstmal die ganzen Programme, die sich ständig in den Vordergrund drängeln und einem dauernd den Focus wegnehmen.

Dann die übervolle Taskleiste. 30 kleine Icons von Programmen, die man nie selbst aufruft.

Dann der Norton Internet Security. Der ist nämlich ausgelaufen - eine neue Lizenz kostet 60 Euro im Jahr. 60 Euro jährlich zusätzlich dafür, dass man ein Betriebssystem gekauft hat. Ich mein, ich reg´ mich ja auch über die 70 Euro Parkpickerl fürs Jahr auf, aber in Sachen Betriebssystem und Virenschutz hat man wenigstens die Wahl. Also runter damit. Aber zuerst Avira installieren. Oh mann, wasn Aufwand …

Nachtrag: Norton runterlöschen, also vom Klick auf “Deinstallieren” bis zum Windows-Neustart hat fast 20 Minuten gebraucht. Krasse Sache sag ich mal.

Mit was für einer Tastenkombination schliesst man eigentlich Fenster, ohne das Programm zu beenden? Hm ….

Okt 29
Herman Munster
admin, 29. Oktober 2008

Also ein Atherom ist eine stecknadelkopf- bis hühnereigroße, gutartige Zyste im Unterhautzellgewebe. Im Volksmund werden diese Zysten auch als Grützbeutel, Balggeschwulst oder Grießknoten bezeichnet.

Ja und so ein Teil hauste bei mir die letzten Jahre auf meinem Hinterkopf. Da ich mich aber letzthin einige Feldenkraislektionen zum Thema “Brücke” (Fußsohlen und z.B. Kopf am Boden, Hintern in die Höhe) studiert habe, störte mich dieses Atherom und ich liess mich beraten.

Doc: “Kein Problem, das können wir ambulant operieren, ein kleiner Schnitt und das ist erledigt. Nur meistens blutet die Kopfschwarte sehr start, das ist das einzige Problem während der OP.”

Gut gut, ich dachte mir, hm, ok,  ein kleiner Schnitt, danach mach ich eine naturheilkundliche Narbenentstörung, und die Sache ist erledigt. Schon 5 Monate später traute ich mich einen Termin zu vereinbaren und dann das hier:

Ich kanns mir auch nicht ganz erklären. Zum einen war mir ur-schlecht während der Operation, weil der Doc hat nämlich nicht nur einen recht ordentlichen Schnitt gemacht, sondern auch gleich ein ganzes Stück Kopfhaut/schwarte mitentfernt. Saubere Arbeit. Die Schmerzen sind erträglich, am problematischten ist eigentlich der psychologische Faktor. Also so Fragen wie mir das passieren konnte, und wie das jetzt ist mit so einer Riesennarbe am Kopf, und ob das auch gut verheilt und wieso man eigentlich einfach so so ein Riesenstück rausschneiden kann usw

Nicht schlecht in Sachen Aufmerksamkeit am Hinterkopf though.

Okt 27
Es ist ein ziemlicher fuck
admin, 27. Oktober 2008

Ja ja das kennt man ja schon von mir. Aber ist es denn verboten sich über seine Umwelt aufzuregen? Da gibt’s dann die Christen und die Eso’s, die dürfen das natürlich nicht, weil die haben ja den internen Wettbewerb wer besser ist im Christ-sein bzw. wer näher an der Erleuchtung ist. Aber wir, ok, ich, der normale Mensch, darf mich schon aufregen. Ich brauch ja keine Angst mehr haben.

Auch wenn’s zu gut ist. Schau mal. Ich hab hier echt alles, nur zuwenig Frauen fürs Auge. Seltsam seltsam sehr seltsam. Oder vielleicht sogar das. Oder ich komm einfach nicht zurecht mit dem Landleben. Ich mein wenn ich zur Haustür rausgehe, dann will ich ein paar fröhliche Gesichter sehen und ein paar süsse Hasen. Sonst komm ich mir vor wie im Leichenschauhaus.

Nachtrag: Hab grad bei einer Firma in Shanghai angerufen. Ich mein, das macht richtig Spass zu telefonieren. Wenn ich hier in Österreich mit der Rezeption telefoniere, dann bin ich schon froh wenn ich keine kalte, steife Briese spüre. Aber mit Chinesen vor allem aber Chinesinen zu sprechen/telefonieren, ich mein das macht mir echt Spass, die sind so nett und unbeschwert, das ist echt super.

Und jetzt, da ich nach Shanghai (ahhhh 我爱上海) will, bin ich endlich erwachsen genug um unsere Gesellschaft und die Menschen besser zu verstehen, auch wie man ein Geschäft macht, wie man hier also zurechtkommt, und wie komm ich jetzt hier weg??

business

Auf der anderen Seite, auch diese Menschen müssen atmen, und aufstehen, und sich hinsetzen, und stehen, und liegen, und eine Million andere Bewegungen machen. Und wie machen sie das? Abgesehen vom Geld und der guten Umgebung, die man durch ein Unternehmen bekommt finde ich das mit Abstand am interessantesten.

Nachtrag: Ich hab hier echt vile gelernt, und es war echt gut hier zu sein, auch wenn’s voll anstrengend war oftmals. Aber ich glaub, das ist echte Karmaarbeit. Ich mein ich denk mir, falls ich draufgehen sollte, ich kann mir zumindest sicher sein, dass ich gut rauskommen werde, am anderen Ende, und im Falle einer Reinkarnation, obwohl ich da nicht so 100% von überzeugt bin, alles cool weitergehen wird.

Nachtrag: Ein Blog mit Nachträgen, wie crazy ist das eigentlich??

Okt 9
Zurück aus Shanghai
admin, 9. Oktober 2008

Ok, erster Schock nach Rückkehr aus Shanghai überwunden. Heute ist der dritte Tag in Österreich. Nachdem ich seit drei Tagen, bis auf die Gemüsesuppe von Dad, nichts ordentliches zu Essen bekommen habe, hab ich mir grad fette Maki-Röllchen mit ordentlich viel Shrimps und Bohnenpaste gemacht. Fühlt sich schon viel besser an :-)

Ansonsten eigentlich alles laivand, meine Arbeitskollegen sind nett wie nie zuvor, meine Freunde sind auch noch da und rufen mich an und ich bin ganz gerührt dass ich geliebt werde, es gibt haufenweise interessante Projekte, d.h. es rufen Leute an und fragen ob ich dies oder das machen könnte. Was ich aber glaub ich meistens ablehnen muss, einfach zu wenig Zeit und ausserdem je mehr ich verdienen würde, desto mehr Steuer würde ich zahlen.

Und da kommt mir natürlich gleich das grosse Kotzen. Warum zahlen wir eigentlich alle Steuern, wenn die dann eh nur verschwendet werden, in Banken reingestopft werden, an Leute verschenkt werden, die das Geld dann im grossen Stil und mit viel Freude ausgeben? Das ist in Österreich sicher nicht anders, und von den vielen Milliarden, die jetzt in die Banken gestopft werden, werden Parties gefeiert, wie sie die Welt noch nicht gesehen hat. Fette Zeiten für die Elite, gleiche Sche**** wie immer für das Volk mit den heruntergezogenen Mundwinkeln und den festgehaltenen Brustkörben.

Und überhaupt, warum muss ich solche Nachrichten lesen, bzw solche Gedanken haben? Warum muss ich mir ringsherum trübe Gesichter anschauen? Die Leute hier haben ALLES, nur keine Lebensfreude. Oder die auch nur, wenn sie triumphierend sicher sind, dass sie die grössere Nummer als das Gegenüber sind und doch mehr am Konto haben als der Nachbar oder das schickere Kleid und die teuere Hose.

Nene, ich geh zurück nach Shanghai/China, dort ist alles besser :-)

« Previous Entries Next Entries »