Rails / Controller-level

From WhyNotWiki
Jump to: navigation, search

Contents

How do I access my environment/ActiveRecord/my models from a (nearly) stand-alone command-line script?

For example, say you want to do some non-Web-based report that you run from the command line. Or a database maintenance script that you want to run regularly (so it wouldn't make sense to put it in a migration, since those are only expected to be run once). Or anything else you'd call from cron.

#!/bin/env ruby
$LOAD_PATH << "../"

require "config/environment"
require "app/models/book.rb"

result = Book.connection.select_all("select * from books")

Or: require File.expand_path(File.dirname(__FILE__) + "/../config/environment")

One could probably do something like this too (haven't tried it yet), which would automatically load the environment before executing the code that you pass to runner:

./script/runner "require 'send_mailing'"

$ ./script/runner -e production 'puts ENV["RAILS_ENV"]'
production

How do I access a controller from a stand-alone (runner) script?

Notice that this doesn't work:

$ ./script/runner "puts MainController.new"
/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/commands/runner.rb:27: /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:123:in `const_missing': uninitialized constant ApplicationController (NameError)
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:131:in `const_missing'
        from script/../config/../app/controllers/main_controller.rb:3
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:140:in `load'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:140:in `load'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:56:in `require_or_load'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:30:in `depend_on'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:85:in `require_dependency'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:98:in `const_missing'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:131:in `const_missing'
        from (eval):1
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `eval'
        from /usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/commands/runner.rb:27
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require__'
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:147:in `require'
        from ./script/runner:3

But this does work:

]$ ./script/console
Loading development environment.
Welcome!
irb -> MainController.new
    => #<MainController:0xb7aefaa8>

So after examining the source of script/console and commands/console, I think I figured out what the difference was that allowed console to have access to it but not runner:

 12 libs =  " -r irb/completion"
 13 libs << " -r #{RAILS_ROOT}/config/environment"
 14 libs << " -r console_app"
 15 libs << " -r console_sandbox" if options[:sandbox]
 16 libs << " -r console_with_helpers"

This seems to work:

$ ./script/runner "require 'console_with_helpers'; puts MainController.new"
#<MainController:0xb78ee6dc>

Ugly, but it works.

How do I access url_for from a stand-alone (runner) script / from a mailer?

I don't know. I've tried to get it to work and failed.

http://lists.rubyonrails.org/pipermail/rails/2006-March/028892.html [Rails] Re: runner: url_for with mailers impossible

http://wrath.rubyonrails.org/pipermail/rails-core/2006-June/001652.html [Rails-core] The old deal with url_for in Mailer

This

puts MainController.new.url_for(:action => 'a')

causes this:

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:490:in `url_for': You have a nil object when you didn't expect it! (NoMethodError)
The error occured while evaluating nil.rewrite  from ./script/whatever.rb:5

This:

http://wiki.rubyonrails.org/rails/pages/HowtoUseUrlHelpersWithActionMailer/ HowtoUseUrlHelpersWithActionMailer in Ruby on Rails

seems to suggest that maybe this would work:

include ActionView::Helpers::UrlHelper
puts url_for(:action => 'a')

but again we get:

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:490:in `url_for': You have a nil object when you didn't expect it! (NoMethodError)
The error occured while evaluating nil.rewrite  from /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/url_helper.rb:27:in `send'
        from /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/url_helper.rb:27:in `url_for'
        from ./script/whatever.rb:7


How do I do something like puts from a controller?

Do I have to set it to a member variable, and then add <%= my_debug_output %> to the template??

Do I have to use a logger instead?

Can't I just say puts my_debug_output? Or render :text => my_debug_output? And have it added to the HTML that will be rendered?

That's one thing I miss about PHP. In PHP, you can put a echo/print statements everywhere to help you debug your code.

My solution: Usually I just use

raise whatever_object.inspect

Sometimes I'll use a global, something like this:

$output = ''
$output += 'some debug output'

And outputting <%= $output %> in my view. But really I should just be using the logger at that point.

What's the difference between redirect_to and render?

both render the view (.rhtml file).

Only redirect_to causes the action in the controller to be called. So if you are doing render and any set up needs to be done, you need to do it in the action method that is calling render or you need to explicitly call the action before you render the template for that action (confusing, I know!).

render keeps the local variables (such as error messages and the current, possibly invalid state of the objects being edited); redirect loses them

both of them keep and show the contents of flash

[need real example]

ActionMailer: Can use the helpers that are used by my normal (controller) views)?

Yes! Just put something like the following in your ActionMailer::Base subclass:

helper :application, :other

filters (before_filter, etc.)

meantime_filter

It's sort of like an around_filter, only cooler! (available as a plugin, I think)

after_filter

after_filter happens after the view, not after the controller (http://woss.name/2006/05/07/notes-from-a-rails-course/0)

Flash

Hey, how come my flash message is sticking around too long?

Normally the flash message will be available to the next action, whatever that is...

Maybe you're setting flash and then rendering and wanting your message to only appear for the current action.

Solution: Try setting flash.now[:error] = 'whatever' instead of flash[:error].

Reference: http://api.rubyonrails.org/classes/ActionController/Flash/FlashHash.html#M000142

Make it work from Ajax; add a fade effect; make it log errors

Rails Notification System (http://blog.andreasaderhold.com/2006/07/rails-notifications) (July 13, 2006). Retrieved on 2007-05-11 11:18.

Here’s a nice trick for building a cool notification system that uses the Rails flash array and can be used with Ajax and with normal actions. I’ve used this on two projects and it works just fine (not much that can go wrong anyways). First, put the following in your layout template:

<% flash.each do |key,value| -%>
  <h4 id="flash" class='alert <%= key %>'>
    <%= value %>
  </h4>
<% end -%>

[Incorrect! This will cause there to be more than one element with id="flash", which is invalid. I would make "flash" a class rather than an id. <h4 class='flash <%= key %>'>]

This gives you a dynamic flash thing, so you can use different type of notifications/alerts (i.e. falsh[:error], flash[:critical], etc.)

The styles for the default types error and notice could look something like this [...]:

h4.alert  {
   font-size: 0.8em;
   font-weight:bold;
   margin:0;
   padding:5px;
}
h4.error  {
   color:#fff; 
   background:#c00;
}
h4.notice {
  color:#060;
  background:#e2f9e3;
}

Ok, so far so good. This is quite standard. Because typing flash[:blah] = "Something" is boring and after all we want to send a message to the notification system and don’t want to care about arrays, it’s just logical we want to do something like notify :error, "Something went wrong". So let’s define a helper method in our application.rb:

def notify(type, message)
  flash[type] = message
  logger.error("ERROR: #{message}") if type == :error
end

Note, this also creates a log entry in case the type is :error. Now that’s it. You have a nice notification system, the CSS class names are named by the notification types and you have easy handler method to fire a note. Oh let it DRY baby. Bonus: Fading messages

It’s optional, but I like it. I don’t want the messages to stand in the layout all the time. It was a notification and that’s it. You would not want to have the credits of a movie scrolling on top of the screen all the time, would you? I don’t, so let’s just fade away the message by adding the following snippet to our application.js file:

/* fade flashes automatically */
Event.observe(window, 'load', function() { 
  $A(document.getElementsByClassName('alert')).each(function(o) {
    o.opacity = 100.0
    Effect.Fade(o, {duration: 8.0})
  });
});

This requires script.aculo.us, so don’t forget to include it. It softly fades out all flash messages over 8 seconds. You can extend/reduce the duration of course. Wait, what about those Ajax actions?

Helpers to rescue. Since we have the RJS stuff and helper methods are available to the page object, this is a no-brainer. Let’s define the notify function in application_helper.rb:

def notify(type, message)
  type = type.to_s  # symbol to string
  page.replace 'flash', "<h4 id='flash' class='alert #{type}'>#{message}</h4>" 
  page.visual_effect :fade, 'flash', :duration => 8.0
end

Ok, this makes a notify method avail so we can send the page object a message:

render :update do |page|
  # do your stuff here
  page.notify  :notice, "Successfully closed all windows" 
end

The last step is to extend your main flash rendering code to something like this:

<% if flash.empty? %>
  <h4 id="flash" class="alert" style="display:none"></h4>
<% else %>  
  <% flash.each do |key,value| -%>
    <h4 id="flash" class='alert <%= key %>'>
      <%= value %>
    </h4>
  <% end -%>
<% end %>

This ensures we always have a element with the ID “flash” rendered, so the notify call can update it.

How to make sure you don't overwrite an existing flash message

It bothers me how the prevailing convention is to set the flash value rather than appending to it: flash[:whatever] = 'My message'.

What if you have, say, some before_filter in your ApplicationController that may want to flash a message in certain cases, and your specific controller action also wants to flash a message? Whichever one sets the flash hash first "wins", silently overwriting the other message and preventing it from ever getting seen... bad!!

One possible solution would be to always append to the string:

flash[:notice] << 'My message'

But then your messages will run together. So you could try to remember (I say "try", because you know you'll forget some of the time) to put each message in an HTML paragraph:

flash[:notice] << '<p>My message</p>'

Instead of having each entry in the hash be a string, each entry should be an array of strings. Or something like that. So you'd have a list (array) of 0 or more error messages, 0 or more notices, etc.

flash[:notice] << 'My message'

You would have to change how you rendered it, though. To something like this:

<% flash.each do |flash_type, messages| -%>
  <h4 class='flash <%= flash_type %>'>
    <% messages.each do |message| -%>
      <p>
        <%= message %>
      </p>
  <% end -%>
  </h4>
<% end -%>

Should there be a standard list of flash "types" (error, notice, etc.)?

No, I think the application developers should be able to use whichever ones make sense to the application. I mean, they should be consistent themselves, but I'm not sure we need to adopt a Rails-community-wide standard here...

Here's one person's proposal:

standardizing rails flash messages [ruby [rails] [flash]] (http://snippets.dzone.com/posts/show/1348). Retrieved on 2007-05-11 11:18.

  • :notice for positive feedback (action successful, etc)
  • :message for neutral feedback (reminders, etc)
  • :warning for negative feedback (action unsuccessful, error encountered, etc)

I don't agree with it though. I would suggest using one of these conventions:

  • Convention 1 (simple):
    • :notice for positive or neutral feedback (action successful, etc)
    • :error for negative feedback (action unsuccessful, error encountered, etc.)
  • Convention 1 (more advanted):
    • :positive for positive feedback (action successful, etc)
    • :neutral for neutral feedback (reminders, etc)
    • :warning for somewhat negative feedback (warnings)
    • :error for very negative feedback (more fatal-type erorrs)

Information about the request: remote IP address

David Reynolds said:

Not sure if anyone else knows this, but request.env['REMOTE_ADDR'] doesn't return the correct visitor IP address when the user is behind a proxy or if the web server forwards requests to an internal server (one server on port 80 that forwards to a different port). In theses cases, request.remote_ip works way better. In Rails, you can apparently find the originating host even if they are proxy chaining.
Here's some more info: http://railsmanual.com/class/ActionController::AbstractRequest/remote_ip


File uploads

http://manuals.rubyonrails.com/read/chapter/57 Rails Cookbook: file upload forms

http://www.kanthak.net/opensource/file_column/ Sebastian Kanthak - FileColumn - easy handling of file uploads in Rails








View-level / Controller-level / params / Model-level interaction (form processing)

Validation and Securing Input

http://www.railsdiary.com/diary/validation_securing_form_input The Rails Diary | Validation and Securing Input

Things to consider when building form-processing code

  • Do you want the models to save even if there are validation errors?
    • If no, then be sure to check model.valid? to find out if there are errors and not check model.save
  • Do you have any fields that are required for the form, but not necessarily be the model and everywhere the model is used?
    • If yes, consider using a FormValidator object
  • Which model objects are being edited in your forms? You need to handle each of them.
  • Associations...


Updating associations from form input

      @company.update_services(params[:services])

  def update_services (services_hash)
    if services_hash
      services.clear
      services_hash.each { |service_id, value|
        services << Service.find(service_id.to_i)
      }
    end

You can just set the collection directly using the 'things_id =' or (?) 'things =' accessor/settor methods!


http://wiki.rubyonrails.com/rails/pages/HowtoUseFormOptionHelpers/:

Assigning to has_many or has_and_belongs_to_many collections If you want to have your multiple choice select assign to a has_many or has_and_belongs_to_many (habtm) collection on your object, you should specify the collection using the <collection name>_ids name. For example, if we have an AddressGroup object with a collection of Address objects called addresses, we would not use this: <select name="addressgroup[addresses][]" multiple="multiple"> but rather this: <select name="addressgroup[address_ids][]" multiple="multiple"> With either select statement the returned value is an array of Address object ids, not the actual array objects, which will fail if we tried to assign to addressgroup.addresses. If you try to do that you will get an error message like “Expected Address and got String”. Instead, we use the implicit address_ids= property (automatically added to an object when it has a has_many or habtm collection), which assigns to the collection based on a collection of IDs.


Forms for associations: my_model = MyModel.new(params[:my_model]) complains that the type passed to the association setter is of type String

[Caveat (category)][Setting has_many association (category)]

For example, you have:

Profile Model:
  habtm :hobbies
View:
  <%= select("profile", "hobbies", @hobbies.options_for_select, { :include_blank => true }) %>
Controller:
  @profile = Profile.new(params[:profile])

When you submit the form, it says "Hobby expected, got String".

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:134:in `raise_on_type_mismatch'
/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_collection.rb:118:in `replace'
/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_collection.rb:118:in `replace'
/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:895:in `hobbies='

Solution:

In this case, I made it extra difficult, because I'm using a single-select field (typically used for has_many associations, not habtm) to a habtm (usually a multi-select would be used).

If I were using a multi-select, this would be all I need to change:

View:
  <%= multi_select("profile", "hobby_ids", @hobbies.options_for_select, { :include_blank => true }) %>

In my case, however, I need to convert my single string value submitted in params[:profile][:hobby_ids] (for example, "3") into an array of strings (["3"]). So I did this (which could easily be thrown into the model logic):

    for key, value in params[:profile]
      if key.match /_ids/ and !value.is_a?(Array)
        params[:profile][key] = [value]
      end
    end

How does an object get converted to a field get converted to a param get converted to an object?

# "Model" object
class Address
  def line_1
    "123 Fake St."
  end
  def line_2
    "Apt. 42"
  end
end
@address = Address.new

# Helpers
text_field("address", "line_1")
text_field("address", "line_2")

# Produce essentially this HTML output:
<input type="text" name="address[line_1]"  />
<input type="text" name="address[line_2]"  />

# Produces these params:
"address"=>{"line_1"=>"123 Fake St.", "line_2"=>"Apt. 42"}}

params[:address][:line_1] = "123 Fake St."
params[:address][:line_2] = "Apt. 42"
<input type="text" name="address[][line_1]"  />
<input type="text" name="address[][line_2]"  />
# Not very useful
"address"=>{""=>{"line_1"=>"234", "line_2"=>"234"}

<input type="text" name="address[lines][]"  />
<input type="text" name="address[lines][]"  />
"address"=>{"lines"=>["123 Fake St.", "Apt. 42"]
params[:address][:lines[0] = "123 Fake St."
params[:address][:lines[1] = "Apt. 42"

How do I handle/prevent ActiveRecord::MultiparameterAssignmentErrors resulting from bad input from date_select?

http://i.nfectio.us/articles/2006/02/22/handling-invalid-dates-with-activerecord-date-helpers :

Rescue the exception in your controller. Iterate over the error details, removing from params all of the values that caused the error. Then send your modified params to the ActiveRecord model again.

Plugin:

http://wiki.rubyonrails.org/rails/pages/Validates+Multiparameter+Assignments :

Add a validates_multiparameter_assignments call in your model. Then it will add the errors to the model's errors collection rather than throwing an exception. Genius!
Ads
Personal tools