Posted by Chuck Vose
Wed, 29 Jun 2011 17:46:00 GMT
If you’re like me you inherit a lot of projects which have very different styles. Today I ran into a project that had never used attr_accessible and I found a total gotcha:
- If there are no attr_accessible attributes on a model it’s open, any attribute can be written with .update_attributes() or a similar mass-assignment function.
- If there is even one attr_accessible the model is closed except for those attributes defined to be attr_accessible.
So there is an implicit toggle involved in attr_accessible as well as the actual desired functionality of allowing an attribute to be mass-assigned. I’d never worked on a project that wasn’t using attr_accessible (count my lucky stars it seems) so i’d never seen this problem before.
Also, try not to confuse attr_accessor and attr_accessible. The latter is a rails security function, the former just defines an instance variable. Mass assignment, or assignment of any kind won’t really work on the former from within rails.
Posted in Rails | Tags attr_accessible, Rails
Posted by Chuck Vose
Tue, 31 Aug 2010 17:48:00 GMT
I’ve now been working professionally in Drupal for a year and have learned a lot about it; I have some patches into contrib but I’ve not really done much with core other than some simpletests I was too shy to commit at Drupalcon and some comments trying to help people out on d.o. Prior to my stint as Drupal programmer I was a hardcore Ruby on Rails developer for about 4 years. Over the last two years in particular I’ve learned a lot about frameworks and I’d like to share an observation about which framework feels right to me for which situations and why.
Administrators vs End-users
At the heart of my observation is this question: Who is putting content into your database and what percentage of the database are they creating? I think it’s fair to assume that there is a database but for those that are in the NoSQL camp and might be using multiple types of database lets just all agree to talk about some mythical database which content comes from.
- At least 80% of my client’s content comes primarily from their staff members and is consumed by the faceless masses on the Internet
- At least 80% of my client’s content comes primarily from logged in users via uploads, comments, content creation, etc.
Nobody is going to argue that making the admin interface of a website easy to use is critical for the first case. If you have people spending their entire days working on the back side of your website it had better be something awesome or they’re all going to stalk you and break your flowers just below the point where they can regrow. Unacceptable of course. Similarly, when it comes right down to it, for me of course, creating a slick admin interface to something like stackoverflow.com would be completely pointless. Probably someone uses it, but the interface needs to be sitting in the front for the users.
As it turns out, this seems to be a great dividing line between Ruby on Rails and Drupal in my mind. I believe that Drupal has one of the greatest admin interfaces of any CMS. Failing that, I don’t think that I’ll get many complaints if I say that it is easy to cajole the Drupal admin into a super usable state if you know what you’re doing (or you just used Drupal 7 to begin with). Drupal manages content exceedingly well, but it is extremely hard to build applications like todo lists in the thing. I’m taking todo lists because they’re an easy concept that doesn’t really require an admin at all.
Ruby on Rails on the other hand, does not come with an admin interface because it is not something that every web application needs. There are some marvelous backend builders for Rails, but it’s not the core competency. Same thing for Sinatra and many of the other Ruby frameworks. What they excel at is being versatile, they excel at doing things that just can’t really be done by the web engines we already understand (Content management and blogging in particular).
What’s crucial to understand here is that for the average website for your average client that just wants to get online, we’ve probably already built an engine that will make them happy. Drupal in particular of course, but Wordpress and Joomla are also excellent at doing the things that everyone else is already doing. A CMS can be done very profitably in Rails, I’ve done it, but it probably would have been much more profitable had I learned Wordpress or Drupal back in the day.
Conclusion / Summary
My point, summarized to one sentence is thus: Do your clients want to be on the Internet, or do your clients want to create some chunk of the Internet. If they want to create awesome content and reap the rewards of it, Drupal is probably your framework of choice. If they want to do something that really doesn’t fit into something that every other website is doing, Ruby on Rails is probably your framework of choice.
The hardest question though, is what do you do when it’s not really so clear cut? When your client wants half of the site to be essentially a perfect candidate for a CMS, but the other half to be a crazy interactive, totally innovative thing? Do you try to use both? Do you strap it all together with Ajax? Or do you try to build the CMS in Ruby and lose out on that time? I don’t know that answer, but I hope that I’ve made you think about your clients and their projects in a slightly different way.
Posted in Rails, Drupal, Programming | Tags admin, data, drupal, model, Rails, rich | 6 comments
Posted by Chuck Vose
Tue, 27 Jan 2009 18:51:00 GMT
Posted for those confused about Rack. Rails doesn’t need Rack right now to integrate with the current webservers so you don’t need to learn about Rack right now. Rack is for frameworks that aren’t Rails but still want to take advantage of the excellent web servers that have come into being lately (read: Phusion Passenger)
I hope you don’t have to troll around the Internet as long as I did just to find this answer :)
Posted in Rails | Tags Integration, Passenger, Rack, Rails, Ruby | 1 comment
Posted by Chuck Vose
Mon, 25 Aug 2008 19:29:00 GMT
When I was at RailsConf2008 I learned a lot about life and Rails but there were some nagging problems that lingered after me for a long time.
At one point in the talks there was a discussion of how to make listing pages have checkboxes and how to handle this data. The problem is complicated by pagination and filtering so it’s actually a really good problem to talk about.
The proposed solution was to send back a structure that looked something like this:
[{:id => 1, :activated => "+", :contact => "-"}, ...]
At this point I was probably playing pogs in the audience with Ryan Schenk so the details are lost to time but the concept remains.
It seems needlessly complex though. A very interesting idea, no doubt about it, and it certainly has its merit but it seems very complex to me when we’re talking about booleans. Also, I feel like it’s worth mentioning that this method is much faster than the below method but somehow that doesn’t turn me off that badly.
Instead of the above I took the already well accepted practice of using the check_box_tag in the view with the addition of a hidden field which would tell me which objects to care about:
<% @users.each do |user| %>
<%= hidden_field_tag('seen[]', user.id) -%>
<%= check_box_tag 'activated[]', user.id -%>
<% end %>
In the controller it’s as easy as loading up params[:seen] and loading each object:
def index
if request.post?
activated_ids = params[:activated].collect {|id| id.to_i} if params[:activated]
seen_ids = params[:seen].collect {|id| id.to_i} if params[:seen]
if activated_ids
seen_ids.each do |id|
r = User.find_by_id(id)
r.activated = activated_ids.include?(id)
r.save
end
end
end
end
So what I don’t get is whether this is wrong on some moral stance or if I really didn’t understand what the presenter was getting at. Yes, he loads and saves fewer objects, which could be important. But he also introduced what seems to me to be a lot of complexity into a fairly simple process.
At any rate, this code is here in case someone needs to find it until I find what the presenter was proposing. I seem to recall being impressed at the time so there must be something I’ve forgotten.
Posted in Ruby, Rails, Programming | Tags boxes, check, check_box, check_box_tag, filter, index, list, multiple, pagination, Rails, Ruby, view | no comments
Posted by Chuck Vose
Thu, 13 Sep 2007 02:40:00 GMT
Rails has a dynamic method where you can do find_or_create_by_attr_name(attr) but it the method names get incredibly long very quickly. So SJS wrote this article in which he tries to remedy the situation. His solution was pretty elegant but it still only worked for fields that were already defined in the database; if you often redefine setter methods it doesn’t work at all.
Here’s my attempt to remedy the situation.
module ActiveRecordExtensions
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def find_or_create(params)
begin
return self.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
attrs = {}
# search for valid attributes in params
self.column_names.map(&:to_sym).each do |attrib|
# skip unknown columns, and the id field
next if params[attrib].nil? || attrib == :id
attrs[attrib] = params[attrib]
end
# call the appropriate ActiveRecord finder method
found = self.send("find_by_#{attrs.keys.join('_and_')}", *attrs.values) if !attrs.empty?
if found && !found.nil?
return found
else
return self.create(params)
end
end
end
alias create_or_find find_or_create
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtensions)
Newb instructions
Create a file called active_record_extensions.rb in your the lib directory. Then add `require ‘active_record_extensions’` to your environment.rb at the bottom (without the ``). Restart your server and see what happens!
Posted in Rails, Programming | Tags ActiveRecord, attribute, based, create, dynamic, find, find_or_create, finders, Rails
Posted by Chuck Vose
Wed, 12 Sep 2007 03:42:00 GMT
Sam Ruby occasionally does an article about his long bets and I really respect his ability to predict the future. This time I feel that he was focusing too close and forgot the long part of the ‘long bet’. REST is already important, and there are further developments in edge rails that make it even more important (active resource). MySQL is already a pain for scaling and the plugins are starting to come out but haven’t really hit the fan yet.
Bet One – MySQL becomes the exception
First, I think Sam Ruby is dead on about databases. Right now there are only a few things that are a consistent pain for deployment and scaling and databases are the top of the heap. I know there are things like MaxDB for mysql and there are ways to set up ring replication but it’s hard; something that Ruby’s community is really good at solving.
My first bet is that MySQL dies out for the rails community and something new pops in. It wouldn’t surprise me if it was Mnesia from Erlang/OTP or something involving Hadoop and HBase. It would surprise me if CouchDB popped onto the scene in a big way but it’s the sort of thinking that could lead us somewhere interesting. The other option is that someone comes up with a really concrete stack of abstractions that makes it easy to balance mysql requests and writes.
Bet Two – Rails drops REST completely
Secondly, I think REST is the wrong way to move forward. REST maps very well onto the CRUD principles, but I feel like we very rarely actually use just CRUD. More often than not I want to run custom little things and create crazy associations. And I realize that this is all possible in the REST model, but it makes the controllers obscene sometimes.
What we really want is Query Language for the Internet and what better language to build that in than Ruby. I’ve seen DSL’s for direct database access and it seems like the routes would be just around the corner if this is where we decide to take it.
The question is whether DHH sees the writing on the wall or desperately wants to hang on to REST. Since he’s put so much effort into the REST idea it seems like he would be loath to drop it, but at the same time he’s an incredibly mature developer and would hopefully handle a change like this if it ever happened.
Conclusion
All respect to Sam Ruby, I really do respect his predictions over my own. But I think his predictions are too close to us right now. I would like to know what happens after REST and what happens in the database arena.
Posted in Rails, MySQL, Programming | Tags Bets, CouchDB, Erlang, Future, Hadoop, HBase, Mnesia, MySQL, Rails, REST, Ruby, Scaling
Posted by Chuck Vose
Mon, 13 Aug 2007 21:05:00 GMT
Every once in a while I find myself in the situation where I need to override a model or create an inheritance without using single table inheritance (STI).
The method_missing call can help us to create an intelligent router that will route calls to the parent class or the child while maintaining an extremely small code footprint.
Here’s an example of what I’m talking about. We needed a landing page that would be dynamic for each user that wanted one. But we needed a template landing page which just needed the values substituted in at the right spot. Here’s a quick Yaml of what the table might look like.
# Example Page
landing_page_template:
title: Welcome to [COMPANY-NAME]
meta_keywords: [COMPANY-NAME], [COMPANY-META-KEYWORDS]
...
# Example Landing Page
landing_page:
company_name: CV, Inc.
company_logo: www.chuckvose.com/fake_logo.gif
company_meta_keywords: happy, rails, method_missing, inheritance
...
So here’s the hope, when we load up the LandingPage object and call LandingPage#title it should display “Welcome to CV, Inc.”
Pseudo-code and implementation
def method_missing(method_name, args*)
if method_name is in the extended database
call method_name
if method_name isn't in the extended database
try calling parent.method_name
return results directly or mangle them somehow
We chose to mangle the results but if you just wanted to add a couple columns you could. You could run into performance issues doing this instead of STI. However this is an absolutely ideal place to mangle the code too if you want. Probably as good as a controller after_filter.
Two steps needed to happen for this, we need to load up the template on initialization, then use method_missing to switch between calls to the LandingPage class (such as :company_name) and the Page class (such as title).
Step 1: Load the template
The first part is a little weird because of rails performance. If you haven’t used after_find/after_initialize rails-core had to put in a little kludge for performance reasons. The solution is just to define a blank function named after_find or after_initialize.
class LandingPage < ActiveRecord::Base
after_initialize :find_template
# Required to activate the after_initialize filter
def after_initialize; end
private
def find_template
@template = Page.find_by_url("landing_page_template")
end
end
Step 2: Create the method_missing switcher
The method_missing switcher had to incorporate two things and one trick. First, it needed to look in the LandingPage table for its own attributes. Then it needed to look in the Page table for anything else before passing it through a regex engine and returning. The last little trick was that the regex engine needed to only be run on Strings, trying to regex a boolean doesn’t work very well. This allows the plethora of query functions such as Page.valid?.
class LandingPage < ActiveRecord::Base
private
def method_missing(meth, *args)
# See if this attribute is in this object already.
# If so just call out to super and let
# ActiveRecord::Base deal with trying to find the
# attribute in the database.
if @attributes.include?(meth.to_s) || @attributes.include?(meth.to_s.gsub(/(=|_before_type_cast)/, ""))
super
else
# Call Page#meth and store the response. Usually something like @page.body or @page.title
resp = @template.send(meth)
# If the attribute returned from above is a String, toss it into the regex grinder
# and return the results.
if resp.is_a?(String)
return resp.html_sub(self)
# If the response is a boolean, symbol, or something else, just return it since we
# can't (and probably don't need to) regex it.
else
return resp
end
end
end
end
Step 3: The regex grinder
Security Warning: Before I begin this I want people to know that it should never be used by people outside your direct trust. It calls ruby code directly off of text found in the database. It can be rewritten to be more secure but this wasn’t necessary for us. If you want slightly more security drop some constraints on the LandingPage.send() method. For even more security do one regex per tag to replace (such as html = html.gsub(/\[MERCHANT-NAME\]/, template.merchant_name || ””). Security Warning
The last step is totally up to you. We needed it to find things like [MERCHANT-NAME] and replace them with LandingPage#merchant_name. This can be done fairly simply monkey-patching String with a blocked gsub.
class String
# Look for snippets like [MERCHANT-NAME] and replace
# them with LandingPage#merchant_name
def html_sub(landing_page)
html = self # self cannot and should not be modified directly.
# Look for [WHATEVER] tags
html = html.gsub(/\[[^\]]+?\]/) do |match|
# For each match, call the LandingPage instance method
# with the same name and return those results or the empty
# string.
landing_page.send(match.gsub(/[\[\]]/, "").gsub(/-/, "_").downcase) || ""
end
end
end
Conclusion
The method_missing call seems to be a rough spot for a lot of discussions. Why_ wrote an article (The Best of Method Missing) that shows this pretty well: he starts by explaining how he has hated every piece of code he’s written with it then proceeds to point out some method_missing code that he loves. Lots of people feel this way about it and about many aspects of Rails in general.
But sometimes it’s a wonderful thing. This example should have taken several hundred lines of code and lots of kludges and bugs, but method missing trimmed it to ~20 LOC and we haven’t been able to find a bug yet.
Hope this helps you with some project, I’ve enjoyed playing with it and writing it up.
Posted in Rails | Tags blocks, inheritance, method_missing, monkey, patching, Rails, regex, sti
Posted by Chuck Vose
Tue, 07 Aug 2007 16:24:00 GMT
Errors you might get at this step:
Can't locate nginx.pm in @INC
Can't locate loadable object for module nginx in @INC
This is how I installed the http_perl_module for Nginx. I'm not sure why these things are the way they are, I'm following a mailing list that was written in russian. :)
Installing from ~/src/nginx-0.6.5
Installing to /opt/local/nginx (if this isn't owned by you use append sudo to the last step)
cd ~/src/nginx-0.6.5
./configure --with-http_perl_module --with-http_ssl_module --prefix=/opt/local/nginx/ --with-perl_modules_path=/opt/local/nginx/modules/perl
make
make install
sudo /opt/local/nginx/sbin/nginx
Now we hope.
Tags: nginx, perl, rails
Posted in Rails | Tags errors, nginx, perl, Rails
Posted by Chuck Vose
Wed, 02 May 2007 20:50:00 GMT
If you can spot what’s wrong with this code you get a cookie. Apparently rendering with a 302 breaks ie6 entirely. Just deleting the 302 makes it okay.
render :partial => 'admin/restricted', :layout => 'admin', :status => "302" and return false
Whether it makes sense or not is not the question. I’m pretty confident that it doesn’t make sense after thinking about it for a while. But why would it make ie6 connections die while firefox et al are fine with it?
Posted in Rails | Tags 302, cookies, partials, Rails
Posted by Chuck Vose
Wed, 25 Apr 2007 20:21:00 GMT
At On & On Creative we have a lot of sites on a common rails backend. It’s incredibly flexible so we can offer a lot of support to customers but because there are so many customers with different needs testing has become a huge pain. Not only does it let us run tests custom to each client, it provides a way to import the data to the test db really easily and it’s way faster than loading the fixtures each time you start a test. Yay!
After googling around I happened upon this caboo.se article about how to run without fixtures at all. It’s a very indepth article with lots of good things in it; far be it for me to try to compete.
Instead, here’s the low down on how we prepare each of our sites for testing.
Add the following to the end of your rails_root Rakefile:
class Rake::Task
def detract(prerequisite)
@prerequisites.delete(prerequisite)
end
end
%w(units functionals integration recent uncommitted).each do |task|
Rake::Task["test:#{task}"].detract('db:test:prepare')
Rake::Task["test:#{task}"].enhance(['environment'])
end
Add the following to lib/tasks/fixtures.rake. Make sure to look at the two commented lines:
# This code courtesy of the "Rails Recipe's book":http://www.pragmaticprogrammer.com/titles/fr_rr/ I believe.
namespace :db do
namespace :fixtures do
desc 'Create YAML test fixtures from data in an existing database. Defaults to development database. Set RAILS_ENV to override.'
task :dump => :environment do
# Remove the limit if you want to. It's a timesaver mostly
sql = "SELECT * FROM %s LIMIT 0,1000"
# Add tables you don't want dumped.
skip_tables = ["schema_info", "plugin_schema_info", "engine_schema_info", "sessions"]
ActiveRecord::Base.establish_connection(:development)
(ActiveRecord::Base.connection.tables - skip_tables).each do |table_name|
i = "000"
File.open("#{RAILS_ROOT}/test/fixtures/#{table_name}.yml", 'w') do |file|
data = ActiveRecord::Base.connection.select_all(sql % table_name)
file.write data.inject({}) { |hash, record|
hash["#{table_name}_#{i.succ!}"] = record
hash
}.to_yaml
end
end
end
end
end
Then the fun easy stuff happens:
rake db:fixtures:dump RAILS_ENV=production
rake db:fixtures:load RAILS_ENV=test
rake test:plugins PLUGIN=your_plugin
If you want to commit the fixtures to the repo you can but it seems a waste to me since you might be regenerating them often.
Also, check the two comments in the fixtures.rake, it could be doing things you don’t expect.
Posted in Rails, Programming | Tags fixtures, plugins, Rails, rake, testing