Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit eff53f60a34a163df26902a9712195ed38bb53d9 @iain committed Jul 16, 2009
BIN .DS_Store
Binary file not shown.
@@ -0,0 +1,52 @@
+= Root Table
+
+Specifies so-called "root-tables" for inclusion with other models. Handy if
+you need to have a user manageable list in a dropdown.
+
+Integrates well with ActsAsList
+
+Has an interface to manage root tables.
+
+It's very early days for this plugin, so, unless you're feeling adventurous,
+don't use it just yet.
+
+== Installation
+
+Install root_table:
+
+ ./script/plugin install git://github.com/iain/root_table.git
+
+Install ActsAsList (optional):
+
+ ./script/plugin install git://github.com/rails/acts_as_list.git
+
+== Usage
+
+In short:
+
+* Create the model that has an item to be selected from a list. Add the foreign
+ key as if you were to define a normal belongs_to-relation.
+* Create a model which will be the list to choose from, include a name field
+ and optionally a position field, when working with ActsAsList.
+* Add to that model "root_table_for" and you're done.
+
+== Example
+
+We'll create products that can have a category to choose from.
+
+* Create the *target* model (Product):
+
+ ./script/generate model Product name:string description:text category_id:integer
+
+* Create the *root table* (Category):
+
+ ./script/generate model Category name:string position:integer
+
+* Migrate
+* Edit the category model:
+
+ class Category < ActiveRecord::Base
+ root_table_for :product
+ end
+
+
@@ -0,0 +1,11 @@
+require 'rake'
+require 'spec/rake/spectask'
+
+desc 'Default: run specs.'
+task :default => :spec
+
+desc 'Run the specs'
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
+ t.spec_files = FileList['spec/**/*_spec.rb']
+end
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,54 @@
+class RootTable::ManageController < ApplicationController
+
+ layout 'categories'
+
+ def index
+ @collection = model.all
+ end
+
+ def new
+ @object = model.new
+ end
+
+ def create
+ @object = model.new(params[table])
+ if @object.save
+ flash[:notice] = "#{model.human_name} saved"
+ redirect_to root_table_table_manage_index_url(table)
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @object = model.find(params[:id])
+ end
+
+ def update
+ @object = model.find(params[:id])
+ if @object.update_attributes(params[table])
+ flash[:notice] = "#{model.human_name} saved"
+ redirect_to root_table_table_manage_index_url(table)
+ else
+ render :edit
+ end
+ end
+
+ protected
+
+ def table
+ @table ||= params[:table_id]
+ end
+ helper_method :table
+
+ def model
+ @model ||= table.to_s.camelize.constantize
+ end
+ helper_method :model
+
+ def columns
+ @columns ||= model.column_names - %w[ id updated_at created_at ]
+ end
+ helper_method :columns
+
+end
@@ -0,0 +1,7 @@
+class RootTable::TablesController < ApplicationController
+
+ def index
+ @tables = [ :category ]
+ end
+
+end
Binary file not shown.
@@ -0,0 +1,2 @@
+module RootTable::ManageHelper
+end
@@ -0,0 +1,2 @@
+module RootTable::TablesHelper
+end
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+ <title>Categories: <%= controller.action_name %></title>
+ <%= stylesheet_link_tag 'scaffold' %>
+</head>
+<body>
+
+<p style="color: green"><%= flash[:notice] %></p>
+
+<%= yield %>
+
+</body>
+</html>
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+ <title>Products: <%= controller.action_name %></title>
+ <%= stylesheet_link_tag 'scaffold' %>
+</head>
+<body>
+
+<p style="color: green"><%= flash[:notice] %></p>
+
+<%= yield %>
+
+</body>
+</html>
@@ -0,0 +1,7 @@
+= f.error_messages
+- columns.each do |n|
+ %p
+ = f.label n
+ %br
+ = f.text_field n
+%p= submit_tag
@@ -0,0 +1,4 @@
+%h2= "Edit " + model.human_name
+
+- form_for @object, :url => root_table_table_manage_path(table) do |f|
+ = render :partial => "form", :locals => { :f => f }
@@ -0,0 +1,17 @@
+%h2= "Manage " + model.human_name
+
+%table
+ %thead
+ - columns.each do |n|
+ %th= model.human_attribute_name(n)
+ %tbody
+ - @collection.each do |c|
+ %tr
+ - columns.each do |n|
+ %td&= c.send(n)
+ %td
+ = link_to "Edit", edit_root_table_table_manage_path(table, c.id)
+ |
+ = link_to "Destroy", root_table_table_manage_path(table, c.id), :method => :destroy, :confirm => "Are you sure?"
+
+%p= link_to "New", new_root_table_table_manage_path(table)
@@ -0,0 +1,4 @@
+%h2= "New " + model.human_name
+
+- form_for @object, :url => root_table_table_manage_index_path(table) do |f|
+ = render :partial => "form", :locals => { :f => f }
@@ -0,0 +1,4 @@
+%ul
+ - @tables.each do |table|
+ %li= link_to table, root_table_table_manage_index_path(table)
+
@@ -0,0 +1,7 @@
+ActionController::Routing::Routes.draw do |map|
+ map.namespace :root_table do |root_table|
+ root_table.resources :tables, :only => :index do |r|
+ r.resources :manage, :except => :show
+ end
+ end
+end
@@ -0,0 +1 @@
+# Install hook code here
@@ -0,0 +1,87 @@
+module RootTable
+
+ module ActiveRecord
+
+ attr_accessor :validations_for_root_table_added #:nodoc:
+
+ # Define this model to be a root table for another model.
+ #
+ # Example:
+ #
+ # class Product < ActiveRecord::Base
+ # # nothing ...
+ # end
+ #
+ # class Category < ActiveRecord::Base
+ # root_table_for :product
+ # end
+ #
+ # In this example the *root* table is Category and the *target* model is
+ # Product. Just so we're clear in what we're talking about.
+ #
+ # The configuration options are:
+ #
+ # * +field+ - Which field of the root table is the displayed value. The
+ # default is :name.
+ # * +to+ - How is the relation called? On the target model you would
+ # normally say "belongs_to :foo, :foreign_key => "bar".
+ # Specify what :foo would be, if different from the convention.
+ # * +foreign_key+ - Just as you would specify the foreign_key in the
+ # belongs_to relation, if different from the relation name.
+ # * +order+ - Default order of the root table. Will be used for
+ # acts_as_list, if installed. Without acts_as_list, this defaults
+ # to :name (or whatever you set field to), with acts_as_list installed
+ # it will default to :position.
+ # * +validate+ - Set to false to disable automatic validations. Defaults
+ # to validating presence and uniqueness of +field+
+ # * +acts_as_list+ - Set to false if you have acts_as_list installed, but
+ # don't want to use it in this case.
+ def root_table_for(target_name, options = {})
+
+ # options and mutations
+ field = options[:field] || :name
+ to = options[:to] || self.name.underscore
+ foreign_key = options[:foreign_key] || "#{to}_id"
+ target = target_name.to_s.camelize.constantize
+ target_plural = target_name.to_s.pluralize.to_sym
+
+ # validations
+ if self.add_validations_for_root_table?(options)
+ self.validates_presence_of(field)
+ self.validates_uniqueness_of(field)
+ self.validations_for_root_table_added = true
+ end
+
+ # order and acts_as_list
+ order = options[:order] || :position
+ if self.acts_as_list?(options, order)
+ self.acts_as_list :column => order
+ else
+ order = options[:order] || field
+ end
+ self.default_scope :order => order
+
+ # relations
+ self.has_many(target_plural, :foreign_key => foreign_key)
+ target.belongs_to(to, :class_name => self.name, :foreign_key => foreign_key)
+ target.delegate(field, :to => to, :prefix => true, :allow_nil => true)
+
+ # debug
+ # [ target_name, options, field, to, self, foreign_key, target, target_plural, order ].each { |v| puts v.inspect }
+
+ end
+
+ def acts_as_list?(options, order) #:nodoc:
+ installed = ::ActiveRecord.const_defined?(:Acts) && ::ActiveRecord::Acts.const_defined?(:List)
+ opted = !options.has_key?(:acts_as_list) || !options[:acts_as_list]
+ has_order_key = self.column_names.include?(order.to_s)
+ installed and opted and has_order_key
+ end
+
+ def add_validations_for_root_table?(options) #:nodoc:
+ !self.validations_for_root_table_added && (!options.has_key?(:validate) || !options[:validate])
+ end
+
+ end
+
+end
@@ -0,0 +1,2 @@
+# Include hook code here
+ActiveRecord::Base.extend RootTable::ActiveRecord
@@ -0,0 +1,3 @@
+sqlite3:
+ :adapter: sqlite3
+ :dbfile: vendor/plugins/root_table/spec/db/root_table.sqlite3.db
Binary file not shown.
@@ -0,0 +1,11 @@
+ActiveRecord::Schema.define(:version => 0) do
+ create_table :products, :force => true do |t|
+ t.column :name, :string
+ t.column :description, :text
+ t.column :category_id, :integer
+ end
+ create_table :categories, :force => true do |t|
+ t.column :name, :string
+ t.column :position, :integer
+ end
+end
@@ -0,0 +1,49 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe RootTable do
+
+ context "default behaviour" do
+
+ it "should add a belongs_to relation on the target model"
+ it "should add a has_many relation on the source model"
+ it "should delegate a name field to the root table"
+ it "should validate presence of name"
+ it "should validate uniqueness of name"
+ it "should act as a list"
+
+ end
+
+ context "with a specified name" do
+
+ it "should add a belongs_to relation on the target model"
+ it "should add a has_many relation on the source model"
+ it "should delegate the specified name field to the root table"
+ it "should validate presence of name"
+ it "should validate uniqueness of name"
+ it "should act as a list"
+
+ end
+
+ context "without being a list" do
+
+ it "should add a belongs_to relation on the target model"
+ it "should add a has_many relation on the source model"
+ it "should delegate a name field to the root table"
+ it "should validate presence of name"
+ it "should validate uniqueness of name"
+ it "should not act as a list"
+
+ end
+
+ context "without validations" do
+
+ it "should add a belongs_to relation on the target model"
+ it "should add a has_many relation on the source model"
+ it "should delegate a name field to the root table"
+ it "should not validate presence of name"
+ it "should not validate uniqueness of name"
+ it "should act as a list"
+
+ end
+
+end
@@ -0,0 +1,20 @@
+begin
+ require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
+rescue LoadError
+ puts "You need to install rspec in your base app"
+ exit
+end
+
+plugin_spec_dir = File.dirname(__FILE__)
+ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
+
+databases = YAML::load(IO.read(plugin_spec_dir + "/db/database.yml"))
+ActiveRecord::Base.establish_connection(databases[ENV["DB"] || "sqlite3"])
+load(File.join(plugin_spec_dir, "db", "schema.rb"))
+
+class Category < ActiveRecord::Base
+end
+
+class Product < ActiveRecord::Base
+
+end
Oops, something went wrong.

0 comments on commit eff53f6

Please sign in to comment.