Archive for the ‘rails’ Category
Rails 2.x automatically dasherizes ActiveRecord inheritors when to_xml is called, but if you want to deal directly with Builder::XmlMarkup you have to go through some hoops to dasherize your element names.
I solved this problem today with a simple subclass. Here's the code:
[sourcecode language='ruby']
# Creates a Builder::Markup implementation that dasherizes all element and attribute names
# Use this just like you would Builder::XmlMarkup
class DasherizingBuilder < Builder::XmlMarkup
def _start_tag(sym, attrs, end_too=false)
super(sym.to_s.dasherize, attrs, end_too)
end
def _end_tag(sym)
super(sym.to_s.dasherize)
end
def _insert_attributes(attrs, order=[])
return if attrs.nil?
new_order= []
order.each {|item| new_order
1 Comment »
I ran into this problem while trying to MonkeyPatch some class methods into existing Rails classes.
Say you have the following Mooable module (as a rails plugin) that you want to dynamically inject into ActiveRecord::Base (or any ruby class for that matter).
[sourcecode language='ruby']
# app/models/my_model.rb
class MyModel < ActiveRecord::Base
...
end
# plugins/mooable/lib/mooable.rb
module Mooable
def moo
return "Moo!"
end
end
# plugins/mooable/init.irb
ActiveRecord::Base.send :include, Mooable
# test
MyModel.new.moo # => "Moo!"
[/sourcecode]
But what if you wanted #moo to be a class method on ActiveRecord::Base? I was tempted to do the following:
[sourcecode language='ruby']
#plugins/mooable/lib/mooable.rb
module Mooable
def self.moo
return "Moo!"
end
end
# test
MyModel.moo # => NoMethodError: undefined method `moo' for MyModel:Class
[/sourcecode]
As you can see, simply defining the module method as a class method is a no-go (I'm sure this is a major nuby mistake, but it's always worth a try!)
The best way I have found so far to monkeypatch new class methods into rails classes with a plugin is to use the Object#extend method inside of the module's self.included hook. The new class methods are defined within a sub-module Mooable::ClassMethods for easy inclusion.
[sourcecode language='ruby']
# plugins/mooable/lib/mooable.rb
module Mooable
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def moo
return "Moo Static/Class Method"
end
end
def moo
return "Moo Instance Method!"
end
end
# test
MyModel.moo # => "Moo Static/Class Method"
MyModel.new.moo # => "Moo Instance Method!"
[/sourcecode]
This works like a charm.
1 Comment »
April 10, 2008 | code, rails
Having problems with Has and Belongs To Many(habtm) relationships loading your models with the wrong Ids? I was, the story follows.
Admittedly, HABTM is a relationship in Rails that I use rarely. This little snag caught me off-guard last week, and the usual internet searches didn't yield particularly fruitful results.
Say you have a simple HABTM relationship:
[sourcecode lang='ruby']
#Predator.rb
class Predator < ActiveRecord::Base
has_and_belongs_to_many :prey
end
#Prey.rb
class Predator < ActiveRecord::Base
has_and_belongs_to_many :predators
end
[/sourcecode]
So you've added the relationship, now you go to add your join table as a new migration:
[sourcecode lang='ruby']
class CreatePreyPredators < ActiveRecord::Migration
def self.up
create_table :prey_predators do |t|
t.integer :prey_id
t.integer :predator_id
end
end
def self.down
drop_table :prey_predators
end
end
[/sourcecode]
Now, say you've added a few relationships in your database:
[sourcecode lang='sql']
select * from prey_predators;
+---+--------------+--------------+
| id | prey_id | predator_id |
+------------------+--------------+
| 1 | 218872420 | 368007386 |
+------------------+--------------+
1 row in set (0.00 sec)
[/sourcecode]
BUT, when you look at your shiny new relationship in the console, you see this:
[sourcecode lang='html']
>> Prey.find(:first).predators
#
[/sourcecode]
You can see that IRB is reporting the predator's id as 1 and not 368007386. What you're seeing isn't the primary key of the predator record, but the primary key of the relationship record in the join table where predator was found.
It would appear that Rails gives any column named 'id' precedence when determining the id of the record-- ignoring the foreign kesy in the join table. I tried specifying the :foreign_key and :associated_foreign_key on the has_and_belongs_to_many relationship, but that only appears to affect the query that selects the records, not the model's "reconstitution" from the database.
Unless you want this behavior, just add :id => false to your migration and you're woes will be gone:
[sourcecode lang='ruby']
class CreatePreyPredators < ActiveRecord::Migration
def self.up
create_table :prey_predators, :id => false do |t|
t.integer :prey_id
t.integer :predator_id
end
end
def self.down
drop_table :prey_predators
end
end
[/sourcecode]
No Comments »
Recently I was writing custom Xml exporting for some of our Rails models. I favor TDD over just shoot-from-the-hip development, so I had to write some tests. For testing the validity of the XML, I chose XPath for its simplicity.
As I added functionality (and thereby added tests) I noticed that my tests were becoming increasingly redundant, and that Test::Unit's syntax was making my test files ugly and more importantly hard to read. As I started DRY-ing up the individual tests and trying to find better ways to share code and make the tests more succinct, I noticed that I was driving very close to creating my own mini-DSL for testing Xml with Xpath.
My Requirements for Xml Exporting
A required element must always exist in the resulting Xml regardless of the model's state
An optional element:
Must exist when the model meets a certain state condition (i.e. the member is not null)
Must not exist when that condition is not met
A DSL That Meets My Testing Needs
As mentioned earlier, this DSL evolved from repeated refactoring my tests in order to make them more clear. Here is an example of an XPath-based Xml test:
[sourcecode lang='ruby']
xpath_tests_for @video_library do
# ensure that the Xml always contains:
#
No Comments »
Inspired by an article on adding XPath matching to RSpec. I'm not using RSpec currently, and I also have a need for this functionality in my tests, so I decided to a helper for use in Test::Unit to achieve XPath matching in my tests.
Add a new helper method:
Add the following code to your test/test_helper.rb in order to create the assert_has_xpath method.
[sourcecode lang='ruby']
#test/test_helper.rb
require 'rexml/document'
# Asserts that the specified xpath matches
# at least once in the given document
def assert_has_xpath (xpath, doc)
doc = doc.is_a?(REXML::Document) ? doc : REXML::Document.new(doc)
match = REXML::XPath.match doc, xpath
assert !match.empty?, "Missing xpath '#{xpath}' in document: #{doc}"
end
[/sourcecode]
Optionally, you can add the above to a module and include it in your test_helper, or in your individual tests.
Use the helper in your tests:
Now, in your tests, you can do something like the following
[sourcecode lang='ruby']
#model.rb
def to_xml options = {}
xml = options[:builder] ||= Builder::XmlMarkup.new(options)
xml.toys{
xml.toy "ball"
xml.toy "iPhone"
}
end
#model_test.rb
def test_create_xml_creates_toys
model = Model.new
xml = model.to_xml
assert_has_xpath "/toys[toy='iPhone']", xml
end
[/sourcecode]
And voila! You now have XPath testing in your Test::Unit tests.
No Comments »
January 21, 2008 | rails
Recently I wanted to create a mixin for String in my Rails 2.0 project that allowed me to DRY up pluralization given an arbitrary number. Here's an example usage:
[sourcecode lang="ruby"]
"mile".pluralize_for(trip.miles) #returns "mile" when trip.miles is one and "miles" otherwise.
[/sourcecode]
The mixin code is simple:
[sourcecode lang="ruby"]
# martian_extensions.rb
module MartianExtensions
def pluralize_for(number)
unless number == 1
self.to_s.pluralize
else
self.to_s
end
end
end
[/sourcecode]
The challenge came with finding a simple, configuration-free way to ensure that this mixin is loaded. Following the guidance of Jamis Buck in this (by now ancient) blog article, i eventually settled on his solution of creating my simple mixin as a full-fledged plugin.
In order to do this without adding any configuration, you can follow Rails' automagic loading rules, namely, create an apt folder structure under your vendor/plugins directory:
/vendor/plugins/martian_extensions/
/vendor/plugins/martian_extensions/init.rb
/vendor/plugins/martian_extensions/lib
/vendor/plugins/martian_extensions/lib/martian_extensions.rb
You've already seen martian_extensions.rb, as for init.rb, that's what does the work of ensuring String loads my module and includes pluralize_for:
[sourcecode lang="ruby"]
#init.rb
String.send :include, MartianExtensions
[/sourcecode]
Personally, I'll keep my core extensions in this single plugin so that I can keep it portable between my applications. It seems pretty clean to me, but if there's a better solution that you have found, please comment!
No Comments »
January 8, 2008 | rails
If you're using globalize to translate or localize your rails site you'll have noticed that your log quickly fills up with messages like the following (especially in the devlopment environment):
[sourcecode language='sql']
Globalize::Language Columns (0.000516) SHOW FIELDS FROM globalize_languages
Globalize::Language Load (0.000202) SELECT * FROM globalize_languages WHERE (globalize_languages.`rfc_3066` = 'en-us') LIMIT 1
Globalize::Language Load (0.000131) SELECT * FROM globalize_languages WHERE (globalize_languages.`iso_639_1` = 'en') LIMIT 1
Globalize::Country Columns (0.000383) SHOW FIELDS FROM globalize_countries
Globalize::Country Load (0.000152) SELECT * FROM globalize_countries WHERE (globalize_countries.`code` = 'us') LIMIT 1
[/sourcecode]
We do a lot of view text translation in our templates, a lot like the following:
[sourcecode lang='ruby']
[/sourcecode]
And over time, you'll end up with tons of .translate calls, each one causing extra log cruft.
If you're developing a feature that doesn't require your localization to be enabled, and you want to skim down the globalize log cruft from this type of translation, here's a simple fix to softly disable the extensions to String
Edit vendor/plugins/globalize/lib/gloablize/localization/core_ext.rb and make it look like the following:
[sourcecode lang='ruby']
def translate(default = nil, arg = nil, namespace = nil)
# Locale.translate(self, default, arg, namespace)
self.to_s
end
[/sourcecode]
You can switch the comment from line #2 to line#3 to re-enable translate easily.
Note: make sure to restart your server to ensure this takes effect.
No Comments »
December 12, 2007 | rails
I am alive!
[sourcecode language='ruby']def hello_world
puts "Boyakasha"
end[/sourcecode]
No Comments »