That topic doesn't exist

12 Mar 2006, 14:36
Generic-user-small

Fora User (865 posts)

So you’ve been reading Rails Recipes and you’ve got the bug to share your own tips and tricks?

Write your own recipes for the world to benefit from.

What is a Rails Recipe?
It’s got to be short and to the point. It’s got to show someone, step by step, how to do something they might not know how to do. It should answer the question, “How do I….?” or “How would I…?”

It should follow this basic structure:
  • Problem
  • Extra Ingredients
  • Credits (anyone deserve credit for this idea?)
  • Solution
  • Discussion
  • Further Reading

Fame and fortune await. Let’s see what you’ve got!

Who knows? You may even end up (with permission) in the book!

10 Mar 2006, 10:37
Generic-user-small

Fora User (865 posts)

I’d like to see a recipe where you can send a complete HTML message including all graphic bells-n-whistles. My need didn’t go that far yet. The only thing I needed I blogged at the Caboo.se href=”http://blog.caboo.se/articles/2006/02/19/how-to-send-multipart-alternative-e-mail-with-inline-attachments”>http://blog.caboo.se/articles/2006/02/19/how-to… about this. Maybe you or someone else can expand that?

11 Mar 2006, 07:32
Generic-user-small

Fora User (865 posts)

Problem

Your the envy of the IT shop for getting such a cool web app up in record time using Rails. But some critics still roll their eyes and say “another trend”. They’re waiting to poke holes in such a new technology.

(Corallary:)
Even though it says clearly in database.yml …

Do not set this db to the same as development or production.

...you’ve gone and done it. More than once. And Murphy’s Law says that someone will do it in production one day as well. You need a backup and recovery strategy.

Credit

Geoffrey Grosenbach has a great plug-in called ar_fixtures href=”http://nubyonrails.com/articles/2005/12/27/dump-or-slurp-yaml-reference-data”>http://nubyonrails.com/articles/2005/12/27/dump… that inspired these scripts. Also much credit to Jim Weirich not just for creating rake, but also actively helping newcomers online. I’ve gotten many tips from his posts href=”http://www.codecomments.com/archive327-2005-3-440877.html”>http://www.codecomments.com/archive327-2005-3-4… .

Ingredients

Make sure that zip and unzip commands are in the PATH of your OS.

Solution

Rake already has two built in tasks to handle restoring the database, :db_schema_dump, and :db_schema_import. By adding code to export and import your data to and from YAML, and code to zip it up along with any files created at runtime, you’ve got yourself a Six Sigma Sabanes-Oxley buzzword-compliant y2k disaster recovery plan.

backup.rake href=”http://www.apptrain.com/backup.rake”>http://www.apptrain.com/backup.rake

The backup script needs some basic file utilities, and the packagetask.

require 'fileutils'
require 'rake/packagetask'

First we’ll get all the model names that need backing up. Omitting anything with an underscore eliminates _mailer or _observer files.

MODELS = FileList['app/models/*'].exclude(/.*_.*/)

Then we loop through all the models and dump them to YAML. (See Recipie #23)

task :backup_app_data => [:db_schema_dump] do
  MODELS.each do |m|
    model = File.basename(m,'.rb')
    dump_to_yaml(nil,model)
  end
end

Now let’s zip it all up. Amazingly enough the PackageTask can create a zip file instead of a gem just by setting the need_zip switch. The FileList can be customized to include any additional data that may be created by your app while running.

Rake::PackageTask.new("#{THISAPP}","0.0.1") do |z|
  # customize this list to include any files created by you app at runtime. 
  z.package_files = FileList["db/*"] #,"public/ad/**/*"]
  z.need_zip = true
end  

There. Now you can report to management that there’s a backup strategy in place. There will be much rejoicing. Unless of course disaster strikes. Then you’ll realize you need a recovery script as well.

restore.rake href=”http://www.apptrain.com/restore.rake”>http://www.apptrain.com/restore.rake

For recovery we’re rebuilding the app from scratch. We’re assuming that the correct version of your app can be retrieved from source control. Once the code is in place, we can unzip our data package.

Unzipping a .zip is a little trickier. You need the zipfilesystem library. It’s documented at “

12 Mar 2006, 14:36
Generic-user-small

Fora User (865 posts)

Using legacy databases

Problem

Your company is running some software that rely on an existing
MySQL database.

Would you like to use Rails at work, your only solution is to teach
Rails the convention used in this legacy database.

These conventions are basically:

  • table names are singular, prefixed by my_ and suffixed by _table
  • the primary key, although named id, is not auto-incremented but rely on sequences: a table foo will have a table my_foo_sequence_table associated.

Solution

Although you can use the set_table_name to indicate for each of your
Foo model to look at table my_foo_table, it’s not very … DRY
compliant.

We’re going to use the class methods table_name_prefix,
table_name_suffix and pluralize_table_names to automate things a
little bit.

So let’s create a file, named adapter.rb in the lib directory of
your rails application. This file will hold the following:


    ActiveRecord::Base.table_name_prefix = "my_" 
    ActiveRecord::Base.table_name_suffix = "_table" 
    ActiveRecord::Base.pluralize_table_names = false

With this all the computed table names will begin with my_ and ends
with _table with the middle name not pluralized. So far, so good.

Just adds a


  require 'lib/adapter.rb'

to boot/environment.rb file.

Now let’s fix this sequence problem. Rails by default assumes that
your primary key is auto-incremented, which is one of th eMySQL
property. However in some case this auto-increment is not used and
simulated through the use of sequence tables. A sequence table just
acts as a counter that allows to predict the next integer to use as a
primary key.

So we want our sequence names to be deduced from our table name. Let’s
rewrite the reset_sequence method of ActiveRecord::Base: this
function is used to set the name of the sequence table the first time
this name is needed.


   module ActiveRecord
     class Base
       class << self
         def reset_sequence_name
           "#{table_name}_sequence" 
         end
       end
     end
   end

We now have to instruct the MySQL adapter that we’re going to rely on
sequences: we need to request a prefetch of the primary key:


    module ActiveRecord
      module ConnectionAdapters
        class MysqlAdapter
          def prefetch_primary_key?(table_name = nil)
            true
          end
        end
      end
    end

The documentation for prefetch_primary_key? says that if its set to
true, then the next_sequence_value function will be called to get a
new value so it seems we need this one too:


   module ActiveRecord
     module ConnectionAdapters
       class MysqlAdapter

         def prefetch_primary_key?(table_name = nil)
           true
         end

         def next_sequence_value(sequence_name)
           sql  = "UPDATE #{ sequence_name} SET Id=LAST_INSERT_ID(Id+1);" 
           update(sql, "#{sequence_name} Update")
           select_value("SELECT Id from #{ sequence_name}",'Id')
         end

     end
   end

Were basically telling to mySQL to get a new value based on the last
one, using the LAST_INSERT_ID MySQL function..

And we’re done :)

21 Mar 2006, 16:58
Generic-user-small

Fora User (865 posts)

Problem

Your company is running some software that rely on an existing
MySQL database.

Would you like to use Rails at work, your only solution is to teach
Rails the convention used in this legacy database.

These conventions are basically:

  • table names are singular, prefixed by my_ and suffixed by _table
  • the primary key, although named id, is not auto-incremented but rely on sequences: a table foo will have a table my_foo_sequence_table associated.

Solution

Although you can use the set_table_name to indicate for each of your
Foo model to look at table my_foo_table, it’s not very … DRY
compliant.

We’re going to use the class methods table_name_prefix,
table_name_suffix and pluralize_table_names to automate things a
little bit.

So let’s create a file, named adapter.rb in the lib directory of
your rails application. This file will hold the following:


    ActiveRecord::Base.table_name_prefix = "my_" 
    ActiveRecord::Base.table_name_suffix = "_table" 
    ActiveRecord::Base.pluralize_table_names = false
   

With this all the computed table names will begin with my_ and ends
with _table with the middle name not pluralized. So far, so good.

Just adds a


   require 'lib/adapter.rb'
   

to boot/environment.rb file.

Now let’s fix this sequence problem. Rails by default assumes that
your primary key is auto-incremented, which is one of th eMySQL
property. However in some case this auto-increment is not used and
simulated through the use of sequence tables. A sequence table just
acts as a counter that allows to predict the next integer to use as a
primary key.

So we want our sequence names to be deduced from our table name. Let’s
rewrite the reset_sequence method of ActiveRecord::Base: this
function is used to set the name of the sequence table the first time
this name is needed.


   module ActiveRecord
     class Base
       class << self
         def reset_sequence_name
           "#{table_name}_sequence" 
         end
       end
     end
   end
   

We now have to instruct the MySQL adapter that we’re going to rely on
sequences: we need to request a prefetch of the primary key:


    module ActiveRecord
      module ConnectionAdapters
        class MysqlAdapter
          def prefetch_primary_key?(table_name = nil)
            true
          end
        end
      end
    end
   

The documentation for prefetch_primary_key? says that if its set to
true, then the next_sequence_value function will be called to get a
new value so it seems we need this one too:


   module ActiveRecord
     module ConnectionAdapters
       class MysqlAdapter

         def prefetch_primary_key?(table_name = nil)
           true
         end

         def next_sequence_value(sequence_name)
           sql  = "UPDATE #{ sequence_name} SET Id=LAST_INSERT_ID(Id+1);" 
           update(sql, "#{sequence_name} Update")
           select_value("SELECT Id from #{ sequence_name}",'Id')
         end

     end
   end
   

Were basically telling to mySQL to get a new value based on the last
one, using the LAST_INSERT_ID MySQL function..

And we’re done :)

20 Jun 2006, 16:36
Generic-user-small

Fora User (865 posts)

Problem

Your the envy of the IT shop for getting such a cool web app up in record time using Rails. But some critics still roll their eyes and say “another trend”. They’re waiting to poke holes in such a new technology.

(Corallary:) Even though it says clearly in database.yml …

Do not set this db to the same as development or production.

...you’ve gone and done it. More than once. And Murphy’s Law says that someone will do it in production one day as well. You need a backup and recovery strategy.

Credit

Geoffrey Grosenbach has a great plug-in called ar_fixtures href=”http://nubyonrails.com/articles/2005/12/27/dump-or-slurp-yaml-reference-data”>http://nubyonrails.com/articles/2005/12/27/dump… that inspired these scripts. Also much credit to Jim Weirich not just for creating rake, but also actively helping newcomers online. I’ve gotten many tips from his posts href=”http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/135728”>http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ru… .

Ingredients

Make sure that zip and unzip commands are in the PATH of your OS.

Solution

Rake already has two built in tasks to handle restoring the database, :db_schema_dump, and :db_schema_import. By adding code to export and import your data to and from YAML, and code to zip it up along with any files created at runtime, you’ve got yourself a Six Sigma Sabanes-Oxley buzzword-compliant y2k disaster recovery plan.

backup.rake href=”http://www.apptrain.com/backup.rake”>http://www.apptrain.com/backup.rake

The backup script needs some basic file utilities, and the packagetask.

require 'fileutils'
require 'rake/packagetask'

First we’ll get all the model names that need backing up. Omitting anything with an underscore eliminates _mailer or _observer files.
MODELS = FileList['app/models/*'].exclude(/.*_.*/)

Then we loop through all the models and dump them to YAML. (See Recipie #23)
task :backup_app_data => [:db_schema_dump] do
  MODELS.each do |m|
    model = File.basename(m,'.rb')
    dump_to_yaml(nil,model)
  end
end

Now let’s zip it all up. Amazingly enough the PackageTask can create a zip file instead of a gem just by setting the need_zip switch. The FileList can be customized to include any additional data that may be created by your app while running.
Rake::PackageTask.new("#{THISAPP}","0.0.1") do |z|
  # customize this list to include any files created by you app at runtime. 
  z.package_files = FileList["db/*"] #,"public/ad/**/*"]
  z.need_zip = true
end  

There. Now you can report to management that there’s a backup strategy in place. There will be much rejoicing. Unless of course disaster strikes. Then you’ll realize you need a recovery script as well.

restore.rake href=”http://www.apptrain.com/restore.rake”>http://www.apptrain.com/restore.rake

For recovery we’re rebuilding the app from scratch. We’re assuming that the correct version of your app can be retrieved from source control. Once the code is in place, we can unzip our data package.

Unzipping a .zip is a little trickier. You need the zipfilesystem library. It’s documented at “

04 Dec 2006, 21:19
Generic-user-small

Fora User (865 posts)

Problem

You want to allow your users to write using an easy-to-read,
easy-to-write plain text format, then convert it to structurally valid
XHTML using a tool such as Markdown and/or Textile (BlueCloth/RedCloth).

Extra Ingredients

This recipe requires the BlueCloth and RedCloth gems for Ruby. Install
them as follows:


gem install -r bluecloth
gem install -r redcloth

Credits

Tobias Luetke’s Typo weblog application makes
extensive use of markup, pre- and post- text filters and inspired this
recipe. For a much more complex example download the Typo source code:


svn co svn://typosphere.org/typo/trunk typo

Solution

Modify config/environment.rb by adding the following line at the end:


require 'bluecloth'
require 'redcloth'

Modify app/helpers/application_helper.rb by adding the following methods:


  def markdown(text)
    BlueCloth.new(text.gsub(%r{</?notextile>}, '')).to_html
  end

  def filtertext(text)
    RedCloth.new(text.gsub(%r{</?notextile>}, '')).to_html
  end

Then in your views you can simply do the following:


  <%= markdown(@user.description) %>

or

  <%= filtertext(@post.body) %>

Discussion

This recipe only affects the display of your data, it is stored in the
DB in its original form (i.e. with all of the special Markdown/Textile
characters). If you’d like to store the HTML-ized version in the DB
you should modify your model(s) with a

:before_save
filter
like so:

class Post < ActiveRecord::Base

  before_save :filtertext

  def filtertext
    self.title = RedCloth.new(self.title).to_html
    self.author = RedCloth.new(self.author).to_html
    self.body = RedCloth.new(self.body).to_html
  end

end

The only draw-back to this approach is that now the HTML code is in
the DB and on a subsequent edit/update of these fields the output may
not be what the user is expecting. Thus I would recommend shadowing
these fields like so:


  def filtertext
    self.title_display = RedCloth.new(self.title).to_html
    self.author_display = RedCloth.new(self.author).to_html
    self.body_display = RedCloth.new(self.body).to_html
  end

and then in your view you would only show the display fields unless
the user was editing the content in which case you’d use the ‘regular’
fields.

Further Reading

See the note above about Typo.

href="http://www.typosphere.org">http://www.typosphere.org
[leetsoft]http://blog.leetsoft.com
25 Apr 2008, 23:28
Generic-user-small

Fora User (865 posts)

Problem

You want to allow your users to use certain HTML tags in their input
while restricting all other HTML tags.

Extra Ingredients

None

Credits

The idea for this recipe comes from Koen Eijsvogels and his post in
the Request a
Recipe forum.

Solution

Modify config/environment.rb by adding the following line at the end:


require_dependency 'rails_patch/text_helper'

Add a new subdirectory rails_patch to your application lib directory.

Add text_helper.rb to your lib/rails_patch directory and ensure it
looks as follows:


module ActionView
  module Helpers
    module TextHelper
      ALLOWED_TAGS = %w(a img) unless defined?(ALLOWED_TAGS)

      def whitelist(html)
        # only do this if absolutely necessary
        if html.index("<")
          tokenizer = HTML::Tokenizer.new(html)
          new_text = "" 

          while token = tokenizer.next
            node = HTML::Node.parse(nil, 0, 0, token, false)
            new_text << case node
                        when HTML::Tag
                          if ALLOWED_TAGS.include?(node.name)
                            node.to_s
                          else
                            node.to_s.gsub(/</, "lt")
                          end
                        else
                          node.to_s.gsub(/</, "lt")
                        end
          end

          html = new_text
        end

        html
      end
    end
  end
end

Then in your views you can simply do the following:


  <%= whitelist(@post.body) %>

NOTE: Due to a limitation in textile (used in this forum) I had to remove the ampersands and semi-colons from the code above, so you’ll need to re-insert those in the gsub calls. So the code should look like node.to_s.gsub(/LESS-THAN/, “AMPERSANDltSEMI-COLON”) where LESS-THAN, AMPERSAND and SEMI-COLON are replaced by their respective characters.

Discussion

Obviously we are taking advantage of the fact that classes (and
modules, in this case) in Ruby are open by patching our own method
into Rail’s TextHelper module.

If you want to allow more tags than just anchors and images, add
the additional tags to the %w() in following line:


ALLOWED_TAGS = %w(a img) unless defined?(ALLOWED_TAGS)

Further Reading

You can see examples of this in the source code for the Typo
weblog where it patches ActiveRecord::Base and also
ActionController::Components.

href="http://www.typosphere.org">http://www.typosphere.org
05 Aug 2007, 16:34
Generic-user-small

Fora User (865 posts)

Problem

You have a rails application with lots of controllers / views. All your pages have common structure elements (like header, footer etc.) through usage of layouts.

Rails allows one level of layouts. Most of the time it is sufficient, but sometimes you feel uncofortable with having pages sharing common elements. For example, all pages of a particular controller should have a common “submenu”. You end up using the same particles on every controller view.

Solution

Most of modern frameworks have an ability to embed pages in a layout, and then embed the result in another layout and so on. With a little trick you can do the equal thing in Rails.

First, add the following method into your ApplicationHelper:


def inside_layout(layout, AMPERSANDblock)
  @template.instance_variable_set("@content_for_layout", capture(AMPERSANDblock))

  layout = layout.include?("/") ? layout : "layouts/#{layout}" if layout
  buffer = eval("_erbout", block.binding)
  buffer.concat(@template.render_file(layout, true))
end

Then, design your views to use e.g. ‘inner_layout’.

In ‘layouts/inner_layout.rhtml’ write:

<% inside_layout 'outer_layout' do %>

  <div id="common_elements">
  </div>

  <%= @content_for_layout %>

<% end %>

The ‘outer_layout.rhtml’ will go as ussual layout (unless it needs to be nested in other higher level layout)

NOTE: Due to a limitation in textile (used in this forum) I had to remove the ampersands from the code above, so you’ll need to re-insert those before “block” variable. So the code should look like “capture(AMPERSANDblock) where AMPERSAND is replaced by the ampersand character.

21 Mar 2006, 16:58
Generic-user-small

Fora User (865 posts)

Note that to be able to use fixtures, we also need to define the reset_pk_sequence! for our MysqlAdapter. This function is called when all the fixtures are loaded to set the value of the sequence table accordingly (otherwise it will have a perhaps inconsistent value).

In our case, we have to get the last id value of our table, and set or update the id value of our sequence table accordingly:


      def reset_pk_sequence!( table, pk = nil, sequence = nil )                                                                                                                      
        sequence_name = table + "_sequence"                                                                                
        max_pk = select_value("SELECT MAX(id) FROM #{table}").to_i + 1                                                    
        sql = "INSERT INTO #{sequence_name} (`id`) VALUES (#{max_pk})"                                                         
        begin                                                                                                                  
          insert( sql )                                                                                                        
        rescue ActiveRecord::StatementInvalid                                                                                  
          sql = "UPDATE #{sequence_name} SET id=#{max_pk}"                                                                     
          nb = update( sql ,"Update Sequence")                                                                                 
        end                                                                                                                    
28 Mar 2006, 08:13
Generic-user-small

Fora User (865 posts)

Can’t wait for the book fellas…another great Rails resource. I made you famous by adding your topics RSS to Rails-a-Rama!. Yes I am pimping it…because its a good thing like Martha Stewart said! I think… ;-)

03 Apr 2006, 17:33
Generic-user-small

Fora User (865 posts)

There doesn’t seem to be any actual source code in the Authentication folder (or any other folder)?

13 Mar 2007, 15:40
Generic-user-small

Fora User (865 posts)

Problem

You want to use AJAX on a field that isn’t free text, but is a list of things that your user can choose.

Extra Ingredients

Rails 1.1

Solution

As of script.aculo.us version 1.5.3, a control called Ajax.InPlaceCollectionEditor deals with precisely this issue. This function works identically to Ajax.InPlaceEditor, except that it takes an array of arrays consisting of option names and values.

A Rails-oriented solution, however, should work similarly to the way select options are generated. Define a function in your application_helper.rb file:

def in_place_collection_editor_field(object,method,container)

The final parameter, container, contains the options for the drop-down box. First, resolve a tag and create the submit URL:

tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
url = url_for( :action => "set_#{object}_#{method}", :id => tag.object.id )

On the controller side, you will need to define a method set_object_method to handle the input. Next, start the definition of the function:

function =  "new Ajax.InPlaceCollectionEditor(" 
function << "'#{method}'," 
function << "'#{url}',"

Then, process the options:

collection = container.inject([]) do |options, element|
  options << "[ '#{html_escape(element.last.to_s)}', '#{html_escape(element.first.to_s)}']" 
end

Finally, add the collection to the final Javascript:

function << "{collection: [#{collection.join(',')}]," 
  function << "});" 
end
javascript_tag(function)

For example, in a list.rhtml file, the following defines an editor for a field called custom_field that has a number of options taken from the FieldType model:

<%= in_place_collection_editor_field 'custom_field', 'field_type', 
                           FieldType.find_all.collect{|x| [x.name,x.id]} %>

The x.name is the plaintext description, to be displayed to the user, while the x.id is the internal id. Given that the selected object id will be passed to the controller, along with the id of the object to process, the controller is simply defined as:

def set_custom_field_field_type
  @i = CustomField.find(params[:id])
  f  = FieldType.find(params[:value])
  @i.update_attribute( :field_type, f )
  render :text => f.name
end

Discussion

While this example also by-passes model validation, this can easily be built into the controller action. Logical extensions to this approach might include the other approaches used in ActionView::Helpers::FormOptionsHelper.

Further Reading

See the script.aculo.us documentation href=”http://wiki.script.aculo.us/scriptaculous/show/Ajax.In+Place+Collection+Editor”>http://wiki.script.aculo.us/scriptaculous/show/...

16 Apr 2006, 09:07
Generic-user-small

Fora User (865 posts)

This is almost certainly not the correct place to post this, so apologies in advance.
Update Its definately the wrong place :-(.

Paragraph four in “The Console is Your Friend” ends with the following line:

“Who needs and IDE?!”

My guess is that this was meant to be “Who needs an IDE?!”

Keith

16 Apr 2006, 09:07
Generic-user-small

Fora User (865 posts)

That’s right! Thanks :)

07 Jun 2006, 00:47
Generic-user-small

Fora User (865 posts)

Hello,

I’m afraid the very first recipe, InPlaceEditing, stumped me. How do I ‘cook’ this recipe? Don’t I have to create a database first? What’s the name of the database to create? I know I have to run some scripts sometime in the process.

Could we, at least for the first few recipes, be as detailed as can be, with one recipe that starts from the very, very beginning?

Thanks!
frustrated

29 Apr 2006, 09:13
Generic-user-small

Fora User (865 posts)

Bold phrase

08 Feb 2007, 21:14
Generic-user-small

Fora User (865 posts)

Problem

A model requires that one or more of its attributes be write once only.
In other words, these protected attributes may only be set when the model is first save dto the database and are immutable thereafter.

Credits

In order to write my unit tests for this code I referred to Ezra Zygmuntowicz’s ez_where plugin href=”http://OpenSVN.csie.org/ezra/rails/plugins/ez_where”>http://OpenSVN.csie.org/ezra/rails/plugins/ez_w…

Solution

The code that you are going to have to write needs to do the following :
  • Only run on an update. This validation doesn’t apply to record creations.
  • Retrieve a copy of the model form the database.
  • Compare each protected attribute of the current model to that of the copy.
  • Add an error to the model’s errors collection for each protected attribute that differs.

You could situate this code within the model’s validate_on_update method. But if you created your own validation as a plugin, validates_write_once_of, you could reuse this validation on other models and you’d gain the benefit of making your code more descriptive.
Rails makes this easy (of course) – we’ll just model the new vaidation on an existing one.

Validations are located in lib/active_record/validations.rb

Examining the method validates_presence_of for example, shows that it takes the following options
  • message – a cusom error message (default is “can’t be blank”)
  • on – Specifies when this validation is active (default is :save, other options are :create, :update)
  • if – Specifies a method, proc or string to call to determine if the validation should occur

The new validation will take all those options except for ‘on’. It only makes sense for this validation to be active on an update. The default message will be “can’t be changed”.

Here’s the code for write_once_of.rb


module WriteOnceOf
  def validates_write_once_of(*attr_names)
    configuration = { :message => "can't be changed" }
    configuration.merge!(attr_names.pop) if attr_names.last.is_a?(Hash)
    send( validation_method(:update) ) do |record|
      unless configuration[:if] and not evaluate_condition(configuration[:if], record)
        previous = self.find record.id
        attr_names.each do |attr_name|
          record.errors.add( attr_name, configuration[:message] ) if record.respond_to?(attr_name) and previous.send(attr_name) != record.send(attr_name)
          # replace the 'and' above with a double ampersand 
        end
      end
    end
  end
end

To get the validation to work as a plugin package it as follows

validates_write_once_of/
  init.rb
  lib/
    write_once_of.rb

The code for init.rb is


require 'write_once_of'
ActiveRecord::Base.extend WriteOnceOf


The validates_write_once_of folder should be placed in the /vendor/plugins folder of your rails application.

Use as follows


class SomeClass < ActiveRecord::Base
  validates_write_once_of :some_attribute, { :message => "can't be changed ... ever" }
end

Discussion

Rails ships with a very full and flexible set of validations.
But being able to create your own validations enables you to write code that is very declarative and specific to your application domain. Its certainly worth doing so given how easily it can be done.

28 May 2006, 03:52
Generic-user-small

Fora User (865 posts)

Problem

You’ve got your software running. You’re using Capistrano to deploy it. But there are several people working on the project who can deploy to production. You want all of the developers to be notified when your software gets deployed to production, including knowing which version of the software was deployed.

Extra Ingredients

Because this hooks into the deployment, this assumes you’ll already be including Capistrano href=”http://manuals.rubyonrails.com/read/book/17”>http://manuals.rubyonrails.com/read/book/17

Solution

Generate a mailer :


 ruby script/generate mailer Notifier deployed
      exists  app/models/
      create  app/views/notifier
      exists  test/unit/
      create  test/fixtures/notifier
      create  app/models/notifier.rb
      create  test/unit/notifier_test.rb
      create  app/views/notifier/deployed.rhtml
      create  test/fixtures/notifier/deployed 

In the capistrano deploy recipe (config/deploy.rb), create an “after_deploy” task :


desc "After the deploy is done, let people know about it" 
task :after_deploy do
  #  The mailer needs the environment configured for it - otherwise it doesn't 
  #  find the templates, and it doesn't know how to send email.
  require File.join(File.dirname(__FILE__), 'environment')
  Notifier.deliver_deployed("#{revision}")
end  

task :after_deploy_with_migrations do
  #  The same stuff that we want to do after a "regular" deploy
  after_deploy
end

Let the Notifier model handle the information sent to it :


  def deployed(version)
    @subject    = "Software Deployed!" 
    body(:version => version)
    @recipients = 'geeks@example.com'
    @from       = 'deployer@example.com'
    @sent_at    = Time.now
    @sent_on    = Time.now
  end  

Finally, modify the view (app/views/notifier/deployed.rhtml) to have some important information.


Hello Geeks,

We just deployed to production with version <%= @version %> of the baseline.

It is now <%= Time.now %>.  Have a nice day.

Thank you, 

"The Deployer" 

Now, whenever someone deploys your software to production, you will receive a nice email letting you know about it.

Discussion

This example uses the version that was deployed to production, but there are several variables that the deployment has available to it. Maybe including the user who did the deployment would be important to you, or the machines that were included in the deployment. See information in the deploy.rb along with documentation on Capistrano.

Including the “environment” in the require means that you will have access to all of the information in your application (and all of the database connections). That means you could also do different things than just sending an email – you could update a column in one of your models, insert information into your database, or just list all the users in your system.

07 Jun 2006, 00:47
Generic-user-small

Fora User (865 posts)

I understand where you’re coming from, resty, but that’s not what “recipe” books are about, in general. It’s an established genre, and it’s pretty much what it sounds like—how to make a specific thing, be it chocolate chip cookies or in-place editing.

To carry the analogy, if you’re holding your recipe book in one hand, and looking worryingly at what you’ve been assured is an “oven,” the recipe book is best perused after you read a book of the “How To Cook” genre.

I would suggest Agile Web Development with Rails, by Dave Thomas and DHH, which is available from Pragmatic Bookshelf. Once you’ve got your head wrapped around that, try Ruby For Rails, by David Black, from Manning Press.

09 Jun 2006, 20:49
Generic-user-small

Fora User (865 posts)

Problem

You want to share instance variables across methods, or controllers.

Extra Ingredients

None

Credits

Taken from a post by Ezra Zygmuntowicz on the rails mailing list.

Solution

Put this in the top of your controller where you want to have a
shared var. Assuming the var is @foo

MyController < ApplicationController

before_filter :setup_foo
 attr_reader :foo
def setup_foo
    @foo = "Whatever you want @foo to contain" 
end

end

Now @foo will be available to every action method in your controller
and also in your views for that controller. If you want @foo to be
available in all your controllers and view, put that code in your
application.rb ApplicationController.

Discussion

You can also use a CONSTANT of course, defined in application.rb, for a variable whose contents will not change, and which will be available to the entire application.

20 Jun 2006, 16:36
Generic-user-small

Fora User (865 posts)

The last method above, load_from_yaml, doesn’t work anymore as written with ruby 1.8.4 . I’ve patched the restore.rake href=”http://www.apptrain.com/restore.rake”>http://www.apptrain.com/restore.rake file. The link above also reflects the changes.

27 Aug 2006, 06:48
Generic-user-small

Fora User (865 posts)

hello,

i am not new to rails but i am new to ajax. when i try to run the in_place_editor_field recipe i get a weird error.

this is what it says.

Error communicating with the server:

Action Controller: Exception caught
body { background-color: #fff; color: #333; }
body, p, ol, ul, td {
  font-family: verdana, arial, helvetica, sans-serif;
  font-size:   13px;
  line-height: 18px;
}
pre {
  background-color: #eee;
  padding: 10px;
  font-size: 11px;
}
a { color: #000; }
a:visited { color: #666; }
a:hover { color: #fff; background-color:#000; }

Unknown action
No action responded to set_client_contact_first_name

i generated my app with scaffolding, and include the default javascript files. this error comes up after i make a change and press OK. then the area where i was editing the field says saving, then the error pops up in a javascript alert window.

any help would be greatly appreciated.

thanks.

10 Sep 2006, 14:15
Generic-user-small

Fora User (865 posts)

One big problem with this recipe:

It doesn’t do anything about tag attributes. A false sense of security is worse than none at all.

This means I can submit javascript handlers on allowed tags, a pretty big no-no. Other pre-santizing factors come into play as well.

The way around this is simple, but annoyingly so because you can’t write to the attributes attribute of an HTML::Tag node.


# stuff . . .
 if ALLOWED_TAGS.include?(node.name)
                             new_node=HTML::Tag.new(node.parent, node.line, node.position, node.name, [], node.closing)
                             new_node.to_s
                           else
# stuff . . .

08 Oct 2006, 16:49
Generic-user-small

Fora User (865 posts)

I’ve written an update to this recipe that allows you to safely filter attributes/tags and define “profiles” that let you create different levels of filtering for different users.

Details here:

>http://www.kookdujour.com/blog/details/11...

07 Nov 2006, 22:41
Generic-user-small

Fora User (865 posts)

I’m totally lost as to what the complete code is supposed to look like or in which files each of your code snippets live? Can you possibly provide a little more detail? It sounds exactly like what I need. ie.values along with the option tags.

04 Dec 2006, 21:19
Generic-user-small

Fora User (865 posts)

Is there a reason why you would use both Bluecloth and RedCloth? Would just one or the other suffice?

31 Jan 2007, 16:40
Generic-user-small

Fora User (865 posts)

Hello i’m new on rails, i will post here anyways think will help some one. the comments above i did this
inf helper/application_helper.rb i added this
module ApplicationHelper

def 
in_place_collection_editor_field(object,method,container) tag = ::ActionView::Helpers::InstanceTag.new(object, method, self) url = url_for( :action => "set_#{object}_#{method}", :id => tag.object.id ) function = "new Ajax.InPlaceCollectionEditor(" function << "'#{method}'," function << "'#{url}'," collection = container.inject([]) do |options, element| options << "[ '#{html_escape(element.last.to_s)}', '#{html_escape(element.first.to_s)}']" end function << "{collection: [#{collection.join(',')}]});" javascript_tag(function) end
on the view i added like this and that works for me
<=@user.sex %><= in_place_collection_editor_field :user,'sex', Sex.find_all.collect{|x| [x.id,x.english]} -%>

where Sex is a object storage on the db so
Also there is a catch that i figured out later from the main solution text you will find this line function << “{collection: [#{collection.join(’,’)}],}); i have to change it to function << “{collection: [#{collection.join(’,’)}]}); so it works with IE now

08 Feb 2007, 21:14
Generic-user-small

Fora User (865 posts)

Very useful post! A good addition to help newbies like me would be to say that the skeleton for the plugin can be generated through

./script/generate plugin validates_write_once_of

Then you can paste into these skeleton files the parts of this recipe

I added a quick note on this on my site http://jcandkimmita.info/jc/?p=63

02 Mar 2007, 06:55
Generic-user-small

Fora User (865 posts)

Hi all I am new(beginner) to rails…. where should i start from… please guide me….
suggest me some books…. thank u

13 Mar 2007, 15:40
Generic-user-small

Fora User (865 posts)

Thanks for the post, helped a lot.

A little tough to follow though, here is the result that worked for me:

>http://pastie.caboo.se/46627...

05 Aug 2007, 16:34
Generic-user-small

Fora User (865 posts)

Great post, Maxim.

To take it even further, you can use any partial to act as a layout:

application_helper.rb


def inside(partial, AMPERSANDblock)
  instance_variable_set(:@content_for_layout, capture(AMPERSANDblock))
  concat(render(:partial => partial), block.binding)
end

_wrapper.rhtml (the outer layout partial)


<div id="wrapper">
  <%= yield %>
<div>

content.rhtml (the actual template)


<% inside "wrapper" do -%>
  Inside the wrapper
<% end -%>

Result:


<div id="wrapper">
  Inside the wrapper
</div>

This is completely compatible with the standard Rails layouts. They’ll work like they did before.

However, I don’t know if this plays well with page caching. Testing this is left as an exercise for the reader :)

—Andy

  You must be logged in to comment