MatthewRudyOnRails

My name is MatthewRudy, and I'm a Rails Developer. I am.... MATTHEW RUDY ON RAILS!!!


rake rails:freeze:edge, awk, xargs, and git rm

So, I had a project.
I did

rake rails:freeze:edge

But ended up with a million files that had been deleted.

How do you "git rm" hundreds of files which have been deleted already?

Sadly git add vendor/rails/*../* doesn't work.

So here's a bit of awk.
git status | awk '$3 ~ /vendor/ && $2 ~ /deleted/ {print $3}' | xargs git rm

BOOM!

Whoops! Rails security flaw.

Yeah, I accidentally messed up my whole website.

All because I did a bit of stupid HTML.

...
<%= text_field :post, :name %>
...

And the controller didn't save me from this hell:
def create
@post = @user.posts.create(params[:post])
end

If only I'd made the model safe...
class Post < ActiveRecord::Base
def name=(value)
self.connection.execute("DROP DATABASE my_production_db")
self[:name] = value
end
end

As you can see,
I messed up bad,
should never have let that :name param get past the controller.

My solution...
def create
safe_params = params[:post].except(:name)
@post = @user.posts.create(safe_params)
end

Lucky I spotted this before we went live!

Silly combinations thing

Yeah,
someone asked "how can I get all the combinations of an array?" on Rails-Talk, and I was bored at work, so I played around.

Here's what I got;


def combinations(array)
return [] if array == []
return [array, []] if array.length == 1

# split [1,2,3] => [1], [2,3]
left, right = array[0..0], array[1..-1]
rtn = []

combinations(right).each do |r_array|
rtn << r_array
rtn << left + r_array
end

return rtn
end

>> combinations([1])
=> [[1], []]
>> combinations([1,2])
=> [[2], [1, 2], [], [1]]
>> combinations([1,2,3])
=> [[3], [1, 3], [2, 3], [1, 2, 3], [], [1], [2], [1, 2]]

Perhaps it could be re-written to order them more nicely.
(I like the ordering: [], [1], [2], [3], [1,2], [1,3] ... but who can really complain)

Select which columns to "reload" with ActiveRecord

There was an interesting ticket on the Rails Lighthouse today.

Regarding;

How can I just reload a couple of columns for a given record?

The suggested solution was a new method for each attribute


record.reload_name

or

record.reload(:name)

or

record.name!



however,
we can already do this.



record.reload(:select => "name")

or

record.reload(:select => "name, other_column")

or even

record.reload(:select => "COUNT(SELECT * FROM other_table WHERE foreign_key = this_table.id) AS other_count")
record.other_count == "12"



This is all down to the nice way that .reload(options) works.

Basically coming down to this;



def reload(options=nil)
new_model = self.class.find(self.id, options)
self.attributes.update(new_model.attributes)
return self
end


(see the real code over at github

So it only actually updates the attributes we end up reloading.
nice!

http_authentication plugin broken in Rails 2

the rails http_authentication plugin seems to be broken,
http://github.com/rails/http_authentication/tree/master

http://github.com/rails/http_authentication/tree/master/lib/http_authentication/basic.rb


Problem is that the included controller method :request_http_basic_authentication passes on the controller as an object,
which in turn is given the :render command externally.
This breaks because :render is now a protected method.


require 'base64'

module HttpAuthentication
module Basic
extend self

module ControllerMethods
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
end

def authenticate_with_http_basic(&login_procedure)
HttpAuthentication::Basic.authenticate(self, &login_procedure)
end

def request_http_basic_authentication(realm = "Application")
HttpAuthentication::Basic.authentication_request(self, realm)
end
end

def authenticate(controller, &login_procedure)
if authorization(controller.request)
login_procedure.call(*user_name_and_password(controller.request))
else
false
end
end

def user_name_and_password(request)
decode_credentials(request).split(/:/, 2)
end

def authorization(request)
request.env['HTTP_AUTHORIZATION'] ||
request.env['X-HTTP_AUTHORIZATION'] ||
request.env['X_HTTP_AUTHORIZATION']
end

def decode_credentials(request)
Base64.decode64(authorization(request).split.last)
end

def encode_credentials(user_name, password)
"Basic #{Base64.encode64("#{user_name}:#{password}")}"
end

def authentication_request(controller, realm)
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
controller.render :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
return false
end
end
end

pastie

The solution is to break the :authentication_request method inside the controller.


module ControllerMethods
def request_http_basic_authentication(realm = "Application")
#HttpAuthentication::Basic.authentication_request(self, realm)
authentication_request(realm)
end

def authentication_request(realm)
headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
render :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
return false
end
end


BOOM

UPDATE:
I changed the way I did this.

controller.send(:render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized)


It's now forked and fixed on GitHub

Cleaning Up Your Models Folder

I love really complex applications.
It feels safe and warm knowing that your code relies on 20 database tables, and maybe some of those have 5 inherited types.

So you end up with a models directory that looks like this;

app/
models/
carnivore.rb
carrot.rb
cauliflower.rb
meat.rb
person.rb
sausage.rb
smelly_sausage.rb
steak.rb
vegetarian.rb
vegetarian_sausage.rb
vegetable.rb

PROPER MESSY! (and that's only from 3 tables - meats, vegetables, people)
So we want to impose a structure on it,
we want;
app/
models/
meat/
sausages/
sausage.rb
smelly_sausage.rb
vegetarian_sausage.rb

meat.rb
steak.rb

people/
carnivore.rb
person.rb
vegetarian.rb

vegetables/
carrot.rb
cauliflower.rb
vegetable.rb

Without doing any work you could make this happen;
Rails expects the model People::Person to live in /people/person.rb,
so if you want to write "People::Person.find(...." every time, then feel free to do so.

The quick, and clean solution is just to add the following lines to environment.rb.
Rails::Initializer.run do |config|
%W( meat meat/sausages people vegetables ).each do |extra_path|
config.load_paths << "#{RAILS_ROOT}/app/models/#{extra_path}"
end
end

And, actually,
that's all it takes!!!

Easy-peasy, SCORE!!!!

You can do whatever you want to tidy up yours tests and fixtures, you just have to add a couple of "/.." to the top of any tests you've moved
require File.dirname(__FILE__) + '/../test_helper'
for anything in /test/unit,
require File.dirname(__FILE__) + '/../../test_helper'
for anything in /test/unit/stuff,
and so on....

Custom Rake Tasks

While I was doing my previous task, I think I worked out Rake pretty well.

The key points are these;
namespaces:


namespace :first do
namespace :second do
namespace :third do
task :do do
puts "stuff"
end
end
end
end

This will create a task named "first:second:third:do" which will just output the word "stuff".

prerequisites:

task :one => [:two, :three] do
puts "one"
end

task :two => :three do
puts "two"
end

task :three do
puts "three"
end

If you run "rake one" it'll ensure that "two" and "three" have both been executed already.