Terminal admin

Codebrawl doesn’t have a proper admin panel, so I tend to do content changes in the database directly using script/rails console. This gives me a lot of freedom, since I can do anything without having to build features for it in my admin panel first. Also, if Codebrawl had an admin panel, I’m absolutely sure I would have to dive into the console to do stuff every once in a while.

Of course, there are some problems with the console when trying to quickly edit a record. Finding the right one requires you to type a query:

c = Contest.first(:conditions => {:name => 'Terminal Admin'})

Also, editing a field requires you to fill in the whole value every time:

c.update_attribute(:description => 'Codebrawl doesn't have a proper...')

The challenge for this week is to think of something that makes this easier and faster. Tackle one problem (like finding records, for example) the best you can, but don’t try to build a complete terminal admin. The idea is to combine good ideas into one project we can keep working on.

Create a solution you can require into the console. It needs to hook into your ORM of choice (Codebrawl uses Mongoid, but you can build something for another ORM if you want), like in this example Gist. Also, don’t forget to add a REAME file explaining how it works.

No shirt, no shoes, no weapons. You have one week to get your entry in. Good luck!

Prize

This week’s winner will receive a code for $25 worth of credit for the 6sync hosting platform!

This contest is finished

Congratulations to this week's winners! The entries and the contestant names are shown below.

  • README.md

    TerminalAdmin

    Slightly inspired by the interactive_editor gem, this allows you to edit mongoid documents as yaml in vim. Once the file is written, the changes are applied to that object. If you tack on the bang, then the changes are automatically persisted to the database.

    example:

    require 'mongoid'
    
    Mongoid.configure do |config|
      config.master = Mongo::Connection.new.db("terminal_admin")
    end
    
    class Product
      include Mongoid::Document
      field :name, :type => String
      field :price, :type => Integer
    end
    
    require 'terminal_admin'
    
    product = Product.new
    
    product.edit
    
    terminal_admin.rb
    module Mongoid::Document
      require 'tempfile'
    
      def edit
        tmp_file = new_temp_file
        open_for_editing(tmp_file)
        update_from_file(tmp_file)
      end
    
      def edit!
        edit
        save
      end
    
      private
    
      def editable_fields
        fields.keys - ["_id", "_type"]
      end
    
      def editable_attributes
        attrs = {}
        editable_fields.each { |k| attrs[k] = attributes[k] }
        attrs
      end
    
      def new_temp_file
        tmp_file = Tempfile.new([self._id, '.yml'])
        tmp_file.open
        tmp_file.write(editable_attributes.to_yaml)
        tmp_file.close
        tmp_file
      end
    
      def open_for_editing(tmp_file)
        system "vim #{tmp_file.path}"
      end
    
      def update_from_file(tmp_file)
        attrs = YAML.load(tmp_file.open.read)
        self.attributes = attrs
      end
    end
    
    View full entry
    Finished in 1st place with a final score of 3.6/5. (View the Gist)
  • README.md

    InteractiveCU is a simple interactive create and update for AR.

    Examples:

    Create:

    ruby-1.9.2-p180 :007 > User
     => User(id: integer, name: string, created_at: datetime, updated_at: datetime) 
    ruby-1.9.2-p180 :008 > User.interactive_create # or User.ic
    
    ==New User==
    
    name: nash
      SQL (0.8ms)  INSERT INTO "users" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 24 Jul 2011 08:49:26 UTC +00:00], ["name", "nash"], ["updated_at", Sun, 24 Jul 2011 08:49:26 UTC +00:00]]
     => true
    

    Update:

    ruby-1.9.2-p180 :009 > User.last.interactive_update # or User.last.iu
    
    ==Old User==
    
    id: 8
    name: nash
    created_at: 2011-07-24 08:49:26 UTC
    updated_at: 2011-07-24 08:49:26 UTC
    
    ==New User==
    
    name: updated_nash
       (0.5ms)  UPDATE "users" SET "name" = 'updated_nash', "updated_at" = '2011-07-24 08:50:28.794082' WHERE "users"."id" = 8
     => true
    

    It shows you validation errors:

    ruby-1.9.2-p180 :010 > User.last.iu
    
    ==Old User==
    
    id: 8
    name: updated_nash
    created_at: 2011-07-24 08:49:26 UTC
    updated_at: 2011-07-24 08:50:28 UTC
    
    ==New User==
    
    name: 
    
    Something went wrong:
    
    name:  <------ can't be blank 
     => nil
    
    interactive_cu.rb
    module InteractiveCU
      NOT_INTERACTIVE = %w(id created_at updated_at)
    
      def self.included base
        base.extend(ClassMethods)
      end
    
      def self.read_attributes attrs
        params = {}
        attrs.each do |name, value|
          unless NOT_INTERACTIVE.include? name
            print "#{name}: "
            params[name] = gets.strip
          end
        end
        params
      end
    
      def self.output(options)
        puts
        puts "==#{options[:status]} #{options[:name]}=="
        puts
      end
    
      def self.validate_errors(obj, attrs)
        puts
        puts "Something went wrong:"
        puts
        obj.errors.messages.each do |attr, error|
          puts "#{attr}: #{obj.send(attr)} <------ #{error.join(', ')} "
        end
        nil
      end
    
      def interactive_update
        InteractiveCU::output :status => "Old", :name => self.class.name
        attributes.each do |name, value|
          puts "#{name}: #{value}"
        end
        InteractiveCU::output :status => "New", :name => self.class.name
    
        new_attributes = InteractiveCU::read_attributes attributes
        self.attributes = new_attributes
    
        unless self.valid?
          InteractiveCU::validate_errors self, new_attributes
        else
          save
        end
      end
    
      alias_method :iu, :interactive_update
    
      module ClassMethods
        def interactive_create
          InteractiveCU::output :status => "New", :name => self.name
          obj = self.new
          new_attributes = InteractiveCU::read_attributes self.attribute_names
          obj.attributes = new_attributes
          unless obj.valid?
            InteractiveCU::validate_errors obj, new_attributes
          else
            obj.save
          end
        end
    
        alias_method :ic, :interactive_create
    
      end
    end
    
    class ActiveRecord::Base
      include InteractiveCU
    end
    
    View full entry
    Finished in 2nd place with a final score of 3.5/5. (View the Gist)
  • README.md

    Just use whatever blocking editor you want!

    class Contest
      include Mongoid::Document
    
      field :name
      field :descr
    end
    
    first = Contest.first
    first.edit_name! # Edit with vi(m)!
    

    That's it!

    If you like:

    first.edit_name! :nano      # For those uncool!
    first.edit_name! "mate -w"  # Remember: as long it blocks!
    
    editor.rb
    require 'tempfile'
    
    module Mongoid::Document  
      def __editor__ editor
        editor = editor || "vi"
        raise "Editor #{editor} not found!" if %x{which #{editor}} == ""
        editor
      end
      
      def method_missing(method, *args)
        if method.to_s =~ /edit_(.+)!/ and fields.include? $1
          editor = __editor__ args.first
          
          temp = Tempfile.new "#{$1}#{srand}"
          temp.write read_attribute($1)
          temp.close
          
          system "#{editor} #{temp.path}"
          
          temp.open
          neww = temp.read
          neww = neww[0..-2] if neww[-1] == "\n"
          update_attribute($1, neww)
          temp.close!
          
          true
        else
          super
        end
      end
    end
    
    View full entry
    Finished in 3rd place with a final score of 3.4/5. (View the Gist)
  • README
    load 'irb_editor.rb'
    
    c = Contest.first
    c.description         # => Prints the original description
    c.description.edit!   # => This launches the vi editor. Edit the content in the editor and type :wq
    c.description         # => Prints the modified description
    c.save!
    irb_editor.rb
    # Ability to edit a large string using your favourite editor - vi
    
    require 'tempfile'
    
    class IrbEditor
    
      TMP_FILE_NAME = "irb_tempfile"
    
      # Right now it supports vi only. Other possible values:
      # => 'mate -w'
      # => 'nane'
      # => 'emacs'
      EDITOR_COMMAND = 'vi'
      
      attr_accessor :file
    
      def initialize
        self.file = Tempfile.new([TMP_FILE_NAME, '.rb'])
      end
    
      def edit(value)
        File.open(self.file.path, 'w') { |f|  f.puts(value) }
        system("#{EDITOR_COMMAND} #{self.file.path}")
        return self
      end
    
      def read
        IO.read(self.file.path).chomp
      end
    
    end
    
    # Extending the string class itself!
    String.class_eval do
    
      IRB.conf[:irb_editor] = IrbEditor.new
    
      def edit!
        self.replace(self.edit)
      end
    
      def edit
        IRB.conf[:irb_editor].edit(self).read
      end
    
    end
    
    View full entry
    Finished in 4th place with a final score of 3.4/5. (View the Gist)
  • inew.rb
    module Inew
    
      def inew_attrs
        @inew_attrs ||= self.column_names.sort.reject{|x| x =~ /^id|_at/}
      end
    
      def inew_attrs=(val)
        @inew_attrs = val
      end
    
      def inew
        foo = new
        self.inew_attrs.each do |x|
          type = self.columns_hash[x].try(:type)
          print x.to_s + "(#{type}) "
          line = gets.strip
          begin
            foo.send("#{x.to_s}=", eval(line))
          rescue
            puts "Whoopsie!"
            # probably should do something, but what?                                                                                                                                                         
          end
        end
        return foo
      end
    
    end
    
    class ActiveRecord::Base
      extend Inew
    end
    
    README.rdoc
    ==The Inew module provides the inew (interactive new) method on your ActiveRecord derived classes.
    ===It should be pretty easy to adapt to MongoDB if that's your flavor.
    
      ~/src/temp@master>rc
      Loading development environment (Rails 3.1.0.rc4)
      ruby-1.9.2-p180 :001 > Content.inew
      body(text) "This is the body"
      dom_id(string) "fancy_content"
      link(string) "/king_kong"
      position(integer) 12
      tipe(string) "billboard"
      title(string) "I like Turtles"
      visible(boolean) true
    
      # 
    
    
    Just open up a console in your application and require inew.rb, and try Class.inew instead of Class.new.
    
    You'll be prompted for the value of each attribute in turn.
    
    
    You can also set which attributes you want to be prompted for, useful when setting password and password_confirmation for a User or similar.
    
      ~/src/temp@master>rc
      Loading development environment (Rails 3.1.0.rc4)
      ruby-1.9.2-p180 :001 > Product.inew_attrs
       => ["name"] 
      ruby-1.9.2-p180 :002 > Product.inew_attrs = [:name, :password, :favorite_color]
       => [:name, :password, :favorite_color] 
      ruby-1.9.2-p180 :003 > Product.inew_attrs
       => [:name, :password, :favorite_color] 
      ruby-1.9.2-p180 :004 > Product.inew
      name() "Dog Food"
      password() "PickleHeart"
      Whoopsie!
      favorite_color() "Green"
      Whoopsie!
       => # 
    
    
    
    View full entry
    Finished in 5th place with a final score of 3.2/5. (View the Gist)
  • README.markdown

    me.rb

    In almost every my project there is a model that refers to myself. It can be an instance of User, Staff, Admin, Dude, etc. I use it for logging in and testing staff. I use it many times in rails console as well. Because typing User.where(:email => 'my@email.com').first can by annoying so I've put together little snippet that work in Mongoid and AcitveRecord as well.

    $ rails console
    Loading development environment (Rails 3.1.0.rc4)
    >> load 'me.rb'
    => nil
    >> User.me
    => #<User _id: 4c14ba297826c943b2000002, encrypted_password: "16336dd440d4bbe843dc39....", .... >
    

    I hope you find it useful!

    me.rb
    module ActiveRecord
      class Base
        class << self
          def me
            where(:email => `git config user.email`.chomp).first
          end
        end
      end
    end if defined? ActiveRecord
    
    module Mongoid
      module Document
        module ClassMethods
          def me
            where(:email => `git config user.email`.chomp).first
          end
        end
      end
    end if defined? Mongoid
    
    View full entry
    Finished in 6th place with a final score of 3.0/5. (View the Gist)
  • README.markdown

    short_arel

    What is this about?

    Do you use ActiveRecord? Tired of writing in console all those verbose Arel methods? Well short_arel is for you!

    The idea is to reduce to the minimum the numbers of characters you have to type to perform ActiveRecord queries. short arel just aliases long-named methods with its short version, so instead of having to write:

    Post.where(:public => true).includes(:comments).order('edited_at ASC')
    

    you could write

    Post.w(:public => true).i(:comments).o('edited_at ASC')
    

    Why?

    This is the reason: http://codebrawl.com/contests/terminal-admin

    Usage:

    Put this file in your rails directory. Load console (rails c) and require the file

    require './short_arel.rb'
    
    short_arel.rb
    class ActiveRecord::Base
      class << self
        short_aliases = {
        :f      => :find,
        :f1     => :first,
        :f1!    => :first!,
        :l      => :last,
        :l!     => :last!,
        :al     => :all,
        :e?     => :exists?,
        :a?     => :any?,
        :m?     => :many?,
        :d      => :destroy,
        :da     => :destroy_all,
        :dl     => :delete,
        :dla    => :delete_all,
        :u      => :update,
        :ual    => :update_all,
        :fe     => :find_each,
        :fib    => :find_in_batches,
        :s      => :select,
        :g      => :group,
        :o      => :order,
        :e      => :except,
        :r      => :reorder,
        :li     => :limit,
        :o      => :offset,
        :j      => :joins,
        :w      => :where,
        :p      => :preload,
        :e      => :eager_load,
        :i      => :includes,
        :h      => :having,
        :c      => :count
        }
        short_aliases.each do |short_method_name, long_method_name|
          eval "alias #{short_method_name} #{long_method_name}"
        end
      end
    end
    
    View full entry
    Finished in 7th place with a final score of 2.7/5. (View the Gist)
  • README.markdown

    Array operator on ActiveRecord

    This adds a [] operator to ActiveRecord models to quickly find single records, e.g.

    User[:id => 1]
    

    Thats it!

    gistfile1.rb
    # Adds a [] operator to ActiveRecord models
    # e.g. User[:id => 1] returns #<User id: 1>
    
    class ActiveRecord::Base
      def self.[](val)
        self.where(val).first
      end
    end
    
    View full entry
    Finished in 8th place with a final score of 2.6/5. (View the Gist)
  • README
    Well I'm a bad boy (cause I don't even miss her) and I went ahead and extended the Object class instead of hooking into mongoid. I'm also pretty drunk so I'm sure this has many many bugs, logic flaws, typos, and whatever else could possibly embarrass me. I'm also not even sure if this is a time saver, or clever, or 'neat-o'/gold star worthy.
    
    Usage: 
    
    Load and then either update or search...
      search Object, :column => 'value'
      search Contest, :id => '1'                                       # Find a Contest object with an :id of 1
    
      
    
      update Object, :column => 'value' [, :where => { :column => value} ]
      update Contest, :name => 'admin'                                 # Updates *ALL* Contest entries to a "name" of "admin"
      update Contest, :name => 'admin_first', :where =>  { :id => '1'} # Updates the Contest entry with an :id of 1 to a name of 'admin first'
    
      
    
    ruby-1.9.2-p0 > load 'admin.rb'
     => nil
    ruby-1.9.2-p0 > search Contest, :name => 'admin_first'
     => [#]
    ruby-1.9.2-p0 > update Contest, :name => 'I\'m updating you right now', :where => { :id => '1'}
     => [#]
    ruby-1.9.2-p0 > search Contest, :id => '1'
     => [#]
    
    admin.rb
    class Object
    
      def search(object, params)
        # Searches $object for $params
        # Returns the an array of found objects or nil
    
        return nil if params.keys.count > 1
        return object.find(:all, :conditions => { params.keys[0] => params.values[0] })
      end
    
      def update(object, params)
        # Updates $object with $params
        # Returns updated objects
    
        valid_attributes = get_attributes_for object
    
        if params.has_key? :where
          o = search(object, params.fetch(:where))
          do_update(o, params, valid_attributes)
        else
          o = Contest.all
          do_update(o, params, valid_attributes)
        end
      end
    
      private
        def get_attributes_for(object)
          # Gets attribute names for $object.
          # Returns an array or attribute names
    
          o = object.first
          o.attribute_names
        end
    
        def do_update(object, params, valid_attributes)
          # Takes an array of objects, a hash of params, and an
          # array of valid_attributes. Loops through to find valid
          # combinations and updates correct permutations
    
          object.each do | o |
            valid_params = {}
            params.each do | param|
              if valid_attributes.include? param[0].to_s
                valid_params[param[0]] = param[1]
              end
            end
            valid_params.each do | param |
              o.update_attribute(param[0], param[1])
            end
          end
        end
    
        def vote_for_my_pathetic_and_badly_coded_gist()
          pass
        end
    end
    
    View full entry
    Finished in 9th place with a final score of 2.4/5. (View the Gist)