Rails 3 Lernen mit d:evolute
Aus den Archiven unserer Software - Abteilung d:evolute ein Einsteiger - Guide fuer Rails (3)
Automatically Create Relations with Inherited Resources and/or Rails3 Magic
If you have a resource (in my case that's Document and want to add or create a related resource (in my case Style) and you use Inherited Resources, your controller may look like this:
class DocumentController < InheritedResources::Base respond_to :html, :pdf end
There is nothing going on, because InheritedResources will do the job for you: providing 'automatically' scaffold functionality.
Okay, in most cases you will not want that, you want to add something.
In my case that is, as I mentioned, to add or create a related 'Style' in the create-action
To override the create action, you could
def create super do @style=Style.create(params[:style]) ... end end - in general.
Inherited Resources skips the 'super' giving you \create!' which can take the block.
Anyway, to really add a new style to the @document (that is used by the magic), i would have to do something like this:
def create if params[:style] @style = Style.create(params[:style]) @document = Document.new(params[:document]) @document.style_id = @style.id if @style end create! end
Two thing here are not necessary:
- params[:style] and the if condition (because inherited resources could already check that)
- @document = Document.new (because inherited resources already did that in fact
To get rid of it, we use a other trick:
class Document < ActiveRecord::Base
belongs_to :style
accepts_nested_attributes_for(:style)
end
This will let the style-attribute take a Hash that can lead to a full create, which is nice.
It's an Rails3 Feature.
With IR again, you can
class DocumentsController < InheritedResources::Base protected def begin_of_association_chain case when params[:style_id] Style.find(params[:style_id]).try(:documents) when params[:style] Style.create!(params[:style]).try(:documents) else Document end end end
Fileupload with Mongoid and Paperclip
Last week, a new gem - ’mongoid-paperclip was released. If you are already used to paperclip, you might in deed be sad if you use mongoid and have to disclaim the convenience of paperclip if you want to add file upload to one of your documents.
Anyway, i’m forming a habit of having a closer look at new gems, and at least check out the codebase as far as necessary to know what i’m dealing with. In the case of mongoid-paperclip I was particularly interested, and I was not too much surprised to find out that it’s actually a very easy implementation.
You don’t have to care about how complicated a gem or library you rely on is, as long as it works fine, but i often see people dismiss an external library in the moment it doesn’t perfectly fit their needs. But often code is not that complicated, and it would be worth to have a look, find out how easy it is to tweak it, go for it, and be happy and independent.
To illustrate how easy a gem like this can be, I’d like to give you some code, and show you what the gem actually does.
But first you have to know, how to use mongoid-paperclip. See this example, and keep in mind, that you can use ‘has_attached_file’ exactly the way you’re used to with paperclip, so checkout the paperclip docs if you’re unsure. I keep it with the most easy example without any additional options.
class Profile
include Mongoid::Document
include Mongoid::Paperclip
has_attached_file :avatar
endAside from the gemspec and readme, yhe code is actually all in one file, in lib/mongoid_paperclip.rb.
First thing that happens, is to require Paperclip, and output a note if it cannot be loaded.
begin
require "paperclip"
rescue LoadError
puts "Mongoid::Paperclip requires that you install the Paperclip gem."
exit
endSecond is, deactivating Paperclips default logger, because it depends on ActiveRecord.
Paperclip.options[:log] = falseTherefore, if Rails is loaded and a logger specified, we can use this.
if defined?(Rails)
if Rails.respond_to?(:logger)
Paperclip.options[:log] = Rails.logger
end
endAnd now we come to the Mongoid::Paperclip module itself. As you see its less then 15 lines of code, and what it does is basically to include paperclip, and to pass the params to paperclips ‘has_attached_file method. Last step is to make sure that mongoid knows about the necessary fields, because paperclip can not deal with mongoids dynamic attributes.
module ClassMethods
def has_attached_file(field, options = {})
include ::Paperclip
include ::Paperclip::Glue
has_attached_file(field, options)
field(:"#{field}_file_name", :type => String)
field(:"#{field}_content_type", :type => String)
field(:"#{field}_file_size", :type => Integer)
field(:"#{field}_updated_at", :type => DateTime)
end
end
endWell, this was pretty easy to understand, and you might want to come back on it in similar situations.
Problems with Rails 3 Generators 'invoke' and migration templates
I want to generate migrations.
It seems, nobody has ever thought about that because in Rails 3 there is idea, that I have to create the timestamp on my own like this:
def self.next_migration_number(dirname) if ActiveRecord::Base.timestamped_migrations Time.now.utc.strftime("%Y%m%d%H%M%S") else "%.3d" % (current_migration_number(dirname) + 1) end end
So some people suggest another solution
class ActsAsTaggableOnMigrationGenerator < Rails::Generators::Base
invoke "migration", %(add_fields_to_tags name:string label:string)
end
but that really does not work for me, it leads to
/usr/lib/ruby/gems/1.8/gems/activesupport-3.0.1/lib/active_support/whiny_nil.rb:48:in `method_missing': undefined method `name' for nil:NilClass (NoMethodError)
from /usr/lib/ruby/gems/1.8/gems/thor-0.14.4/lib/thor/invocation.rb:116:in `invoke_task'
from /usr/lib/ruby/gems/1.8/gems/thor-0.14.4/lib/thor/group.rb:224:in `dispatch'
from /usr/lib/ruby/gems/1.8/gems/thor-0.14.4/lib/thor/invocation.rb:109:in `send'
from /usr/lib/ruby/gems/1.8/gems/thor-0.14.4/lib/thor/invocation.rb:109:in `invoke'
:)
Update:
This worked:
invoke "migration", "add_fields_to_tags" "name:string label:string")
Do i use Mongoid, Mongomapper or Mongomatic as my Ruby ORM for MongoDB?
Now that I had a first chance to use MongoDB (through Mongoid) in the wilderness, I decided to move back and reflect on the alternative ORMs for Mongo in Ruby. Which mostly means Mongomapper, though, with Mongomatic, we have a third player in the game.
I found some pretty interesting ressources on this topic. First of all, Peter Cooper passes the question of what mapper to use, to the authors of both, Mongoid and Mongomapper. I recommend to read the article, or at least the yellow boxes featuring the authors opinions. But don’t get confused about the statement Mongoid would support Rails 3, while Mongomapper is still focussing on Rails 2. They are both Rails 3 compatible.
It remains to say that Mongoid is definitley following the style of ActiveRecord, making use of ActiveModel wherever possible and having a Arel-like Query Api, while MongoMapper is rather having its source in the DataMapper world. There is another interesting comparision on the Antrarestrader Blog.
I recommend you to read on by yourself, but i try to put together what i learned about this two orms for mongo:
mongoid:
- better support for embedded documents
- better support for very large documents (>500kb)
- closer to activerecord, by making use of active model and an arel like query syntax
- very nice and lean documentation to be found on the project website..
mongomapper:
- better support for reference associations
- supposedly it has a bigger community, or at least still had a bigger community last summer
- better plugin interface
mongomatic
Refactoring Redmine to be more agile and ajaxified
I did some redmine tweaking this weekend.
The goal was, to make it more agile. Agile in this terms means:
- You can list issues, that follow each other easily
- You need much less clicks, to get an overview over issue details and look into them, while browsing
- You can add and edit (sub) issues inline
I think these are main goals, people address in plugins now or not at all.
I came up with some mayor changes, that should be done in core redmine, to fit my needs:
1) Pack Views into helpers and partials
I want to be able to toggle through single sections if my issue - create flow.
Therefore i created some helpers, that added divs and toggle - links with Javascript around the specific section (.e.g. in issues/show)
Doing this, i saw, that redmine is amazingly unflexible here. You even get problems toggling single sections, because of cascaded divs that have been added there just for design purpose (the background color of some parts of the issue and such)
So the tasks would be:
- move sections into partials
- wrap everything into sections
2) Make Controllers AJAX-agnostic
Take Redmine and try to render issues/show or issues/new in a modalbox via AJAX.
It works, most controllers contain a block like this:
respond_to do |format|
format.html {#normal render behaviour}
format.js {#render some attributes partial}
end
But
a) not every action has this block
b) it sometimes doesn't do, what you actually want to do.
If you want to render any action via xhr, you need two things to do:
a) skip the layout on xhr - requests
b) change the return - to behaviour slightly, to pass redirect_to or return_to.
Why the latter is needed - take a look at this example:
- You create an issue in an inline div.
- After you created the div, it should NOT redirect to issues show.
- Instead ideally it would know, where you have been and render only a bit of javascript, that magically loads the right row in some table.
- The same for error handling
So what is to do:
- Also with the goal to be compatible with new Rails 3 goodness (responds_with...): Remove all respond_to - blocks and replace them with a Responder, that can handle those things.
- Replace all links, that should be UI-context agnostic to pass parameters for redirect_to and others
Go with a plugin!
Plugins could do this as well (and we will come up with some plugins, eventually) but the problem is:
To add some simple line of code to let's say every action of your Issues-Controller would require
a) a hook at every place, where you want to do that
b) to copy the whole controller into your plugin (which likely is not compatible with others!)
One of the great things in Redmine is the list of hooks. Hooks are a way to inject code at exactly one single position.
A hook can for example add things to the bottom of the issue_details_list
in the code it looks similar to this:
call_hook(:issue_details_bottom) # or so I will be about to paste a complete list of hooks soon. Sadly, this pattern is not followed consequently enough. A plugin can only be as good, as the positions for the hooks are chosen. According to the above suggested refactorings, I would recommend:
- Go through controllers and views
- Create a ViewHook for every (now toggable and named) section in the view
- Create a hook for the corresponding Responder (or block, that responds to the browser) in the controllers.
Refactoring Redmine
Things to consider when starting a new Rails app
Rails 3
Well, to avoid people from telling me they do not know what rails 3.0 will be shipped out with, here’s the link to the full release notes:
http://guides.rails.info/3_0_release_notes.html
I allow myself to quote the pretty self confident introduction to their new release:
Rails 3.0 is ponies and rainbows! It’s going to cook you dinner and fold your laundry. You’re going to wonder how life was ever possible before it arrived. It’s the Best Version of Rails We’ve Ever Done!
I don’t want to go in to any detail about rails 3, because it’s pretty much described on the page that you’ll see when you follow the link above. So go ahead :)
Rails Plugins
While reading an article about how the behavior of Rails Plugins will change with Rails 3.0 , i stumbled upon this new page ’railsplugins.org’ that engine yard has created to keep track about which plugins are compatible with rails 3, with ruby 1.9, if they run with jruby and if they are thread safe.
Though there are many more requirements one could have on rails plugin, like a solid test coverage, and a proper re-usability (e.g. a slightly different approach then the one the plugin was originally designed for, should still be easy to solve with the plugin), it’s a good start, and a nice list of plugins, currently counting 145.
After all, we should somehow always have an more complex overview over all those plugins, and collect all interesting meta-information, and remember at least which plugin we use in which application. A good start could be to extract this plugin list, and store it in some kind of database for its own, to be able to connect it with our own meta-information.
Who needs help on building a custom scraper, to scrape out the plugin list :)?