Hi everyone, I''ve just been looking at the associations.rb example (ActiveRecord). This seems like too much coding for what I need. I think I should just be able to establish a connection, and have all my classes spring into existence, based on whatever tables are in the database. To that end, I have been playing with some possible extensions to ActiveRecord. I have added the following method declaration to class AbstractAdapter: # Returns an array of table names (as strings). def get_table_names() end And then implemented it in class MysqlAdapter: def get_table_names select_all("SHOW TABLES") end To check my handywork, I wrote the following example: # This example uses the database created by the "associations" example. # Run that example first, to create some tables. require File.dirname(__FILE__) + ''/shared_setup'' logger = Logger.new(STDOUT) logger.info ''class_discovery example'' logger.info ''======================='' logger.info ''Tables:'' ActiveRecord::Base.connection.get_table_names.each { |table| puts table } logger.info ''That\''s all folks!'' Here''s the output: class_discovery example ======================Tables: Tables_in_activerecord_examplescompanies Tables_in_activerecord_examplespeople That''s all folks! Are Ruby arrays always one dimensional? What type are the elements of the array returned by select_all? I need to get just the table names, but I don''t really understand what I am doing. Adelle.
Hi Adelle, On 25.1.2005, at 13:15, Adelle Hartley wrote:> > Are Ruby arrays always one dimensional?No, not at all. Any array item can itself be an array, so you can create n-dimensional arrays if you wish: irb(main):005:0> a = [["a", "b"], ["c", "d"]] => [["a", "b"], ["c", "d"]] irb(main):006:0> a[0] => ["a", "b"] irb(main):007:0> a[0][0] => "a"> What type are the elements of the > array returned by select_all?Hash.From the api docs (http://rails.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/ AbstractAdapter.html#M000284): "Returns an array of record hashes with the column names as a keys and fields as values."> > I need to get just the table names, but I don''t really understand what > I am > doing. > > Adelle. > > > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- Jarkko Laine http://jlaine.net http://odesign.fi _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
Hi Jarkko,> > Are Ruby arrays always one dimensional? > > No, not at all. Any array item can itself be an array, so you > can create n-dimensional arrays if you wish:OK, so arrays can be nested, but are one dimensional at each level of nesting. I can cope with that.> > What type are the elements of the > > array returned by select_all? > > Hash.From the api docs > (http://rails.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/ > AbstractAdapter.html#M000284): > > "Returns an array of record hashes with the column names as a > keys and fields as values."Thanks! I now have a working definition for get_table_names (at least for the MysqlAdapter): def get_table_names result = Array.new n = 0 select_all("SHOW TABLES").each { |record| record.each_value { |value| result[n]=value; n=n+1 } } result end Now, I know that it is possible to do all kinds of neat things with reflection in Ruby, like examine all the objects in the current context, finding out an object''s methods at run-time, etc. Is it possible to add a class dynamically: ie, in the following code, I need to know what to put between { and }, so that I will end up with one class for each table in the database: ActiveRecord::Base.connection.get_table_names.each { |table| MagicallyCreateClass table } Is this possible? Adelle.
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Adelle Hartley wrote:> I think I should just be able to establish a connection, and have all my > classes spring into existence, based on whatever tables are in the database. > > To that end, I have been playing with some possible extensions to > ActiveRecord.This is cool. I have considered Inspector, a generic Rails model browser, since it''s easy to introspect on the database and intelligently create model classes on the fly. "Naked Objects" takes this approach to remove the UI layer from app development in favor of directly manipulating business objects. http://nakedobjects.org On the downside, you can''t customize dynamically created classes, but we can just as well generate code for them, as with "scaffold :model" versus "./script/generate scaffold model" Here''s the method I use for PostgreSQL 7.4 def table_names execute(<<-end_sql).flatten SELECT table_name FROM information_schema.tables WHERE table_catalog=''#{@connection.db}'' AND table_schema=''public'' AND table_type=''BASE TABLE'' end_sql end And for SQLite (2 and 3): def table_names execute(''.table'') end Now we need a Table class encapsulating name, columns, indexes, constraints, etc. jeremy -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.6 (Darwin) Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org iD8DBQFB9nROAQHALep9HFYRAtDHAJ9Z+egD3kyGUT7cjpyx+Kyt34Cc5QCguRub Tua0RFpnxqcWWcP/jT+tKik=hfni -----END PGP SIGNATURE-----
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Adelle Hartley wrote:> ActiveRecord::Base.connection.get_table_names.each { |table| > MagicallyCreateClass table } > > Is this possible?Sure: connection.table_names.each do |table_name| class_name = table_name.camelize unless Object.const_defined?(class_name) eval "class #{class_name} < ActiveRecord::Base; end" end end jeremy -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.6 (Darwin) Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org iD8DBQFB9pdyAQHALep9HFYRAvVYAJ47HxwcSbh6CCN8st5oQOc4mPF7lwCdEiK3 f6M5iFVhqF82jsc+ItmT+Q4=DtZO -----END PGP SIGNATURE-----
Jeremy Kemper wrote:> Adelle Hartley wrote: >>ActiveRecord::Base.connection.get_table_names.each { |table| >>MagicallyCreateClass table } >>Is this possible? > > Sure: > connection.table_names.each do |table_name| > class_name = table_name.camelize > unless Object.const_defined?(class_name) > eval "class #{class_name} < ActiveRecord::Base; end" > end > endWhile this works it''s a bit nasty and can be a little confusing when somebody uses an invalid class name. I tend to avoid eval() when I can and use some of the higher level ways of doing meta stuff. In this case you could also do the following: connection.table_names.each do |table_name| class_name = table_name.camelize unless Object.const_defined?(class_name) Object.const_set(class_name, Class.new(ActiveRecord::Base) do one_to_one_relations.each do |name| has_one name.intern end def foo() end # and so on end) end end
Florian Groß wrote:> > While this works it''s a bit nasty and can be a little > confusing when somebody uses an invalid class name. I tend to > avoid eval() when I can and use some of the higher level ways > of doing meta stuff. In this case you could also do the following: > > connection.table_names.each do |table_name| > class_name = table_name.camelize > unless Object.const_defined?(class_name) > Object.const_set(class_name, Class.new(ActiveRecord::Base) do > one_to_one_relations.each do |name| > has_one name.intern > end > > def foo() end > # and so on > end) > end > endThanks. You probably saved me some frustration further down the track. Adelle.
Jeremy Kemper wrote:> This is cool. I have considered Inspector, a generic Rails > model browser, since it''s easy to introspect on the database > and intelligently create model classes on the fly. "Naked > Objects" takes this approach to remove the UI layer from app > development in favor of directly manipulating business > objects. http://nakedobjects.orgDo you have a link for Inspector?> On the downside, you can''t customize dynamically created > classes,It depends on what you mean by customize. My example below does what I''d expect it to: # This example uses the database created by the "associations" example. # Run that example first. require File.dirname(__FILE__) + ''/shared_setup'' logger = Logger.new(STDOUT) logger.info ''class_discovery example'' logger.info ''======================='' logger.info ''Tables:'' ActiveRecord::Base.connection.table_names.each { |table| puts table } logger.info ''Creating classes:'' ActiveRecord::Base::pluralize_table_names = false ActiveRecord::Base.connection.table_names.each do |table_name| class_name = table_name.camelize logger.info '' -- class ''+class_name unless Object.const_defined?(class_name) Object.const_set(class_name, Class.new(ActiveRecord::Base) do ActiveRecord::Base.connection.one_to_one_relations(table_name).each do |name| has_one name.intern end # define a method, that will apply to all # generated classes. def foo() ''foo'' end # define another method. The source code for # this method could come from a database table. eval(''def greet() \''And a good day to you to.\'' end'') end) end end # Add a method that applies to a single generated class. class People def insult() ''I am rubber, you are glue.'' end end first_person = People.find_first(1) logger.info ''First person in database is: '' + first_person.name logger.info first_person.foo logger.info first_person.greet logger.info first_person.insult logger.info ''That\''s all folks!'' Adelle.
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Adelle Hartley wrote:> Jeremy Kemper wrote: >>This is cool. I have considered Inspector, a generic Rails >>model browser, since it''s easy to introspect on the database >>and intelligently create model classes on the fly. "Naked >>Objects" takes this approach to remove the UI layer from app >>development in favor of directly manipulating business >>objects. http://nakedobjects.org > > Do you have a link for Inspector?No, it''s still just a gleam in my eye, but I think it''d be an absolutely killer app for Rails.>>On the downside, you can''t customize dynamically created >>classes, > It depends on what you mean by customize. > My example below does what I''d expect it to:These models are indeed customizable, but writing the classes is so little code in the first place; why not create them? Here''s a snippet that will generate basic models for every table in your database. It assumes a #tables instance method on the database adapter. $ cat script/old_generate_models #!/usr/bin/env ruby require File.dirname(__FILE__) + ''/../config/environment'' ActiveRecord::Base.establish_connection(ARGV.shift || ''development'') ActiveRecord::Base.connection.tables.each do |table| `#{File.dirname(__FILE__)}/generate model #{table.singularize}` end Currently, this *overwrites* every model, unit test, and fixture: not acceptable. The new generators (http://dev.rubyonrails.com/ticket/487) ask whether to overwrite by default and have --skip and --force options. $ cat script/generate_models #!/usr/bin/env ruby require File.dirname(__FILE__) + ''/../config/environment'' require ''rails_generator'' require ''rails_generator/scripts/generate'' require ''rails_generator/simple_logger'' Rails::Generator::Base.logger = Rails::Generator::SimpleLogger.new(STDOUT) ActiveRecord::Base.establish_connection(ARGV.shift || ''development'') ActiveRecord::Base.connection.tables.each do |table| Rails::Generator::Scripts::Generate.new.run([''--skip'', ''model'', table.singularize]) end I hope to do more introspection on the database to intelligently create associations also, so in the end you can just do $ ./script/generate schema development I have something working now, but it is kind of dumb since it only looks at table and column names to discern associations when it could just look at foreign key references. I hate wizards, but love this; what gives? jeremy -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.6 (Darwin) Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org iD8DBQFB98piAQHALep9HFYRAppyAKC0qQuaSxgcM7GGWdMB1J100C95owCglPm+ prGtkUnNGyv9dAXz5Gw+4s4=NYDY -----END PGP SIGNATURE-----