Any fool…
February 22nd, 2009 — 05:09 pmAny fool can write code that a computer can understand. Good programmers write code that humans can understand
Martin Fowler dixit
Any fool can write code that a computer can understand. Good programmers write code that humans can understand
Martin Fowler dixit
Cada vez que edito algún fichero con Vim en mi macbook voy dejando restos en forma de ficheros con terminación ~. Es decir, si edito el fichero foo tras guardar y salir me encuentro que ahora también tengo el fichero foo~.
¿Soluciones? Varias.
De todas formas yo opte por la mas sencilla -aunque menos drástica, deshabilitar la opción escribir ficheros de backup, que no guardar una copia temporal mientras editamos.
Extracto de la documentación de Vim
If you write to an existing file (but do not append) while the ‘backup’,
‘writebackup’ or ‘patchmode’ option is on, a backup of the original file is
made. The file is either copied or renamed (see ‘backupcopy’). After the
file has been successfully written and when the ‘writebackup’ option is on and
the ‘backup’ option is off, the backup file is deleted. When the ‘patchmode’
option is on the backup file may be renamed.*backup-table*
‘backup’ ’writebackup’ action
off off no backup made
off on backup current file, deleted afterwards
on off delete old backup, backup current file
on on delete old backup, backup current file
Es decir, en mi .vimrc tengo las siguientes lineas
set nobackupset writebackupCon ello, y atendiendo a la anterior tabla, se creara un fichero *.swp mientras editamos y este sera borrado al finalizar.Lo cual por lo menos para mi, es suficiente.
Existen otras soluciones, como crear directorios donde almacenar estos ficheros,… comentadas aquí
Rails implementa el patron observador para permitir observar un modelo y responder así los diferentes estados que puede atravesar un objeto de dicho modelo.
Actualmente estoy trabajando en un proyecto donde es necesario que dos aplicaciones realizadas en Rails se comuniquen entre si mediante mediante Rest.Para conseguir esto, una de las aplicaciones incluye un plugin (proxy) en la otra.En este plugin se encarga de lo siguiente:
Antes de continuar con este post, tengo que confesar una cosilla, observar un modelo desde un plugin es un problema, si y solo si se hace en modo desarrollo.¿Y por qué?
Si no te interesa saber el porque de los problemas y has llegado hasta aqui buscando una solucion, con indicar a Rails que recargue los plugins en cada petición tendrás el asunto resuelto.
Para ello sigue los siguientes pasos:
1º. Indicar a Rails que quieres recargar los plugins
En config/environment.rb añade esta linea:
config.reload_plugins = true
Con esto lo único que hacemos en realidad, es evitar que el path /lib de cada plugin sea incluido en Dependencies.load_once_paths
Pero esto puede que no nos arregle nuestro problema y para muestra un ejemplo:
Arrancare el webrick en modo debug con un breakpoint en el método dispatch del fichero dispatcher.rb, método invocado para cada petición.
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout) debugger new(output).dispatch_cgi(cgi, session_options) end
Arrancamos y enviamos una peticion:
=> Booting WEBrick... => Debugger enabled => Rails 2.1.1 application started on http://events.trabenet.local:3001 => Ctrl-C to shutdown server; call with --help for options [2008-11-16 21:37:24] INFO WEBrick 1.3.1 [2008-11-16 21:37:24] INFO ruby 1.8.7 (2008-08-08) [i686-darwin8] [2008-11-16 21:37:25] INFO WEBrick::HTTPServer#start: pid=1394 port=3001 /opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36 new(output).dispatch_cgi(cgi, session_options) (rdb:2)
El plugin del que tratamos declara una constante, EventObserver. Veamos si existe:
(rdb:2) EventObserver EventObserver (rdb:2) EventObserver.object_id 25840630
Perfecto.
Como en mi config/environment.rb indico que quiere recargar mis plugins, tenemos que:
(rdb:2) Dependencies.load_once_paths []
Y los load_paths son:
(rdb:2) pp Dependencies.load_paths ["/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/controllers/", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/models", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/controllers", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/helpers", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/config", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor", "/opt/local/lib/ruby/gems/1.8/gems/rails-2.1.1/lib/../builtin/rails_info/", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/background/lib", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/localization-with-gettext/lib", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/localized_url_helpers/lib", "/opt/local/lib/ruby/gems/1.8/gems/mbleigh-acts-as-taggable-on-1.0.2/lib", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/thinking-sphinx/lib", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet-auth/lib", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet-core/lib", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet-people-proxy/lib", "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet_subscriptions_proxy/lib", "/opt/local/lib/ruby/gems/1.8/gems/will_paginate-2.2.2/lib"]
Por tanto en cada nueva petición los plugins serán candidatos a ser recargados.
Veamos si esto es así con otra petición:
127.0.0.1 - - [16/Nov/2008:22:00:21 CET] "POST /events.xml HTTP/1.1" 201 1 - -> /events.xml /opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36 new(output).dispatch_cgi(cgi, session_options) (rdb:11) EventObserver.object_id 25840630
Pues no, aquí no se recarga nada!! porque como podemos apreciar , el object_id de la constante es el mismo que el de la primera petición, es decir son el mismo objeto.
2º.Segundo paso de la solución,descargar las constantes del entorno
La explicación a que el objeto representado por EventObserver sea el mismo esto es la siguiente:
Para empezar hay que tener en cuanta que para que una constante definida en un plugin tenga validez en Rails es necesario que ocurra una de las siguientes posibilidades:
La importancia de la forma en la que se carga una constante en Rails, reside en que es lo que se recarga en cada petición y que es lo que se descarga tras cada peticion.Tras cada petición (si estamos en modo de desarrollo) se enviara un mensaje a Dependencies.clear.
def clear log_call loaded.clear remove_unloadable_constants! end
La parte interesante de Dependencies.clear es la llamada al método remove_unloadable_constants!
def remove_unloadable_constants! autoloaded_constants.each { |const| remove_constant const } autoloaded_constants.clear explicitly_unloadable_constants.each { |const| remove_constant const } end
En este método se borra la constante indicada del modulo o clase correspondiente. Como se puede apreciar se borraran tanto las constante “autocargadas” como las indicadas explicitaménte en Dependencies.explicitly_unloadable_constants.
A pesar de haber indicado en nuestro environment.rb que queríamos recargar los plugins (config.reload_plugins=true); para cada petición no se volverá a evaluar el fichero init.rb de cada plugin y por tanto, si nuestras constantes se cargan en Rails mediante llamadas a require (no existirán en el array Dependencies.autoloaded_constants) ; estas no volverán a ser cargadas debido a que no habrá necesidad de ello al no haber sido descargadas en Dependencies.explicitly_unloadable_constants.Para solucionar esto podemos optar por una de estas opciones:
Volviendo a nuestro ejemplo, probemos una de estas opciones.Utilizaremos Depedencies.explicitly_unloadable_constants.
En mi caso, defino las constantes que quiero que se descargen en el init.rb del plugin (por eso de tener todo juntito : ) ).
Dependencies.explicitly_unloadable_constants = 'EventObserver'
Y lo probamos de nuevo con webrick:
=> Booting WEBrick... => Debugger enabled => Rails 2.1.1 application started on http://events.trabenet.local:3001 => Ctrl-C to shutdown server; call with --help for options [2008-11-17 00:01:15] INFO WEBrick 1.3.1 [2008-11-17 00:01:15] INFO ruby 1.8.7 (2008-08-08) [i686-darwin8] [2008-11-17 00:01:15] INFO WEBrick::HTTPServer#start: pid=1442 port=3001 /opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36 new(output).dispatch_cgi(cgi, session_options) (rdb:2) EventObserver.object_id 25840560
Lanzamos otra petición.
127.0.0.1 - - [17/Nov/2008:00:01:17 CET] "POST /events.xml HTTP/1.1" 201 1 - -> /events.xml /opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36 new(output).dispatch_cgi(cgi, session_options) (rdb:11) EventObserver.object_id 27418300
Como se puede apreciar en esta ocasión son objetos distintos y por tanto realmente se recargado la constante.
Tras hacer esto, ya podemos observar con tranquilidad a nuestros modelos desde el plugin.
A partir de aquí voy a explicar el porque de la necesidad de recargar el contenido de nuestros plugins para poder observar a un modelo en modo de desarrollo.
Como explique antes, si no se recarga el contenido de nuestros plugins y en concreto la clase donde definimos el observador del modelo, tendremos un problema.¿Y por qué?
Primero , algunas cosillas a tener en consideración:
Empezaremos por el segundo punto.
En Rails, existen dos puntos prefijados donde se instancian los observadores correspondientes a cada modelo.El primero es en el fichero initializer.rb en el metodo de clase process donde se invoca el metodo load_observers
def process ... load_observers end
Con load_observers como sigue,
def load_observers if gems_dependencies_loaded && configuration.frameworks.include?(:active_record) ActiveRecord::Base.instantiate_observers end end
En ActiveRecord::Base.instantiate_observers crearemos para cada modelo sus observadores y será aquí donde comienzen nuestros quebraderos de cabeza.
def instantiate_observers return if @observers.blank? @observers.each do |observer| if observer.respond_to?(:to_sym) # Symbol or String temp = observer.to_s.camelize.constantize temp.instance elsif observer.respond_to?(:instance) observer.instance else raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance" end end end
POR TERMINAR, lo hare en otro post : )
madtrick:~/programacion/ror/diary madtrick$ ruby script/server webrick => Booting WEBrick... /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.1.0/lib/active_record/base.rb:1667:in `method_missing': undefined local variable or method `acts_as_authorizable' for #<Class:0x336e194> (NameError) from /Users/madtrick/programacion/ror/diary/app/models/user.rb:21 from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:215:in `load_without_new_constant_marking' from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:215:in `load_file' from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:354:in `new_constants_in' from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:214:in `load_file' from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:95:in `require_or_load' from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:60:in `depend_on' from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:456:in `require_dependency' from /Users/madtrick/programacion/ror/diary/vendor/plugins/authentication_system/lib/authentication_system.rb:1 from /opt/local/lib/ruby/vendor_ruby/rubygems/custom_require.rb:27:in `gem_original_require' from /opt/local/lib/ruby/vendor_ruby/rubygems/custom_require.rb:27:in `require' from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:509:in `require' ...
Eso fue lo que me encontre hoy al ir a probar unas cosillas en Rails.
El problema deriva del orden en el que se cargan los plugins residentes en $RAILS_ROOT/vendor/plugins, que a parecer es lexicografica, con lo cual yo me encontraba en la situacion de que el plugin A hacia uso del modelo M pero a su vez este modelo hacia uso de un metodo “mixeado” en ActiveRecord::Base por el plugin B.
Por lo tanto, como A se cargaba antes que B ya teniamos montado el belen.
Tras preguntarle al señor google que como arreglabamos este desaguisado, este primero me dijo que ya habia mas gente con este problema allá por el 2006 (1) y (2) y que finalmente la solucion por la que se opto fue la siguiente :
En environment.rb ponemos config.plugins = {:plugin1,:plugin2,:all} pudiendo controlar asi, el orden en el que se carga dichos plugins,cuales se cargan, etc.El simbolo :all, evidentemente se utiliza para cargar todos los demas plugins.
Actualizacion:
Soy algo cortito, si me hubiera molestado en leerme lo que ponia en environment.rb no habria tenido ningun problema.Extraido de environment.rb:
# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
# :all can be used as a placeholder for all plugins not explicitly named
Si queremos utilizar el gem (o es “la” gem, no tengo muy claro el genero) en rails 2.1.0 nos vamos a encontrar con lo siguientes problemas tras haberlo instalado:
Primer problema,cuando vamos a generar nuestro sistema de login.La extension de las plantillas nos causa el primer problemilla:
madtrick$ ruby script/generate login account create lib/login_system.rb create app/controllers/account_controller.rb create test/functional/account_controller_test.rb create app/helpers/account_helper.rb create app/views/layouts/scaffold.rhtml No such file or directory - /opt/local/lib/ruby/gems/1.8/gems/rails-2.1.0/ lib/rails_generator/generators/components/scaffold/templates/layout.rhtml
El error deriva de que el script que genera el login sigue considerando que las plantillas tienen extension .rhtml, cuando eso ya no es asi.Ahora las plantillas utilizan la extension .html.erb
Por lo tanto para solucionar este problema lo que debemos de hacer es modificar la extension del fichero solicitado en el generador , login_generator.rb en este caso.
En mi caso el fichero login_generator.rb se encuentra en /opt/local/lib/ruby/gems/1.8/gems/login_generator-1.2.2 en otros sistemas puede que sea /usr/local/lib/ruby/gems/1.8/gems/login_generator-1.2.2 o /ruby/local/lib/ruby/gems/1.8/gems/login_generator-1.2.2
Pasaremos de:
17 | m.template "scaffold:layout.rhtml", "app/views/layouts/scaffold.rhtml" |
A:
17 | m.template "scaffold:layout.html.erb", "app/views/layouts/scaffold.rhtml" |
Si ahora volvemos a intentar generar el sistema de login no tendremos ningun problema:
madtrick$ ruby script/generate login account identical lib/login_system.rb identical app/controllers/account_controller.rb identical test/functional/account_controller_test.rb identical app/helpers/account_helper.rb overwrite app/views/layouts/scaffold.rhtml? \ (enter "h" for help) [Ynaqdh] Y force app/views/layouts/scaffold.rhtml create public/stylesheets/scaffold.css create app/views/account create app/views/account/welcome.rhtml create app/views/account/login.rhtml create app/views/account/logout.rhtml create app/views/account/signup.rhtml create README_LOGIN
Segundo problema.Tras solventar lo anterior, estamos ansiosos por probar nuestro sistema de login asi que vamos a nuestro navegador y tecleamos: http://localhost:3000/account/login
En la anterior direccion asumimos lo siguiente:
- Que el controlador que hemos creado para gestionar las funciones de login tiene por nombre AccountController
- Que tenemos una ruta en routes.rb que funciona para esta peticion
Si probamos esto una bonita pantalla de error nos dara la bienvenida:
ArgumentError in AccountController#login wrong number of arguments (0 for 1)
Bien, para solucionar esto debemos de modificar todos los @request.method que nos encontremos en nuestro controlador por @request.request_method para evitar colisiones con la palabra reservada de ruby method
Tras solucionar esto volvemos a probar nuestro sistema de login, pero un vez mas nos la dan con queso:
Segundo problema,
NoMethodError in AccountController#login You have a nil object when you didn't expect it! The error occurred while evaluating nil.request_method
Bien, bien.Veamos que pasa aqui.Como podemos observar el mensaje de error nos dice que el error ocurrio al tratar de evaluar nil.request_method.Pero oiga, como que nil!! Si yo estoy viendo @request.request_method.
Lo que pasa aqui es que en esta version de Rails, @request (al igual que @params, @session, @flash, @cookies, @headers, @response) ha quedado en desuso en favor de los “accessor methods” que tienen el mismo nombre que las variables pero sin la arroba.Es decir donde tenemos @request.request_method pasamos a tener request.request_method y asi sucesivamente.
Probamos los cambios y nos encontramos con el tercer problema:
NoMethodError in Account#login Showing account/login.rhtml where line #1 raised: undefined method `start_form_tag' for #<ActionView::Base:0x35a61dc>
login.rhtml luce de la siguiente forma:
<%%= start_form_tag :action=> "login" %> ... <%%= end_form_tag %>
start_form_tag, tambien esta en desuso, en su lugar debemos de utilizar form_tag.De esta forma login.rhtml nos queda de la siguiente forma:
<% form_tag '/account/login' do %> ... <% end %>
Ahora por lo menos podemos probar nuestro login.Lo probamos y tras hacer click en el boton de login,nos encontramos con el cuarto error,
NoMethodError in AccountController#login undefined method `find_first' for #<Class:0x365c8b0>
find_first es otro de los metodos en desuso.Debemos de cambiarlo por find, con el simbolo :first como primer parametro y la opcion :conditions con la condicion que teniamos en find_first.
Es decir, pasamos de:
find_first(["login = ? AND password = ?", login, sha1(pass)])
A:
find(:first,:conditions=>["login = ? AND password = ?", login, sha1(pass)])
Si volvemos a probar, adivinad que pasa….si, otro error.Por el quinto vamos,otra vez por elementos en desuso.En este caso se trata de la variable @session residente en el fichero login_system.rb.Cambiamos esta y las demas variables existentes en dicho fichero a sus “accesor methods” , guardamos y volvemos a probar… e-voilà!!
Funciona!!
Espero que todo este rollo le sea de ayuda a alguien : )