ar-defaults for rails 2.2.2

useful?

http://drawohara.com/post/78208216/rails-activerecord-defaults-for-rails-2-2-2

a @ http://codeforpeople.com/

It seems this kind of code incites the use of database specific SQL all over the place. Moving these default settings in a before_save and extending the connection adapters (in a /lib/adapters_ext/... folder) to get the values seems like a cleaner way. There can also be transactional issues with this kind of code so it should be clearly stated as *exceptional*.

Gaspard

well that is indeed true - but sometimes you absolutely need this functionaliy. currently, with ar, you cannot even save a record to a postgresql field which is both non-null and has no default value. ar will try to insert 'NULL' into the column and blows up. you absolutely need to be able to pass in DEFAULT at minimum - which requires non-quoting and non-typecasting of the value.

also your approach is impossible to provide data integrity with. this is a bug

transaction do   record.created_at = Time.now end

it's not obivous why until you do this

transaction do   10000.times do      record = Record.new      record.created_at = Time.now   end end

now all records will have slightly different times. of course databases have solved this for 20 years: the correct approach is

transaction do   10000.times do      record = Record.new      record.created_at = SQL('current_timestamp')   end end

currently the only fix is use, guess what, database specific sql in the create migration, but this is also another subtle bug (failing silently but doing the wrong thing)

create_table :foo do |t|   t.created_at :timestamp, :default => Time.now end

it's turtles all the way down - unless you use database functions you cannot retrieve database values. you can do super hacky stuff like

  now = connection.raw_connection.execute('select current_timestamp').first[0]

but it's really a mess.

anyhow - the issue i was solving with this is the 'not null but no default' issue which, imho, should be simple with something as large as ar. it's simply unrealistic and non-pragmatic to *never* be able to do anything literal with ar.

this post from a while ago

  http://drawohara.com/post/6677354/rails-activerecord-default-values

while now dated (doesn't work with current ar) is the most read on my blog: 30000 views, and i still get about 1 email per week regarding it, so i think this is a real problem. i know if had to hack ar to do something similar for every single rails app i've ever built, although it is the case that i deal with legacy dbs more that most people.

kind regards.

Could you explain this? It works just fine for me. You create a new model instance with that value set and it saves ok. Of course, you have to explicitly give the model that value for it to work in a non-null, defaultless field, but that's the whole point of making it non-null and defaultless in the schema.

sorry - typing too fast. i had *meant* to say a not null field with a default value is a no go for AR:

cfp:~/rails_root > cat db/migrate/20090214040409_create_models.rb class CreateModels < ActiveRecord::Migration   @tablename = 'models'

  def self.up     execute <<-sql       create table #{ @tablename } (          timestamp not null default current_timestamp       )     sql   end

  def self.down     execute <<-sql       drop table #{ @tablename };     sql   end end

cfp:~/rails_root > cat app/models/model.rb class Model < ActiveRecord::Base end

cfp:~/rails_root > ./script/runner 'p Model.create!' /Users/ahoward/rails_root/vendor/rails/railties/lib/commands/runner.rb: 47: /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/abstract_adapter.rb:188:in `log': PGError: ERROR: null value in column "foo" violates not-null constraint (ActiveRecord::StatementInvalid) : INSERT INTO "models" ("updated_at", "foo", "bar", "created_at") VALUES('2009-02-15 02:43:09.452285', NULL, NULL, '2009-02-15 02:43:09.452285') RETURNING "id" from /Users/ahoward/rails_root/vendor/ rails/activerecord/lib/active_record/connection_adapters/ postgresql_adapter.rb:503:in `execute'         from /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/postgresql_adapter.rb:1000:in `select_raw'         from /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/postgresql_adapter.rb:987:in `select'         from /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/abstract/database_statements.rb:7:in `select_all_without_query_cache'         from /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/abstract/query_cache.rb:62:in `select_all'         from /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/abstract/database_statements.rb: 13:in `select_one'         from /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/abstract/database_statements.rb: 19:in `select_value'         from /Users/ahoward/rails_root/vendor/rails/activerecord/lib/ active_record/connection_adapters/postgresql_adapter.rb:443:in `insert'          ... 18 levels...         from /Users/ahoward/rails_root/vendor/rails/railties/lib/ commands/runner.rb:47         from /opt/local/lib/ruby/site_ruby/1.8/rubygems/ custom_require.rb:31:in `gem_original_require'         from /opt/local/lib/ruby/site_ruby/1.8/rubygems/ custom_require.rb:31:in `require'         from ./script/runner:3

a simple fix, with 'ar-defaults.rb'

cfp:~/rails_root > cat app/models/model.rb class Model < ActiveRecord::Base   def after_initialize     self['foo'] = SQL('current_timestamp')   end end

cfp:~/rails_root > ./script/runner 'p Model.create!' #<Model id: 16, foo: "current_timestamp", bar: nil, created_at: "2009-02-15 02:44:14", updated_at: "2009-02-15 02:44:14">

bingo.

the fact is that it would be *extremely* difficult for AR to do the 'right thing' with dynamic default values like this. or we can have a ten line addition to handle all such cases with a simple work-around.

make sense?

cheers.

i threw this code up if you want to see: http://s3.amazonaws.com/drawohara.com.data/ar-defaults.tgz

you absolutely need to be able to pass in DEFAULT at minimum - which requires non-quoting and non-typecasting of the value.

How about not putting that column (and the meaningless NULL value) into the INSERT statement if it is a new record on a column with a default value function (as opposed to a default value constant, which is the norm)? Would that work?

If I understand what you're aiming for correctly, I think you may meet resistance to getting ar-defaults into Rails core, because non-constant default values is a thing that not many people use with an ORM layer, and does not fit well into the ORM lifecycle; for example, it is expected that when you initialize a new record, the attributes on that record should all be fit to display. If the attribute value isn't determined until the record gets inserted into the database, that's a no-go. (Similarly you'd need to reload the attributes after insert to find their values after save.) As a general rule, I think "we" like doing our model work in our app, rather than the DB - it's more of a choice we've made than a "this is the only right way to do it in the world" kind of statement, there's plenty of room to do it other ways.

IMHO, there'll always be people who need to step outside the box sometimes, but unfortunately core Rails libraries can't necessarily cater to all of them - we'd end up like Microsoft Word where everyone only uses 10%... but a different 10% each :).

But if there's something simple that we can do to make life easier for them, maybe that's a better bet, whether it's cleaning up internals slightly to make it easier to hook in plugins, or maybe if it's a small patch making changes like detecting "special" columns and leaving them out of inserts (if that works, as above).

Personally, I think the http://drawohara.com/post/78208216/rails-activerecord-defaults-for-rails-2-2-2 you posted looks pretty clean already, so I think that should just get released as a plugin. We've all got our own list of "10% of Microsoft Word" features we need from our own plugins, me included :).

How about not putting that column (and the meaningless NULL value) into the INSERT statement if it is a new record on a column with a default value function (as opposed to a default value constant, which is the norm)? Would that work?

well, it depends on on the db. with postgresql yes, with mysql the null is fine. the fact is that it would have to be determined per- connection adapter. that sounds like a lot of work to me. i know i cannot provide a patch for every connection adapter myself. you're advocating maintaining 10 or 20 bits of code which need to be tested against 10 or 20 dbs who themselves have changing apis. that's not very pragmatic which a 10 lines patch will handle all cases - IMHO.

If I understand what you're aiming for correctly, I think you may meet resistance to getting ar-defaults into Rails core, because non-constant default values is a thing that not many people use with an ORM layer, and does not fit well into the ORM lifecycle; for example, it is expected that when you initialize a new record, the attributes on that record should all be fit to display.

you mean outside of after_initialize, after_create, before_save, etc, etc? afaikt those are all about having dynamic attributes.

If the attribute value isn't determined until the record gets inserted into the database, that's a no-go. (Similarly you'd need to reload the attributes after insert to find their values after save.)

well - i fail to see how this is in any way different from an after_save hook. ar has tons is issues that require reloading - with the object itself and it's associations. anyone that's every writtten an application with more that a 'posts' and 'comments' table can attest to this. consider something at simple as 'created_at' and 'updated_at'.

also, your comments are simply not true (wrt 'no-go') with any case where a db default value exists - you will always have to save and reload the record to see the default - ar flat out does not handle this case - it (appropriately) let's the db do it.

As a general rule, I think "we" like doing our model work in our app, rather than the DB - it's more of a choice we've made than a "this is the only right way to do it in the world" kind of statement, there's plenty of room to do it other ways.

ar uses the db to generate ids because they are so important for RI to function. why should other RI constraints be any different?

it's worth nothing that my request is *precisely* motivated by the desire to stay in the app. i personally will not even use 'find_by_sql' in any of my code in any circumstance and have avoided using any sql in my own code for the last 4 or 5 applications. but recall that doing something as simple as 'find not in(list)' currently *requires* the use of sql with AR - it is not always possible to avoid sql.

IMHO, there'll always be people who need to step outside the box sometimes, but unfortunately core Rails libraries can't necessarily cater to all of them - we'd end up like Microsoft Word where everyone only uses 10%... but a different 10% each :).

But if there's something simple that we can do to make life easier for them, maybe that's a better bet, whether it's cleaning up internals slightly to make it easier to hook in plugins, or maybe if it's a small patch making changes like detecting "special" columns and leaving them out of inserts (if that works, as above).

hrm. i guess i don't feel like a 'not null default' column is out of the box in any way. it may be for blogging applications, but anyone who has been dropped into a data model with 200 tables with hardly find this paradigm out of the ordinary. please keep in mind that the current AR is a total FAIL in this case with some dbs - you cannot use it at *all*. consider explaining that to IT - it's not professional and makes rails look like a toy.

Personally, I think thehttp://drawohara.com/post/78208216/rails-activerecord-defaults-for-ra… you posted looks pretty clean already, so I think that should just get released as a plugin. We've all got our own list of "10% of Microsoft Word" features we need from our own plugins, me included :).

sure - it *was* good. now it does work at all. AR isn't designed to allow plugins like this to function across versions - they have to get to deep into messy internals and are therefore fragile. that old code no longer works as a result and i'm sure my new code will stop working soon too for the same reasons. this is not fault of AR - it's a complicated peice of code and i think it's unreasonable for it's maintainers to support an unchanging internal interface, which is what's required for plugins acting this level to function across versions.

this is precisely why i think AR needs to support literal values in general - then it *is* possible for a plugin to support this use case simply. it's the lack of literal sql values of any type that needs to be in core. both dm and sequel currently support sql literals, for instance in sequel you can say:

  dataset.update(:updated_at => :NOW.sql_function)   dataset.update(:updated_at => 'NOW()'.lit)

  dataset.update(:updated_at => "DateValue('1/1/2001')".lit)   dataset.update(:updated_at => :DateValue.sql_function('1/1/2001'))

any objection to having sql literal support (all ten lines of it) should apply equally to find_by_sql, connection.execute, set_table_name, etc. and yet all of those exists and are heavily used. to me it just seems idealistic and non-pragmatic.

my 2cts.

Latest approach (ar-literal)

http://s3.amazonaws.com/drawohara.com.data/ar-literals.tgz

# passing pure sql through to active-record is simply not supported, but # soometimes it's needed badly such as when you need to initialize a value # with a database function or provide a default as defined by the db. this # small hack, saved in RAILS_ROOT/lib/ar-sql.rb will provide a simple # mechanism to pass sql value through unaltered to active-record

You mean like the 'id' column?