2

1.) Is it possible to create a controller action that does not directly interact with a model? (i.e. upload a file to be parsed, then added to a DB model)

2.) What is the controller's order of operation? I don't understand how the controller action both instantiates the view, and reacts to params variables inputted by the user.

Could somebody please explain this, thank you.

PART II - Routing a Model-less form

So for my current upload form, I have 2 actions, an upload action (takes a file from a user) that I would like routed to the parse_upload action (manipulates the file uploaded in the upload.html.erb view):

routes.rb:

::Application.routes.draw do
    devise_for :users
    resources :revenue_models do
        get 'upload', :on => :collection
        put 'parse_upload',:on => :collection
    end
    root :to => "home#index"
 end

actions:

# UPLOAD multiple files from an Exel Doc; integrate them accordingly
def upload
    @uploaded_doc = { :workbook => RubyXL::Parser.new }     
end
# Parse the uploaded file
def parse_upload
@worksheet = RubyXL::Parser.parse(params[:uploaded_doc]       
                  [:workbook]).worksheets[0].extract_data
end

upload.html.erb (I want this upload form to push its params to the parse_upload action)

<%= form_tag(:url => {:controller => "revenue_models", :action => "parse_upload"}, :html => {:method => "put", :multipart => true}) do %>
    <%= file_field(:uploaded_doc, :workbook) %>
<%= submit_tag("Upload") %>     
<% end %> 

Currently I am getting a routing error when I submit the file: No route matches [POST] "/revenue_models/upload"

I am assuming everything is working fine up to the point of routing from upload form to the [parse_upload] action. I tried following your MORE answer below, but due to the fact that in my case, I am not using a form centered around an existing model, I am a bit lost. Any clue what the issue is? Thanks in advance.

Utopia025
  • 1,181
  • 3
  • 11
  • 21

2 Answers2

3

1) Yes, certainly, controllers do not need to work with any models at all or they can work with thousands of them. Don't get confused with the scafolding. The fact that a controller is usually sitting on top of a model is because it's main responsibility is to interact with that model but that is not the rule.

2) After Rack is finished with the HTTP stack, it is dispatched to your routing (defined in config/routes.rb) the router then dispatches it to the controller/method you specified in that file. If you want to see all routes in your app currently, type: "rake routes".

The controller receives the params from rack. The http request params are neatly packed into a hash and sent your way that you can conveniently access with the params() method. The controller doesn't to anything to the params besides reading them.

As for the views, they are not dispatched by the controller. When the controller invokes render(), render_to_string(), render_with(), etc... the appropriate view template along with the layout (both of which you can specify or have defaulted to by the controller) are loaded and processed (converted to a string that is then sent out via the HTTP response). The only magic here, is that instance variables in the controller are made available to the views (which they themselves can use as instance variables)

I hope this cleared some things up for you.. :)

ilan berci
  • 3,883
  • 1
  • 16
  • 21
2

1) Yes absolutely, a controller 'action' does NOT have to deal with a model, i.e.

ThingController < ApplicationController
  def status
    @status = system("#{Rails.root}/lib/mystatusscript");
  end
end  

Actions are called when a URL hits the server, and the routing table is consulted, and a controller and action are determined. So if you put this in your routes.rb:

match "/whatever" => "things#status"

and type

http://localhost:3000/whatever

The status action in the ThingsController (app/controllers/things_controller.rb) will get called.

What happens next, by default, because you've not told it to do anything else, rails will look for app/views/things/status.html.erb, and render it, i.e.:

The stats is <%= @status %>

But you can prevent that, and make rails do something else, possible examples:

ThingController < ApplicationController
  def status
    @status = system("#{Rails.root}/lib/mystatusscript");
    render :js=>"$('#status_retreived').show();"
  end
end  

ThingController < ApplicationController
  def status
    system("#{Rails.root}/lib/do_something_server_side");
    render :nothing=>true
  end
end  

ThingController < ApplicationController
  def status
    @status = system("#{Rails.root}/lib/mystatusscript");
    render action=>:edit
  end
end  

ADDITIONAL

Let's make a form and see what happens

Say you have this in app/views/things/edit.html.erb:

<%= form_for @thing do |f| %>
  <%= f.input :name %>
  <%= f.submit %>
<% end %>

Say you have these routes in routes.rb:

get '/things/:id/edit' => 'things#edit'
put '/things/:id/update' => 'things#update'

And your controller has:

def update
  @thing = Thing.find(params[:id])
  @thing.attributes = params[:thing]
  @thing.save
end
def edit
  @thing = Thing.find(params[:id])
end

So here is the flow, you hit your app with '/things/100/edit'

The edit action is called , the instance variable @thing is set to the record who's id is 100. Then the edit.html.erb view is rendered, presenting you with an edit screen for the name field and a submit button.

When you click 'submit', you will PUT to '/things/100/update'

Because of the way the route was defined '/things/:id/update', when you get inside the update action, params[:id] will contain 100, AND params[:thing] will contain what was posted by the form, i.e. your params could contain:

params[:thing][:name]
params[:thing][:city]
....
params[:thing][:zip]

The ID is abstracted out into params[:id], and the form data is in params[:thing]

MORE

rails does a lot of automatic url generation for you, it's very smart about it, for example, in edit.html.erb, you have this:

<%= form_for @thing do |f| %>
  <%= f.input :name %>
  <%= f.submit %>
<% end %>   

If you look at the HTML generated you'll see something like:

<form id="edit_thing_100" method="put" action="/things/100/update"> 

How did rails KNOW to do an update instead of a create? Because it checked @thing and noticed it had already been saved to the database prior, it's NOT a new record, so it must be an update.

So in your view you typically create various URI's that get sent to the server via links , submit buttons, etc. When they are looked up in routes.rb, the appropriate action in the appropriate controller is called.

FILE UPLOAD

Is easier than you may think, first you need to add the file upload field AND change the form slightly:

<%= form_for @thing do ,:html=>{:multipart=>true} |f| %>
  <%= f.input :name %>
  <%= f.file_field :upload %>
  <%= f.submit %>
<% end %>   

Now, when inside the update action you can do this:

def update
  filename = params[:thing][:upload].original_filename
  filetype = params[:thing][:upload].content_type
  filedata = params[:thing][:upload].read

  File.open("#{Rails.root}/filestorage/#{filename}","wb") { |f| f.write(filedata) } 

  @thing = Thing.find(params[:id])

  @thing.attributes = params[:thing]
  @thing.uploadstoredin = "#{Rails.root}/filestorage/#{filename}"
  @thing.save
end

Because you made the form multipart, and you declared an attribute :upload as a file_field, when the params are posted, the :upload param has three extra methods (original_filename, content_type and read), Rails MAGIC!

RadBrad
  • 7,234
  • 2
  • 24
  • 17
  • So what happens in an instance where a form is involved? If I have an upload form in my app/views/things/status.html.erb view <%= form_tag({:action => :upload}, :multipart => true) do %> <%= file_field_tag 'file' %> <%= submit_tag 'Upload' %> <% end %> and I click submit? Where does the params[:file] object go, and where would I go to parse its data? Sorry for the ugly output. – Utopia025 Feb 20 '13 at 22:59
  • Added some more explanation – RadBrad Feb 20 '13 at 23:27
  • How do the routes you specified know to interact with one another? How does the edit form know to submit the entries to the update action? (thanks so much for everything, so far you have been extremely helpful!!!) – Utopia025 Feb 21 '13 at 19:40
  • I've added a use case question to my original question (Part II), any further help would mean the world. Thanks so much (again) @RadBrad – Utopia025 Feb 22 '13 at 17:23
  • Sorry I may have been unclear, how do I route the upload form to the parse_upload action? Again (and I'm not 100% sure this is relevant) but the file upload is not part of any existing models or database values; I don't know if it is possible to use the Thing Model example, I also want to create a separate action, not use the existing update. @RadBrad – Utopia025 Feb 22 '13 at 18:56
  • Fixed by adding [map.connect '/revenue_models/upload', :controller => 'revenue_models', :action => 'upload'] to my routes.rb file – Utopia025 Feb 22 '13 at 22:08