This blog post will cover how to write JavaScript that is easier to maintain inside of a Ruby on Rails application. The following technique is what I use whenever writing JavaScript for a Rails application now. It uses JavaScript Objects to provide application specific objects and methods.
First assume there exists an application called MyApp
and it has requirements defined for managing users and
a blog. Also assume the requirement calls for a spinner
upon submitting object data to the controllers. To satisfy
this requirement assume spin.js
has been chosen as the spinner library.
In the past, when I was more of a newbie, I would have gone to each view page that called for a spinner and would have written something as follows...
The wrong way
app/views/users/create.html.erb
<script type='text/javascript'>
$(document).ready(function() {
var opts = {
lines: 13, // The number of lines to draw
length: 7, // The length of each line
width: 4, // The line thickness
radius: 10, // The radius of the inner circle
corners: 1, // Corner roundness (0..1)
rotate: 0, // The rotation offset
color: '#000', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: 'auto', // Top position relative to parent in px
left: 'auto' // Left position relative to parent in px
};
$('input#submit-button').click(function() {
var target = document.getElementById('my-spinner');
var spinner = new Spinner(opts).spin(target);
});
});
</script>
app/views/posts/create.html.erb
<script type='text/javascript'>
$(document).ready(function() {
var opts = {
lines: 13, // The number of lines to draw
length: 7, // The length of each line
width: 4, // The line thickness
radius: 10, // The radius of the inner circle
corners: 1, // Corner roundness (0..1)
rotate: 0, // The rotation offset
color: '#000', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: 'auto', // Top position relative to parent in px
left: 'auto' // Left position relative to parent in px
};
$('input#submit-button').click(function() {
var target = document.getElementById('my-other-spinner');
var spinner = new Spinner(opts).spin(target);
});
});
</script>
To make this better I use JavaScript objects and dependency injection to reuse this spinner whenever I need it (DRY right?).
The right way
First we'll make a base object in JavaScript that will provide a nice application specific object for our application:
app/assets/javascripts/my_app_base.js
// This is just an object so we have a place to put things
// and only add a single global.
window.MyApp = {};
Now we can make our reuseable spinner object:
app/assets/javascripts/my_app_spinner.js
MyApp.spinner = function(target, event_target, evt){
var spin_it = function(target) {
var opts = {
lines: 13, // The number of lines to draw
length: 7, // The length of each line
width: 4, // The line thickness
radius: 10, // The radius of the inner circle
corners: 1, // Corner roundness (0..1)
rotate: 0, // The rotation offset
color: '#000', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: 'auto', // Top position relative to parent in px
left: 'auto' // Left position relative to parent in px
};
var spinner = new Spinner(opts);
spinner.spin(target[0]);
}
target = $(target);
event_target = $(event_target);
event_target.on(evt, function(e) {
spin_it(target);
});
}
And now the view pages are a lot less cluttered with repeated JavaScripts:
app/views/users/create.html.erb
$(document).ready( new MyApp.spinner($('#my-spinner'), $('#submit-button'), 'click') );
app/views/posts/create.html.erb
$(document).ready( new MyApp.spinner($('#my-other-spinner'), $('#submit-button'), 'click') );
Things to note
For this particular instance, notice that we can pass any DOM/jQuery element to the spinner object with an event to bind to that object and the spinner object. These dependencies are chosen (or injected) at runtime. This is one element aiding cleaning up the code by removing the hard-coded dependencies.
Linkdump! To the knowledge:
- git-pivotal-hooks - tie your git into your pivotal tracker.
- Broadway got multi-process support - so Broadway is a rendering engine for gtk that outputs to http/canvas. If you've not seen it before, watching this video will be a treat (if you're into awesome things)
- Wacky is a Rails engine for painlessly embedding a wiki in your rails app. It's very basic. We developed it to be our internal wiki, and we think it would be a good idea for you to use it too :)
- SVG Path Morphing - this is one of the neatest things I've ever seen done with SVG :)
- pelusa - lint your Ruby code to improve your OOP skills.
- 3d printed quadcopter frame - I just got a makerbot replicator2 yesterday, so this is on my list of things to print now :)
- Revisiting JavaScript Objects - an article with lots of nice things you can do in javascript in most deployed browsers today, that you likely aren't taking advantage of (I wasn't, and I write a lot of javascript)
- Obvious is a clean architecture framework. I've not yet built an app with this framework, but I desparately want to. I read through one.
- helium is "a tool for discovering unused CSS across many pages on a web site."
- visual event - see javascript events that have been attached to DOM elements
- On Antifragility in Systems and Organizational Architecture - I've not yet read Antifragile but this post made me really badly want to. Good.
- Awesome table built with pipe
- Rails devops cheatsheat
- Asset Pipeline Internals
- form follows function this is amazing. CSS experiments gone right.
- Android Mini PC RK3066
- The language of languages and hn discussion
- Notes on Distributed Systems for Young Bloods
- The Deep Synergy Between Testability and Good Design
- spanish inquisition - simple ruby survey DSL
- 360 degree panoramic video from the stratosphere - You Will Not Be Disappointed.
It's that time again! LINKS!
- Netzke - client server components with Sencha, ext.js, and Ruby
- Ratchet - prototype iphone app designs with html/css easily
- Ruboto - build and package android applications with ruby
- Android Scripting Layer
- wizardwerdna reworked Avdi's Objects On Rails, but in Bob Martin's Clean Architecture - This is probably the most awesome link in this linkdump. Still unsure when it's proper / useful to actually build an app out this way from the start. I guess if you're used to it, then the cost isn't too high.
- Moonbase - Graphical programming sort of - very nice js project
- Awesome dribbble #1
- Awesome dribbble #2
- PEG.js - Need to build a parser in javascript using Parsing Expression Grammar formalism? This is your boy.
- Respond.js - A fast & lightweight polyfill for min/max-width CSS3 Media Queries (for IE 6-8, and more)
- ql.io - A DECLARATIVE, DATA-RETRIEVAL AND AGGREGATION GATEWAY FOR QUICKLY CONSUMING HTTP APIS
- acorn - A small, fast, JavaScript-based JavaScript parser
- jsfsm - A JavaScript finite state machine
- FastBook - because HTML5 is fast enough dangit
- How to read macros
- analytics.js - The hassle-free way to integrate analytics into any web application
- MathBox.js - Make presentation-quality math diagrams in WebGL
So I haven't done a linkdump in a really long time. I got sick and then I got lazy. I've overcome the laziness for today. Hurray.
- Interesting Giles Bowkett article on DCI in Rails, and avoiding God Objects generally. - I'm digesting this at present.
- Chris Coyier has some pretty awesome slides on a modern web development workflow.
- Just got a raspberry pi and a 3d printer? CASE! - I'll be honest, this link's a bit me showboating, because this is totally the first thing I'm printing :)
- Tony Arciere made a ruby binding for the NaCl crypto library - not to be confused with Chrome's NaCl - bet they get sick of saying that.
- LinkedIn diffs pngs for quality assurance in their continuous integration environment for their mobile apps. - Come on, that's some hardcore acceptance testing.
- Sikuli can be used to automate GUIs - script in python
- Keynote is a presenter gem for rails that is not using the Decorator pattern. This sits well with my soul.
- better_errors for rails - a gem that makes error pages in development mode amazing. Get an in-line repl any time you get a 500. In the browser.
- The TTY demystified is an old post from Linux Akesson but it's always fun to give it a re-read. If you've not read it and you like UNIX, it will make you happy.
- A Hexagonal Refactoring of a controller - I'm trying to get comfortable with the Hexagonal Rails approach, and this was a useful article I came across a month or so ago.
Finally, David Chapman wished us all a Merry Circuitsmas as he was wrapping up his circuits class this semester :)

If you're getting something like undefined local variable or method
'new_user_session_path' after adding mount Refinery::Core::Engine, :at =>
'/' to config/routes.rb you can use the following fix:
config/initializers/devise.rb
Devise.setup do |config|
config.router_name = :main_app
end
Devise will now know to use the main_app routes. End of line.
How to remove the purple from your grub menu, and how to get rid of that purple boot screen on Ubuntu:
Step 1:
sudo vim /lib/plymouth/themes/default.grub
# Change your background to black and white will this
if background_color 0,0,0; then
clear
fi
Step 2:
sudo vim /etc/default/grub
:%s/quiet splash//g
Note: This changes GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" to
GRUB_CMDLINE_LINUX_DEFAULT=""
That is all :-)
> Team.where(:name => "Testing").where(:conference_id => 1).first
=> nil
> Team.where(:name => "Testing").where(:conference_id => 1).first_or_initialize
=> #<Team id: nil, name: "Testing", conference_id: 1, created_at: nil, updated_at: nil, logo_image_uid: nil, short_name: nil>
> _.persisted?
=> false
> Team.where(:name => "Testing").where(:conference_id => 1).first_or_create
=> #<Team id: 128, name: "Testing", conference_id: 1, created_at: "2012-10-18 20:26:36", updated_at: "2012-10-18 20:26:36", logo_image_uid: nil, short_name: nil>
> _.persisted?
=> true
I didn't realize this was this elegant in rails, and just stumbled upon it. Maybe you didn't know either?
Enjoy.
@ryanbigg asked me how I set up deployment, figured I'd post this internal guide we've been using.
Derived from this link
The main difference is that this configuration supports rails 3.2.
To set up a server, do the following as root:
echo '--- Install system packages ---'
apt-get update
Let that finish. Then:
apt-get upgrade -y
Let that finish. Then:
apt-get install build-essential ruby-full libmagickcore-dev imagemagick libxml2-dev libxslt1-dev git-core postgresql postgresql-client postgresql-server-dev-8.4 nginx curl node
Let that finish. Then:
apt-get build-dep ruby1.9.1
Install node so you can do asset precompilation later:
sudo apt-get install python-software-properties && sudo add-apt-repository ppa:chris-lea/node.js && sudo apt-get update && sudo apt-get install nodejs nodejs-dev
Then make a deployer user:
useradd -m -g staff -s /bin/bash deployer
passwd deployer
Then fill in the deployer password.
Add this to /etc/sudoers:
%staff ALL=(ALL) ALL
Install system-wide rvm (as root)
bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
Install ruby
source /etc/profile.d/rvm.sh
rvm install 1.9.2
rvm use 1.9.2@projectname --create
gem install bundler
As your deployer user, add this to your ~/.rvmrc
rvm_trust_rvmrcs_flag=1
Add the following to /etc/environment to always run commands in production on this server.
RAILS_ENV=production
Setup postgres database:
sudo -u postgres createdb projectname_production
sudo -u postgres psql
Then execute the following SQL (use your own password):
CREATE USER projectname_production WITH PASSWORD 'isotope_bang';
GRANT ALL PRIVILEGES ON DATABASE projectname_production TO projectname_production;
Make sure this user can actually log in (by default, only local-system users can). Modify /etc/postgresql/9.1/main/pg_hba.conf and change out the line requiring local users to use ident to md5.
local all all md5
Restart postgres
/etc/init.d/postgresql restart
Then set up nginx:
# /etc/nginx/sites-available/default
upstream projectname.com {
# fail_timeout=0 means we always retry an upstream even if it failed
# to return a good HTTP response (in case the Unicorn master nukes a
# single worker for timing out).
# for UNIX domain socket setups:
server unix:/tmp/projectname.com.socket fail_timeout=0;
}
server {
# if you're running multiple servers, instead of "default" you should
# put your main domain name here
listen 80 default;
# you could put a list of other domain names this application answers
server_name projectname.com;
root /home/deployer/apps/projectname.com/current/public;
access_log /var/log/nginx/projectname.com_access.log;
rewrite_log on;
location / {
#all requests are sent to the UNIX socket
proxy_pass http://projectname.com;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}
# if the request is for a static resource, nginx should serve it directly
# and add a far future expires header to it, making the browser
# cache the resource and navigate faster over the website
# this probably needs some work with Rails 3.1's asset pipe_line
location ~ ^/(images|javascripts|stylesheets|system|assets)/ {
root /home/deployer/apps/projectname.com/current/public;
expires max;
break;
}
}
And then:
# /etc/nginx/nginx.conf
user deployer staff;
# Change this depending on your hardware
worker_processes 4;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
types_hash_bucket_size 512;
types_hash_max_size 2048;
sendfile on;
tcp_nopush on;
tcp_nodelay off;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
# gzip_vary on;
gzip_proxied any;
gzip_min_length 500;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Now start nginx (won't work till unicorn's up but go ahead)
/etc/init.d/nginx start
Add unicorn to your project's Gemfile:
# Gemfile
gem "unicorn"
group :development do
gem "capistrano"
end
Add unicorn config to your project:
# config/unicorn.rb
# Set environment to development unless something else is specified
env = ENV["RAILS_ENV"] || "development"
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
worker_processes 4
# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/tmp/my_site.socket", backlog: 64
# Preload our app for more speed
preload_app true
# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30
pid "/tmp/unicorn.my_site.pid"
# Production specific settings
if env == "production"
# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/home/deployer/apps/my_site/current"
# feel free to point this anywhere accessible on the filesystem
user 'deployer', 'staff'
shared_path = "/home/deployer/apps/my_site/shared"
stderr_path "#{shared_path}/log/unicorn.stderr.log"
stdout_path "#{shared_path}/log/unicorn.stdout.log"
end
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
# Before forking, kill the master process that belongs to the .oldbin PID.
# This enables 0 downtime deploys.
old_pid = "/tmp/unicorn.my_site.pid.oldbin"
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
end
after_fork do |server, worker|
# the following is *required* for Rails + "preload_app true",
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,
# and Redis. TokyoCabinet file handles are safe to reuse
# between any number of forked children (assuming your kernel
# correctly implements pread()/pwrite() system calls)
end
Capify the project:
capify .
Replace config/deploy.rb with the following:
# config/deploy.rb
require "bundler/capistrano"
set :application, "projectname.com"
set :scm, :git
set :repository, "git@github.com:isotope11/projectname.com.git"
set :branch, "origin/master"
set :migrate_target, :current
set :ssh_options, { forward_agent: true }
set :rails_env, "production"
set :deploy_to, "/home/deployer/apps/projectname.com"
set :normalize_asset_timestamps, false
set :user, "deployer"
set :group, "staff"
set :use_sudo, false
role :web, "192.168.1.154"
role :app, "192.168.1.154"
role :db, "192.168.1.154", primary: true
set(:latest_release) { fetch(:current_path) }
set(:release_path) { fetch(:current_path) }
set(:current_release) { fetch(:current_path) }
set(:current_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip }
set(:latest_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip }
set(:previous_revision) { capture("cd #{current_path}; git rev-parse --short HEAD@{1}").strip }
default_environment["RAILS_ENV"] = 'production'
# Use our ruby-1.9.2-p318@my_site gemset
default_environment["PATH"] = "--"
default_environment["GEM_HOME"] = "--"
default_environment["GEM_PATH"] = "--"
default_environment["RUBY_VERSION"] = "ruby-1.9.2-p318"
default_run_options[:shell] = 'bash'
namespace :deploy do
desc "Deploy your application"
task :default do
update
restart
end
desc "Setup your git-based deployment app"
task :setup, except: { no_release: true } do
dirs = [deploy_to, shared_path]
dirs += shared_children.map { |d| File.join(shared_path, d) }
run "#{try_sudo} mkdir -p #{dirs.join(' ')} && #{try_sudo} chmod g+w #{dirs.join(' ')}"
run "git clone #{repository} #{current_path}"
end
task :cold do
update
migrate
end
task :update do
transaction do
update_code
end
end
desc "Update the deployed code."
task :update_code, except: { no_release: true } do
run "cd #{current_path}; git fetch origin; git reset --hard #{branch}"
finalize_update
end
desc "Update the database (overwritten to avoid symlink)"
task :migrations do
transaction do
update_code
end
migrate
restart
end
task :finalize_update, except: { no_release: true } do
run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
# mkdir -p is making sure that the directories are there for some SCM's that don't
# save empty folders
run <<-CMD
rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids &&
mkdir -p #{latest_release}/public &&
mkdir -p #{latest_release}/tmp &&
ln -s #{shared_path}/log #{latest_release}/log &&
ln -s #{shared_path}/system #{latest_release}/public/system &&
ln -s #{shared_path}/pids #{latest_release}/tmp/pids &&
ln -sf #{shared_path}/config/database.yml #{latest_release}/config/database.yml
CMD
#precompile the assets
run "cd #{latest_release}; bundle exec rake assets:precompile"
if fetch(:normalize_asset_timestamps, true)
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", env: { "TZ" => "UTC" }
end
end
desc "Zero-downtime restart of Unicorn"
task :restart, except: { no_release: true } do
run "kill -s USR2 `cat /tmp/unicorn.my_site.pid`"
end
desc "Start unicorn"
task :start, except: { no_release: true } do
run "cd #{current_path} ; bundle exec unicorn_rails -c config/unicorn.rb -D"
end
desc "Stop unicorn"
task :stop, except: { no_release: true } do
run "kill -s QUIT `cat /tmp/unicorn.my_site.pid`"
end
namespace :rollback do
desc "Moves the repo back to the previous version of HEAD"
task :repo, except: { no_release: true } do
set :branch, "HEAD@{1}"
deploy.default
end
desc "Rewrite reflog so HEAD@{1} will continue to point to at the next previous release."
task :cleanup, except: { no_release: true } do
run "cd #{current_path}; git reflog delete --rewrite HEAD@{1}; git reflog delete --rewrite HEAD@{1}"
end
desc "Rolls back to the previously deployed version."
task :default do
rollback.repo
rollback.cleanup
end
end
end
def run_rake(cmd)
run "cd #{current_path}; #{rake} #{cmd}"
end
Now there is one little thing you'll need to do. I like to run my apps, even on
the server, to use their own gemset. This keeps everything clean and isolated.
Login to the deployer account and create your gemset. Next run rvm info and fill
the PATH, GEM_HOME and GEM_PATH variables accordingly.
Place the database configuration on the server in the shared directory.
# /home/deployer/apps/projectname.com/shared/config/database.yml
production:
adapter: postgresql
encoding: unicode
database: projectname_production
pool: 5
username: projectname_production
password: password
Modify the server to support password-based SSH logins by editing
/etc/ssh/sshd_config and setting:
PasswordAuthentication yes
Then restart ssh:
/etc/init.d/ssh restart
Set up a deploy key for the deployer user on your git repository. As deployer run the following:
ssh-keygen
Then:
cat ~/.ssh/id_rsa.pub
Push that as a deploy key to the project on github.
SSH into github as deployer from the server, to let this box validate that the host is valid to connect to:
ssh git@github.com
Then run a capistrano setup for the project:
cap deploy:setup
You also might have to go onto the server and, as deployer, run:
mkdir ~/apps/projectname.com/shared/pids
Deployments
Whenever you have a new feature developed in a feature branch, this is the process of deploying it:
Merge feature_branch into master
Run your tests to make sure everything is dandy.
Push master
Run `cap deploy`
This post isn't really programming related, but it will save a lot of people some grief, and it is computer related. If you've had any problems with YouTube videos looking like the image below, then there are a couple of things you can do to fix this.

First you can simply sign up for the YouTube HTML5 Video Player, but this doesn't solve the problem for the videos that fall back to the Adobe Flash player when they lack compatibility for the new HTML5 player.
To fix the videos that play in flash, right click on a YouTube video you're currently watching and go to "Settings...". Next you'll want to un-check "Enable hardware acceleration"

Reload the page, and vuala!

So on a project we were working on, we had something that looked / acted an
awful lot like a normal ActiveRecord association. However, it was a custom
method we wrote that returned an ActiveRecord::Relation. This worked
fantastically, for everything except for a moderately deep query involving this
'relation.'
The code for the relation looked like this:
# app/models/person.rb
class Person < ActiveRecord::Base
has_many :person_school_links
def person_school_links(status = :status_active)
PersonSchoolLink.where(person_id: self.id).send(status)
end
end
This let us do @person.person_school_links.more_chained_methods just fine,
without causing any grief. We got a few months into this project without
trouble.
Then, we were building a report that went across this 'relation.' The report had a query that looked like:
RewardDelivery.includes(to: [ :person_school_links ]).where(to: { person_school_links: { school_id: 1 } })
This query worked fine if there were no matching RewardDeliveries. However, any time it had actual data to return, Rails would throw
undefined method `target' for #<ActiveRecord::Relation:0x000000079e0fa0>
It took quite a bit of digging into the Rails stack to figure out what was
happening, but essentially the methods generated by a has_many, etc.
relationship in ActiveRecord::Base are not returning ActiveRecord::Relations,
but rather something derived from ActiveRecord::Reflection::MacroReflection.
Ultimately, when the ActiveRecord::Relation from the above query was coerced
into something Array-like, Rails looks for a #target method on the
reflection. Since we were returning an ActiveRecord::Relation, and since
those don't actually have a #target method defined, it was blowing up.
To get around this and still get our preferred behaviour, I ended up writing a
class I called MacroReflectionRelationFacade. I essentially use
SimpleDelegator to wrap the ActiveRecord::Relation and add an acceptable
#target method to it. Code follows, thought the story was interesting:
# lib/macro_reflection_relation_facade.rb
class MacroReflectionRelationFacade < SimpleDelegator
def target
self
end
end
# app/models/person.rb
require 'macro_reflection_relation_facade'
class Person < ActiveRecord::Base
has_many :person_school_links
# Relationships
def person_school_links(status = :status_active)
MacroReflectionRelationFacade.new(PersonSchoolLink.where(person_id: self.id).send(status))
end
end
I'm unsure if this is a perfect solution, but it works perfectly for all my purposes. Enjoy :)