alexx's starred items
acts_as_state_machine is a great plugin really useful when you want to add constraints and behavior to your model objects.
Note : Those who don’t know what this plugin is all about should stop reading right there or risk being completely lost.
One thing that seems impossible to do with the plugin is to have variable “initial states” for an object. Say for example that you have a User model that needs to act as a state machine. New users have to fill up a signup form to gain access to the application. These new users will be “pending” until an administrator approve them (enabled) or refuse them (disabled)
-
class User < ActiveRecord::Base
-
acts_as_state_machine :initial => :pending
-
state :pending
-
state :enabled
-
state :disabled
-
-
event :enable do
-
transitions :from => :pending, :to => :enabled
-
transitions :from => :disabled, :to => :enabled
-
end
-
-
event :disable do
-
transitions :from => :pending, :to => :disabled
-
transitions :from => :enabled, :to => :disabled
-
end
-
end
So far so good. But what if an administrator can also create a new user? In this case we would’nt want the user to be “pending”, we want him to be “enabled” right away.
I experimented a similar scenario, so I tried a few things to bypass the initial state :
-
#FAILED ATTEMPT #1
-
def create
-
user = User.new(params[:user])
-
user.state = :enabled
-
user.save
-
end
Too bad, so I tried this :
-
#FAILED ATTEMPT #2
-
def create
-
User.initial_state = :enabled
-
user = User.new(params[:user])
-
user.save
-
end
Of course, the following worked :
-
#SUCCESSFUL BUT SUCKY ATTEMPT
-
def create
-
user = User.new(params[:user])
-
user.save
-
user.enable!
-
end
Yuk… there had to be something better.
Finally, I learned that initial_state was an inheritable attribute. I didn’t know what those were all about so I did some googling. The only good explanation I found is this one.
Anyway, I was able to override the initial state of my user object by doing this :
-
#SUCCESSFUL ATTEMPT!
-
def create
-
User.write_inheritable_attribute :initial_state, :enabled
-
user = User.new(params[:user])
-
user.save
-
end
It’s not pretty I know, but at least it gets the job done. I wonder if the fact that you cannot “easily” change the initial state programmatically is a missing feature that should be added in a future version… or if it’s only my understanding of a “state machine” that sucks. Any thoughts?
Rails 2.1 is now available for general consumption with all the features and fixes we’ve been putting in over the last six months since 2.0. This has been a huge effort by a very wide range of contributors helping to make it happen.
Over the past six months, we’ve had 1,400 contributors creating patches and vetting them. This has resulted in 1,600+ patches. A truly staggering number. And lots of that has made it into this release.
New features
The new major features are:
- Time zones (by Geoff Buesing): Tutorial | Introdction | Railscast
- Dirty tracking: Introduction (partial updates) | Railscast
- Gem Dependencies: Introduction | Railscast
- Named scope (by Nick Kallen): Introduction | Railscast
- UTC-based migrations: Introduction | Railscast
- Better caching: Introduction
Thanks to Ryan Daigle for the feature introductions and Ryan Bates for the Railscasts. It makes writing the release notes so much easier :).
As always, you can install with:
gem install rails
...or you can use the Git tag for 2.1.0.
Enjoy!
I get to see a lot of Ruby code while writing for Ruby Inside. Most is very good, but sometimes we forget some of Ruby's shortcuts and tricks and instead reinvent the wheel. In this post I present 21 different Ruby tricks, from those that most experienced developers already use every day to those that are more obscure. Before writing this piece, for example, I had no idea about trick number 2! Whatever your level, a refresh may help you the next time you encounter similar scenarios.
Note to beginners: If you are still learning Ruby, check out my Beginning Ruby book (18 5-star reviews so far). It’s only $26.39 on Amazon in print or Kindle format or available from the publisher in PDF format.
1 - Extract regular expression matches quickly
A typical way to extract data from text using a regular expression is to use the match method. There is a shortcut, however, that can take the pain out of the process:
email = "Fred Bloggs <fred@bloggs.com>" email.match(/<(.*?)>/)[1] # => “fred@bloggs.com” email[/<(.*?)>/, 1] # => “fred@bloggs.com” email.match(/(x)/)[1] # => NoMethodError [:(] email[/(x)/, 1] # => nil email[/([bcd]).*?([fgh])/, 2] # => “g”
2 - Shortcut for Array#join
It's reasonably common knowledge that Array#*, when supplied with a number, multiplies the size of the array by duplicate elements, but it's a lot lesser known that when supplied with a string Array#* instead does a join!
%w{this is a test} * ", " # => “this, is, a, test” h = { :name => "Fred“, :age => 77 } h.map { |i| i * "=" } * "\n" # => “age=77\nname=Fred”
3 - Format decimal amounts quickly
Formatting floating point numbers into a form used for prices can be done with sprintf or, alternatively, with a formatting interpolation:
money = 9.5 "%.2f" % money # => “9.50″
4 - Surround text quickly
The formatting interpolation technique from #3 comes out again, this time to insert a string inside another:
"[%s]" % "same old drag" # => “[same old drag]“
You can use an array of elements to substitute in too:
x = %w{p hello p} "<%s>%s</%s>" % x # => “<p>hello</p>"
5 - Delete trees of files
Don't resort to using the shell. Ruby comes with a handy file utilities library that can do your bidding:
require 'fileutils' FileUtils.rm_r 'somedir'
Be careful how you use this one!
6 - Exploding enumerables
* can be used to “explode” enumerables (arrays and hashes). We'll let the examples do the talking:
a = %w{a b} b = %w{c d} [a + b] # => [["a", "b", "c", "d"]] [*a + b] # => ["a", "b", "c", "d"]
a = { :name => "Fred“, :age => 93 } [a] # => [{:name => "Fred", :age =>93}] [*a] # => [[:name, "Fred"], [:age, 93]]
a = %w{a b c d e f g h} b = [0, 5, 6] a.values_at(*b).inspect # => ["a", "f", "g"]
7 - Cut down on local variable definitions
Instead of defining a local variable with some initial content (often just an empty hash or array), you can instead define it “on the go” so you can perform operations on it at the same time:
(z ||= []) << 'test'
8 - Using non-strings or symbols as hash keys
It's very rare you see anyone use non-strings or symbols as hash keys. It's totally possible though, and sometimes handy (and, no, this isn't necessarily a great example!):
does = is = { true => 'Yes', false => 'No' } does[10 == 50] # => “No” is[10 > 5] # => “Yes”
9 - Use 'and' and 'or' to group operations for single liners
This is a trick that more confident Ruby developers use to tighten up their code and remove short multi-line if and unless statements:
queue = [] %w{hello x world}.each do |word| queue << word and puts "Added to queue" unless word.length < 2 end puts queue.inspect # Output: # Added to queue # Added to queue # ["hello", "world"]
10 - Do something only if the code is being implicitly run, not required
This is a very common pattern amongst experienced Ruby developers. If you're writing a Ruby script that could be used either as a library OR directly from the command line, you can use this trick to determine whether you're running the script directly or not:
if __FILE__ == $0 # Do something.. run tests, call a method, etc. We're direct. end
11 - Quick mass assignments
Mass assignment is something most Ruby developers learn early on, but it's amazing how little it's used relative to its terseness:
a, b, c, d = 1, 2, 3, 4
It can come in particularly useful for slurping method arguments that have been bundled into an array with *:
def my_method(*args) a, b, c, d = args end
If you want to get really smart (although this is more 'clever' than truly wise):
def initialize(args) args.keys.each { |name| instance_variable_set "@" + name.to_s, args[name] } end
12 - Use ranges instead of complex comparisons for numbers
No more if x > 1000 && x < 2000 nonsense. Instead:
year = 1972 puts case year when 1970..1979: "Seventies" when 1980..1989: "Eighties" when 1990..1999: "Nineties" end
13 - Use enumerations to cut down repetitive code
%w{rubygems daemons eventmachine}.each { |x| require x }
14 - The Ternary Operator
Another trick that's usually learned early on by Ruby developers, but again something I see quite rarely in less experienced developers' code. The ternary operator is not a fix-all, but it can sometimes make things tighter.
puts x == 10 ? "x is ten" : "x is not ten" # Or.. an assignment based on the results of a ternary operation: LOG.sev_threshold = ENVIRONMENT == :development ? Logger::DEBUG : Logger::INFO
15 - Nested Ternary Operators
It may be asking for trouble but ternary operators can be nested within each other (after all, they only return objects, like everything else):
qty = 1 qty == 0 ? 'none' : qty == 1 ? 'one' : 'many' # Just to illustrate, in case of confusion: (qty == 0 ? 'none' : (qty == 1 ? 'one' : 'many'))
16 - Fight redundancy with Ruby's logical provisions
I commonly see methods using this sort of pattern:
def is_odd(x) # Wayyyy too long.. if x % 2 == 0 return false else return true end end
Perhaps we can use a ternary operator to improve things?
def is_odd(x) # Don't EVER put false and true in a ternary operator!! x % 2 == 0 ? false : true end
It's shorter, and I see that pattern a lot but really you should go one step further and just rely on the true / false responses Ruby's comparison operators already give!
def is_odd(x) # Use the logical results provided to you by Ruby already.. x % 2 != 0 end
17 - See the whole of an exception's backtrace
def do_division_by_zero; 5 / 0; end begin do_division_by_zero rescue => exception puts exception.backtrace end
18 - Allow both single items AND arrays to be enumerated against
# [*items] converts a single object into an array with that single object # of converts an array back into, well, an array again [*items].each do |item| # … end
19 - Rescue blocks don't need to be tied to a 'begin'
def x begin # … rescue # … end end
def x # … rescue # … end
20 - Block comments
I tend to see this in more 'old-school' Ruby code. It's surprisingly under-used though, but looks a lot better than a giant row of pound signs in many cases:
puts "x" =begin this is a block comment You can put anything you like here! puts “y” =end puts "z"
21 - Rescue to the rescue
You can use rescue in its single line form to return a value when other things on the line go awry:
h = { :age => 10 } h[:name].downcase # ERROR h[:name].downcase rescue "No name" # => “No name”
If you want to post your own list of Ruby tricks to your blog, send trackback here or leave a comment, and I'll link to all of them in a future post. Alternatively, feel free to post your own Ruby tricks as comments here, or critique or improve on those above.
As an aside, Ruby Inside is exactly two years old today. Thanks for your support! Intriguingly, the first post was another Ruby trick that I forgot to include above, so check that out too.
So there’s been a bit of activity on the Paperclip front. I’ve added Amazon S3 support using the RightAWS gem. Some fellow githubbers have contributed some patches to fix up said S3 support, as well as to add more (and better) validations for content type, etc. We’ve brought it up to a nice, round v2.1.2 as of yesterday. Give yourselves a round of applause.
I highly encourage you to give it a whirl if you haven’t already. It’s very easy to both get and to use!
An interesting note!
Ken Robertson has gone to great lengths to port Paperclip to DataMapper, which should be great news for all you Merbers out there. He’s kept it quite up-to-date, and it’s right alongside the 2.1.2 current release. Hopefully we’ll be able to merge codebases in the future, though right now there’s enough of a difference between DM and AR to make that not terribly feasible.
Here’s a handy tip!
One of the most frequently asked questions is how to use data from your instances in the path and/or URL. The answer is the interpolations hash, which is quite user-extensible. Let’s say you had a song that played in the background of each User’s profile (making this exercise purely hypothetical, of course), and you wanted its name to be the same as the User’s username.
One really good way of allowing this functionality would be to add the following to your config/initializers/paperclip.rb file:
1 2 3 |
Paperclip::Attachment.interpolations[:username] = proc do |attachment, style| attachment.instance.login # or whatever you've named your User's login/username/etc. attribute end |
The #instance method is the instance of the model that this attachment is attached to. You can access any of the model’s attributes, methods, associations, and so on from that object just like normal. Also note that you don’t have to name your interpolation the same thing as the attribute you’re interpolating (though it helps clarity).
Now you can add this to your :path and :url parameters like so:
1 2 3 4 5 |
class User < ActiveRecord::Base has_attached_file :song, :path => ":rails_root/public/system/:attachment/:username.:extension", :url => "/:attachment/:username.:extension" end |
When you call #url or #path on your attachment, Paperclip will run through the interpolations hash, find strings that match its keys, and replace them with the return values of the procs. In this case, you’d produce a lovely url of ”/songs/jyurek.mp3”
It’s a very easy way to add flexibility to your files’ names without having to modify any code yourself.
Another handy tip!
S3 support is now baked in! The updated has_attached_file call looks kinda like this one does:
1 2 3 4 5 6 7 8 |
class House < ActiveRecord::Base has_attached_file :blueprint, :styles => { :thumbnail => "150x150" }, :path => ":attachment/:id/:style.:extension", :storage => :s3, :s3_credentials => "#{RAILS_ROOT}/config/s3.yml", :bucket => "bucket-of-holding" end |
Please note the additional options, including:
:storage: Specify:s3here to use S3. Strictly speaking you could specify:filesystemhere if you’re using the filesystem, but it’s the default so don’t bother.:s3_credentials: You should give this a Hash, a path to a file, or a File object. The File should contain a YAML-ized Hash, and the contents of the Hash should be the access_key_id and secret_access_key used to access your S3 account. You can also “environment-space” these inside the hash, just like your database.yml file.:bucket: The name of the S3 bucket that will be holding your data.
Note that there’s no :url option. That’s because the S3 URLs are generated from the bucket and the path name, so you don’t have to worry about them. You can also specify permissions for your files, in case you don’t want the default “public-read”, by using :s3_permissions.
You can find more about the S3 Storage options and the Filesystem Storage options at their RDocs.
Keep up to date!
Remember, there’s always the Paperclip Google Group and the Paperclip Lighthouse Account in case you have problems, questions, or feature requests.
Last Update: For archival reasons, I’m removing direct links to releases, etc. from here. All the info you need is at the official paperclip project page.
For some reason, file attachment is annoying. I don’t know why, and I know a lot of people have attempted to solve the problem in the past, myself included. Yet it still is. Having gotten fed up with gotchas and design decisions that we didn’t agree with, I went and wrote Paperclip on the plane to RailsConf last year. We’ve been using it here in various forms since and IMHO it’s the way to handle uploads, and finally decided that it should be released.
1 2 3 4 5 |
class User < ActiveRecord::Base has_attached_file :avatar, :styles => { :square => ["64x64#", :png], :small => "150x150>" } end |
A file is treated like any other attribute. It’s assigned like any other attribute, and it’s not saved until you call #save. It doesn’t have its own model. You can say where it’s saved on the filesystem, and what URL it’s referred to by (which means you can let Apache/nginx handle it or you can route it through the app for permissions/security). You can say what thumbnails are made, what resolution and format they are, and you can actually save cropped square thumbnails without any hassle.
1 2 3 4 5 6 7 8 9 |
class AddAvatarToUser < ActiveRecord::Migration def self.up add_column :users, :avatar_file_name, :string add_column :users, :avatar_content_type, :string add_column :users, :avatar_file_size, :integer end def self.down; ...; end end |
You don’t need mini_magick (which we’ve found may have been causing issues), you don’t need ImageScience (which, on the first image we tried to upload, failed to decode it, and it was a simple GIF), you don’t need RMagick (which has memory issues that were the impetus for mini_magick in the first place). You just need ImageMagick installed somewhere, which is as easy as yum, apt-get, or port on any system worth hosting on.
And this isn’t just for avatars and images. You can upload anything. No thumbnails are made by default, so it won’t automagically choke on your Excel docs.
Usage
In your model:
1 2 3 4 5 |
class User < ActiveRecord::Base has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" } end |
In your edit and new views:
1 2 3 |
<% form_for :user, :html => { :multipart => true } do |form| %> <%= form.file_field :avatar %> <% end %> |
In your controller:
1 2 3 |
def create @user = User.create( params[:user] ) end |
In your show view:
1 2 3 |
<%= image_tag @user.avatar.url %> <%= image_tag @user.avatar.url(:medium) %> <%= image_tag @user.avatar.url(:thumb) %> |
Installation
- piston import https://svn.thoughtbot.com/plugins/paperclip/trunk
It can’t be everything for everyone, but for the vast majority of cases we’ve come across, this is the right fit. Really, once you use this you’ll wonder why managing files was such a hassle.
Au début du 21ème siècle, l'infrastructure globale des serveurs de la société Initrode commençait à montrer des fissures. Tous ceux qui avaient pénétré dans la salle serveur comprenaient immédiatement que la croissance de l'infrastructure avait été incontrôlée. Des serveurs en rack mis à coté des stations de travail reconditionnées, connectés avec des câbles à peine sécurisés. Clairement quelqu'un avait fait un effort pour nettoyer la pièce mais s'était arrêté à mi-chemin.
Avec le temps, un serveur passerelle (système propriétaire) pour communiquer avec les agences de traitement de crédits se plantait de plus en plus souvent. Et c'était le genre de plantage plutôt méchant, le serveur ne répondait plus aux ping et il fallait le rebooter manuellement.
D’accord, ce n'était pas réellement un problème pour Erik, l'administrateur, d'appuyer sur le bouton lorsqu'il était présent mais ce n'était le cas que 40 heures par semaine. Or, un organisme de crédit se doit de fonctionner 24 h sur 24 7 jours sur 7. Pourtant, la société était réticente à embaucher l'équipe nécessaire pour assurer le service. Le problème amplifiant de jours en jours, la DSI convoqua l'équipe technique en réunion.
"Ok messieurs, que pouvons nous faire à propos de ce problème?" demanda Laura, la DSI. "
Les développeurs peuvent-ils réparer ça?".
"Non", répondit Erik avant qu'un seul développeur puisse prendre la parole. "C'est un problème technique, pas logiciel".
"Bien, quand le contrat de maintenance se termine t'il?"
"Il y a deux ans."
"Super... il nous est impossible de le remplacer avec le gel des budgets actuel..." Laura ne savait pas quoi faire "Qu'elle est notre solution pour l'instant quand ça plante?"
"Pour l'instant juste appuyer sur le bouton reset."
"Bien, nous remplacerons le serveur lorsque le budget le permettra mais là maintenant, que pouvons nous faire très concrètement? On a besoin de ce serveur 24/24".Laura soupira "Personne n'a d'idées?".
"On pourrait construire un robot administrateur", blagua Erik.
Quelques heures plus tard, alors que Erik était dans le datacenter, appuyant sur le bouton reset une fois de plus, et déçu que la réunion se soit terminée sans réelle solution, Laura rentra dans la salle avec une idée en tête.
"Lors de notre précédente réunion, tu as suggéré la construction un robot " - Laura semblait avoir pris sa proposition au sérieux - "Peux-tu vraiment le faire?"
"Hum, en fait c'était juste une blague Laura, je n'y connais rien en électronique et encore moins comment construire un robot" dit Erik en prenant un ton mi sérieux mi blagueur en tentant de jauger la réaction de Laura. C'est alors qu'il regarda fixement son ordinateur qui venait juste d'éjecter un DVD gravé. Une idée lui vint immédiatement mais c'était trop absurde pour le dire à voix haute. Il ne put néanmoins s'empêcher de sourire.
"Quoi?" demanda Laura.
"Non, rien" répondit Erik " c'est stupide."
"Dis toujours, au point où on en est."
"C'est vraiment stupide " soupira Erik "Je viens de penser qu'en éjectant le lecteur CD ROM d'un vieux pc, on pourrait appuyer sur le bouton reset. Mais c'est vraiment une idée stupide".
"Attends attends, " Commença Laura " tu peux vraiment faire ça?"
Et ce fût exactement ce que Erik fit. Il passa la fin de l'après-midi à mettre tout en place. Un vieux Pc effectuait un ping toutes les deux minutes et éjectait son lecteur cd en cas de non réponse. Avec l'aide de quelques annuaires il trouva la hauteur parfaite pour appuyer sur le bouton reset. A chaque instant pendant son installation il s'attendait à voir Laura arriver sourire aux lèvres en disant "mais non je blague" mais cela n'arriva jamais. Finalement, Erik se releva et admira honteusement son travail. Il colla une étiquette sur le Pc qui disait "ITAPPMONROBOT" et une autre juste en dessous en caractère rouge "NE PAS DEPLACER".
Des années plus tard, longtemps après qu'Erik soit parti, le serveur capricieux fut arrêté et remplacé par une machine toute neuve avec une adresse IP différente. Pendant le remplacement ITAPPMONROBOT fut déplacé dans un coin de la pièce, rebranché et oublié aussi vite. Il passa les dernières semaines de sa vie à ouvrir et fermer fidèlement son lecteur CD toutes les deux minutes, cherchant en vain le bouton reset qu'il ne trouverait jamais plus...
No, not the heavyweight annotations that you see in Java or C# – these are just a way to denote the todos and other mental reminders we often litter in our code comments:
1 2 3 4 5 6 7 8 9 |
class ContactsController < ApplicationController # TODO: factor out model verification to a filter # OPTIMIZE: optimize this # FIXME: it's broken def index ... end end |
With this new rake task we can now spit out a list of items we’ve annotated:
1 2 3 4 5 |
rake notes app/controllers/contacts_controller.rb: * [ 3] [TODO] factor out model verification to a filter * [ 4] [OPTIMIZE] optimize this * [ 5] [FIXME] it's broken |
You can also only list the annotations of a particular kind with rake notes:todo, rake notes:optimize and rake notes:fixme.
1 2 |
svn export http://svn.rubyonrails.org/rails/trunk/railties/lib/tasks/annotations.rake \
lib/tasks/annotations.rake |
tags: rubyonrails, rails
If you’re just not happy with some of the existing options out there for doing Http Authentication in Rails, we’ve got another entry into the market. There’s now an official Rails http authentication plugin in the Rails repo that makes this a simple proposition.
A few convenient methods are now available to you in your controllers to hold your hand: authenticate_or_request_with_http_basic that hands the basic username and password to a block for evaluation and request_http_basic_authentication that asks the client to authenticate itself using http basic authentication. Using them is pretty simple – their inclusion in an authentication filter seems to be the most intuitive use:
class DocumentsController < ActionController
before_filter :verify_access
def show
@document = @user.documents.find(params[:id])
end
# Use basic authentication in my realm to get a user object.
# Since this is a security filter - return false if the user is not
# authenticated.
def verify_access
authenticate_or_request_with_http_basic("Documents Realm") do |username, password|
@user = User.authenticate(username, password)
end
end
end
When the block of authenticate_or_request_with_http_basic evaluates to false, request_http_basic_authentication is invoked which requests that the user enter their credentials. You can also manually invoke request_http_basic_authentication if you are doing a mix of authentication methods and want to use http authentication only if session authentication fails etc…
So what’s with the “plea”? Well, digest authentication hasn’t been added to the plugin yet – though the structure and framework as all but been laid at your feet to contribute the digest logic yourself. Take a look at HttpAuthentication::Digest and see how easy it would be to contribute back to your favorite framework. Just fill in those tiny places that say stuff like # Fancy nouncing goes here and # You compute me and the world will revere you.
References
tags: rails, rubyonrails, http authentication