Write Your Own!
Fora User
0 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:
Fame and fortune await. Let’s see what you’ve got! Who knows? You may even end up (with permission) in the book! |
Fora User
0 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 about this. Maybe you or someone else can expand that? |
Fora User
0 posts
|
ProblemYour 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 …
...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. CreditGeoffrey Grosenbach has a great plug-in called ar_fixtures 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 . IngredientsMake sure that zip and unzip commands are in the PATH of your OS. SolutionRake 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. 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. 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 http://rubyzip.sourceforge.net/classes/ . require 'zip/zipfilesystem' After opening the .zip, you need to extract each file by name. Notice the last line of this task. We’re copying everything we zipped up to the RAILS_ROOT.
task :unzip_app do
FileUtils.rm_rf(THISAPP_ZIP.sub('.zip',''))
Zip::ZipFile.open(THISAPP_ZIP, Zip::ZipFile::CREATE) { |zipfile|
Zip::ZipFile.foreach(THISAPP_ZIP) { |f|
if f.file?
mkdirs RAILS_ROOT+"/#{BACK}/"+f.parent_as_string if f.parent_as_string
zipfile.extract(f,BACK+'/'+f.name)
end
}
}
FileUtils.cp_r(Dir.glob("#{THISAPP_ZIP.sub('.zip','')}/."),RAILS_ROOT)
end
Now it’s time to import our data. Once again the comments warn about the destructive nature of this task. If your schema already exists, you can change the dependency of restore_app to ’:environment’.
# CAUTION!!! executing ':db_schema_import' blows away existing tables and data
# This script is meant only for a recovery from scratch not for migration.
task :restore_app => [:unzip_app, :db_schema_import] do
MODELS.each do |m|
model = File.basename(m,'.rb')
# Since some models contain validation that fails on import a
# new blank model is generated just for the sake of the import.
make_temp_model(m)
load_from_yaml("db/#{model.downcase.pluralize}.yml",'test',model)
# Alternatively, use this line with the ar_fixtures plugin.
# eval("#{model.camelize}.load_from_file") if File.exists?("db/#{model.downcase.pluralize}.yml")
restore_model(model)
end
end
DiscussionNotice the two methods make_temp_model and restore_model. This is a brute force solution to an interesting problem. load_from_yaml creates instances of classes on the fly, but often validation in the model objects fails. The most common being validates_presence_of another class. There’s no need to enforce all the business rules for creating objects here because we know our backup data came from an identical application. So we simply rely on ruby’s power and back the models up , create empty models on the fly, and then put the old one’s back. A lot of work in some languages, but just a few lines of code for us Ruby enthusiasts.
def make_temp_model(file_name)
FileUtils.mv(file_name,file_name+'.bak')
Dir.chdir(RAILS_ROOT)
system("ruby script/generate model #{File.basename(file_name,'.rb')} -s >>mod.txt")
end
def restore_model(model)
file_name = RAILS_ROOT+'/app/models/'+"#{model}.rb"
FileUtils.mv(file_name+'.bak',file_name) if File.exists?(file_name+'.bak')
end
Lastly here’s the code from the ar_fixtures plugin, modified slightly to work here. # code from http://wiki.rubyonrails.org/rails/pages/AR+Fixtures+Plugin def load_from_yaml(yaml_file,app,obj,save_id = true) if File.exists?(yaml_file) records = YAML::load( File.open(yaml_file)) records.each do |record| new_obj = nil; eval ("new_obj = #{obj.camelize}.new(record.attributes)") new_obj.id = record.id if save_id # For Single Table Inheritance klass_col = record.class.inheritance_column.to_sym if record[klass_col] new_obj.type = record[klass_col] end new_obj.save end end end |
Fora User
0 posts
|
Using legacy databasesProblemYour 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:
SolutionAlthough you can use the We’re going to use the class methods So let’s create a file, named
With this all the computed table names will begin with Just adds a
to 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
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:
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:
Were basically telling to mySQL to get a new value based on the last
one, using the And we’re done :) |
Fora User
0 posts
|
ProblemYour 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:
SolutionAlthough you can use the We’re going to use the class methods So let’s create a file, named
With this all the computed table names will begin with Just adds a
to 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
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:
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:
Were basically telling to mySQL to get a new value based on the last
one, using the And we’re done :) |
Fora User
0 posts
|
ProblemYour 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. CreditGeoffrey Grosenbach has a great plug-in called ar_fixtures 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 . IngredientsMake sure that zip and unzip commands are in the PATH of your OS. SolutionRake 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. 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.
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 http://rubyzip.sourceforge.net/classes/ .require 'zip/zipfilesystem'After opening the .zip, you need to extract each file by name. Notice the last line of this task. We’re copying everything we zipped up to the RAILS_ROOT.
task :unzip_app do
FileUtils.rm_rf(THISAPP_ZIP.sub('.zip',''))
Zip::ZipFile.open(THISAPP_ZIP, Zip::ZipFile::CREATE) { |zipfile|
Zip::ZipFile.foreach(THISAPP_ZIP) { |f|
if f.file?
mkdirs RAILS_ROOT+"/#{BACK}/"+f.parent_as_string if f.parent_as_string
zipfile.extract(f,BACK+'/'+f.name)
end
}
}
FileUtils.cp_r(Dir.glob("#{THISAPP_ZIP.sub('.zip','')}/."),RAILS_ROOT)
end
Now it’s time to import our data. Once again the comments warn about the destructive nature of this task. If your schema already exists, you can change the dependency of restore_app to ’:environment’.
# CAUTION!!! executing ':db_schema_import' blows away existing tables and data
# This script is meant only for a recovery from scratch not for migration.
task :restore_app => [:unzip_app, :db_schema_import] do
MODELS.each do |m|
model = File.basename(m,'.rb')
# Since some models contain validation that fails on import a
# new blank model is generated just for the sake of the import.
make_temp_model(m)
load_from_yaml("db/#{model.downcase.pluralize}.yml",'test',model)
# Alternatively, use this line with the ar_fixtures plugin.
# eval("#{model.camelize}.load_from_file") if File.exists?("db/#{model.downcase.pluralize}.yml")
restore_model(model)
end
end
DiscussionNotice the two methods make_temp_model and restore_model. This is a brute force solution to an interesting problem. load_from_yaml creates instances of classes on the fly, but often validation in the model objects fails. The most common being validates_presence_of another class. There’s no need to enforce all the business rules for creating objects here because we know our backup data came from an identical application. So we simply rely on ruby’s power and back the models up , create empty models on the fly, and then put the old one’s back. A lot of work in some languages, but just a few lines of code for us Ruby enthusiasts.
def make_temp_model(file_name)
FileUtils.mv(file_name,file_name+'.bak')
Dir.chdir(RAILS_ROOT)
system("ruby script/generate model #{File.basename(file_name,'.rb')} -s >>mod.txt")
end
def restore_model(model)
file_name = RAILS_ROOT+'/app/models/'+"#{model}.rb"
FileUtils.mv(file_name+'.bak',file_name) if File.exists?(file_name+'.bak')
end
Lastly here’s the code from the ar_fixtures plugin, modified slightly to work here.
# code from http://wiki.rubyonrails.org/rails/pages/AR+Fixtures+Plugin def load_from_yaml(yaml_file,app,obj,save_id = true) if File.exists?(yaml_file) records = YAML::load( File.open(yaml_file)) records.each do |record| new_obj = nil; eval ("new_obj = #{obj.camelize}.new(record.attributes)") new_obj.id = record.id if save_id # For Single Table Inheritance klass_col = record.class.inheritance_column.to_sym if record[klass_col] new_obj.type = record[klass_col] end new_obj.save end end end |
Fora User
0 posts
|
ProblemYou 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 IngredientsThis recipe requires the BlueCloth and RedCloth gems for Ruby. Install them as follows:
CreditsTobias 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:
SolutionModify config/environment.rb by adding the following line at the end:
Modify app/helpers/application_helper.rb by adding the following methods:
Then in your views you can simply do the following:
or
DiscussionThis 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 filter
like so:
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:
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 ReadingSee the note above about Typo. |
Fora User
0 posts
|
ProblemYou want to allow your users to use certain HTML tags in their input Extra IngredientsNone CreditsThe idea for this recipe comes from Koen Eijsvogels and his post in SolutionModify config/environment.rb by adding the following line at the end:
Add a new subdirectory rails_patch to your application lib directory. Add text_helper.rb to your lib/rails_patch directory and ensure it
Then in your views you can simply do the following:
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. DiscussionObviously we are taking advantage of the fact that classes (and If you want to allow more tags than just anchors and images, add
Further ReadingYou can see examples of this in the source code for the Typo
|
Fora User
0 posts
|
ProblemYou 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. SolutionMost 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:
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. |
Fora User
0 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
|
Fora User
0 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… ;-) |
Fora User
0 posts
|
There doesn’t seem to be any actual source code in the Authentication folder (or any other folder)? |
Fora User
0 posts
|
ProblemYou want to use AJAX on a field that isn’t free text, but is a list of things that your user can choose. Extra IngredientsRails 1.1 SolutionAs 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:
The final parameter, container, contains the options for the drop-down box. First, resolve a tag and create the submit URL:
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:
Then, process the options:
Finally, add the collection to the final Javascript:
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:
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:
DiscussionWhile 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 ReadingSee the script.aculo.us documentation |
Fora User
0 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 |
Fora User
0 posts
|
That’s right! Thanks :) |
Fora User
0 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 |
Fora User
0 posts
|
Bold phrase |
Fora User
0 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 Solution The code that you are going to have to write needs to do the following :
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
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
To get the validation to work as a plugin package it as follows
The code for init.rb is
The validates_write_once_of folder should be placed in the /vendor/plugins folder of your rails application.
Use as follows
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. |
Fora User
0 posts
|
ProblemYou’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 IngredientsBecause this hooks into the deployment, this assumes you’ll already be including Capistrano SolutionGenerate a mailer :
In the capistrano deploy recipe (config/deploy.rb), create an “after_deploy” task :
Let the Notifier model handle the information sent to it :
Finally, modify the view (app/views/notifier/deployed.rhtml) to have some important information.
Now, whenever someone deploys your software to production, you will receive a nice email letting you know about it. DiscussionThis 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. |
Fora User
0 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. |
Fora User
0 posts
|
ProblemYou want to share instance variables across methods, or controllers. Extra IngredientsNone CreditsTaken from a post by Ezra Zygmuntowicz on the rails mailing list. SolutionPut this in the top of your controller where you want to have a shared var. Assuming the var is @foo MyController < ApplicationController
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. DiscussionYou 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. |
Fora User
0 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 file. The link above also reflects the changes. |
Fora User
0 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:
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. |
Fora User
0 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.
|
Fora User
0 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: |
32 posts, 1 voice
