Lugovoi Nikolai
2005-Sep-19 08:26 UTC
DRY for ActiveRecord w/PostgreSQL : defaults and validations
As I''ve already posted some ideas to this list (see http://article.gmane.org/gmane.comp.lang.ruby.rails/22231), here''s continuation. I think, that properly developed DB schema has to include all (or almost all) constraints and validation rules for our particular domain data model. Rails gives very handy and useful ways to support various callbacks and validations on data model objects, but as for me, some points and solutions are neat when business rules and data integrity are held rather in application code than in DBMS (as seen in many PHP+MySQL apps). Part of such issues can be more effectively handled on DBMS server side (I mean triggers, proper referential integrity support, stored procedures etc.). In PostgreSQL practically full information about schema, intertable relationships and constraints can be extracted and injected to Rails app. Why to repeat yourself? If data constraints are already in schema, why not query DB to validate data before insert or update? Why not query DB for record defaults defined in schema? Why not retrieve associations defined by foreign keys from schema and inject them? So, here are my ideas. --------------- 1. ActiveRecord::Base.default_new - creates record, filled with defaults, defined in DB. It can be done in such manner: rows=exec(DEFAULTS_query) # see queries below new_record=find_by_sql("SELECT #{rows.join('','')} ") Benefits: * no need to convert db defaults to Ruby expr, * values even for complex defaults with functions calls can be easily retrieved * autogenerated serial ids come from db -------------- 2. Validation on INSERT OR UPDATE directly against database table constraints: a) value type and NOT NULL constraints can be checked in app from column definitions (and already are?) b) char(n)/varchar(n) size violations can be checked in app, as part of type check when setting attr value c) CHECK, UNIQUE, PRIMARY KEY, FOREIGN KEY constraints can be checked against "VIRTUAL" row, see below Example: CREATE TABLE Groups ( id serial primary key, name varchar(200) not null ); INSERT INTO GROUPS VALUES(12); CREATE TABLE Users ( id serial, login varchar(64) not null, group_id integer not null, birth_date date, constraint pkey_user primary key (id), constraint fkey_user_group foreign key (group_id) references groups (id) on delete cascade, constraint uniq_user_login unique(login), constraint chek_user_age check (birth_date + ''18 years''::interval < now()) ); Let new record be (100, ''smith'', 12, ''1986-10-30'') It can be checked against table constraints before really doing insert. Step 1. Construct SQL query t represent a virtual record: SELECT 100::integer as id, ''smith''::character varying(64) as login, 12::integer as group_id, ''1986-10-30''::date as birth_date Step 2. Construct predicate list query to check constraints: SELECT (birth_date + ''18 years''::interval < now()) as VALID_chek_user_age, NOT EXISTS (SELECT 1 FROM users u WHERE u.login = virt.login) as VALID_uniq_user_login, EXISTS (SELECT 1 FROM groups g WHERE g.id = virt.group_id) as VALID_user_group, NOT EXISTS (SELECT 1 FROM users u WHERE u.id = virt.id) as VALID_pkey_user Step 3. Joining above two :) SELECT -- CHECK constraint (birth_date + ''18 years''::interval < now()) as VALID_chek_user_age, -- UNIQUE constraint NOT EXISTS (SELECT 1 FROM users u WHERE u.login = virt.login) as VALID_uniq_user_login, -- FOREIGN KEY constraint EXISTS (SELECT 1 FROM groups g WHERE g.id = virt.group_id) as VALID_user_group, -- PRIMARY KEY constraint NOT EXISTS (SELECT 1 FROM users u WHERE u.id = virt.id) as VALID_pkey_user FROM (SELECT 100::integer as id, ''smith''::character varying(64) as login, 12::integer as group_id, ''1986-10-30''::date as birth_date ) as VIRT; So, we can check if record is valid and display all possible flaws that will lead DB to exception when doing INSERT. Well, for UPDATE, some changes needed: 1. predicate for PRIMARY KEY constraint is changed to EXISTS. 2. UNIQUE constraints will look smth like this: NOT EXISTS (SELECT 1 FROM users u WHERE u.login = virt.login AND u.id <> virt.id) 3. FOREIGN and CHECK w/o changes. A possible special cases here are excluding BYTEA fields and FKEY checks where fields with permitted NULL occur. In Rails model code it could look like this: class User < ActiveRecord::Base set_new_db_defaults true # see first idea on defaults # validate_against_db (when, constraint_list, messages) validate_against_db :create, :all, [:pkey_user => ''Already exists!'', :uniq_user_login => ''Login is already used!'', :fkey_user_group => ''Cannot add user to nonexistent group'', :chek_user_age => ''Too young to die!'' ] end --------------------- 3.REF_FROM, REF_TO associations. Information about foreign key constraints can be used to generate scaffold models with ref_from, ref_to associations that can be then easily converted to more semantic has_one, has_many, belongs_to, has_and_belongs_to_many. A more detailed study may show that it can be possible to fish them out directly? ---Basic Schema investigation queries:--- COLUMN DEFINITIONS: select attname as name, format_type(atttypid, atttypmod) as sql_type, attnotnull as not_null, pg_get_expr(adbin, adrelid) as default_def from pg_attribute left join pg_attrdef on (attrelid = adrelid and adnum=attnum) where attrelid = :TABLE_NAME ::regclass and not attisdropped and attnum>0; DEFAULTS: select pg_get_expr(adbin, adrelid) || ''::'' || format_type(atttypid, atttypmod) || '' as '' || quote_ident(attname) as def_v from pg_attribute left join pg_attrdef on (attrelid = adrelid and adnum=attnum) where attrelid = :TABLE_NAME ::regclass and not attisdropped and attnum>0 and atthasdef TABLE CONSTRAINTS: select conname, contype, pg_get_constraintdef(oid) as condef from pg_constraint where conrelid = :TABLE_NAME ::regclass; FOREIGN KEYS: select conname, contype, pg_get_constraintdef(oid) as condef, (select relname from pg_class c1 where c1.oid = conrelid) as ref_from_table, (select relname from pg_class c2 where c2.oid = confrelid) as ref_to_table from pg_constraint where conrelid = :TABLE_NAME ::regclass or confrelid = :TABLE_NAME :: regclass and contype = ''f''; ------------------------------------ Probably, if this list isn''t the proper place to this post, suggest another one :)
Kev Jackson
2005-Sep-19 09:13 UTC
Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
> > > In PostgreSQL practically full information about schema, intertable > relationships and constraints can be extracted and injected to Rails app.What about other RDBMSs that rails supports?> > Why to repeat yourself? If data constraints are already in schema, > why not query DB to validate data before insert or update? > Why not query DB for record defaults defined in schema? > Why not retrieve associations defined by foreign keys from schema and > inject them?[snip ideas] I agree with the DRY principle, but I''m thinking that if you do a query to check the constraints for every insert, then you''ll be bogging the database down with two calls for every insert instead of 1 call. Just something to consider. Although the fact that the schema does define the constraints should mean that the developer shouldn''t have to repeat these in the model code. Kev
Lugovoi Nikolai
2005-Sep-19 09:39 UTC
Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
Kev Jackson wrote:>> In PostgreSQL practically full information about schema, intertable >> relationships and constraints can be extracted and injected to Rails app. > > What about other RDBMSs that rails supports? >Well, I don''t have much experience with other SQL DBMS, but SQL standard includes "information schema", so it can be possible with other DBMS also, if the feature is properly supported.> > I agree with the DRY principle, but I''m thinking that if you do a query > to check the constraints for every insert, then you''ll be bogging the > database down with two calls for every insert instead of 1 call. Just > something to consider. Although the fact that the schema does define > the constraints should mean that the developer shouldn''t have to repeat > these in the model code. > > KevAs for me it''s better to make one more constraint check query to database than handling weird exception messages (or even exposing them to end users). Though, if one doesn''t care neither about data integrity(primary reason) nor friendly UI(keep users with this software), validations aren''t necessary at all and can be avoided :) WBR, Lugovoi Nikolai
Michael Schuerig
2005-Sep-19 10:30 UTC
Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
On Monday 19 September 2005 11:39, Lugovoi Nikolai wrote:> As for me it''s better to make one more constraint check query to > database than handling weird exception messages (or even exposing > them to end users).Before reinventing anything, have a look at http://dev.rubyonrails.org/ticket/861 Michael -- Michael Schuerig Most people would rather die than think. mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org In fact, they do. http://www.schuerig.de/michael/ --Bertrand Russell
Lugovoi Nikolai
2005-Sep-19 11:02 UTC
Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
Michael Schuerig wrote:> On Monday 19 September 2005 11:39, Lugovoi Nikolai wrote: > >>As for me it''s better to make one more constraint check query to >>database than handling weird exception messages (or even exposing >>them to end users). > > > Before reinventing anything, have a look at > > http://dev.rubyonrails.org/ticket/861 > > Michael >Tnahks, but JS client-side validation can be bypassed, and server-side then is also required. And after all there are still schema constraints that DBMS has to check :) I wouldn''t rely on JS client-side validation, though it can be more user friendly and use less traffic. WBR, Lugovoi Nikolai
Alex Young
2005-Sep-19 11:56 UTC
Re: Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
Nicholas Van Weerdenburg wrote:> There has been a few long threads on the philosophy of dbms > constraints versus application-logic constraints. IIRC correctly, > application-logic validation was the philosophical direction, being > DHH''s preference. > > Part of the influence on this was, I think, MySQL''s only rather recent > support of constraints in the DBMS. > > The other was that an OO domain model can be hard to constrain in a > RDMS constraint model. > > NickMy view on this (for what it''s worth) is that DBMS constraints should be used if they''re available, but that it should be possible to put them in the application if not - ideally, this should be an transparent fall-back, but I can see that breaking in lots of nasty ways. My reasoning is that the DBMS constraint code is much more mature (in general), and therefore likely to be far better tested, than new application-level code (with no disrespect intended to its authors, naturally). It''s also likely to be more efficient, but that''s a tangential issue. Going the other way, it would be *extremely* nice if the DB wrappers could push AR-defined constraints down to the DB level if it detected that they were available (ALTER TABLE... et al), but signal back that they were not if necessary, so that AR could handle it at the application level for those running on a feature-poor backend. Am I totally off-base here? I can''t help but think that being able to say :dependent in AR is far clearer than having to say ''FOREIGN KEY...'' in SQL, in part because you''re then defining the cascade in the same place as the relationship itself, and with a possible push-down from the wrapper layer, you get the best of both worlds. Thoughts? Comments? Rotten fruit? Pointers for where this might be patchable-in? -- Alex
Nicholas Van Weerdenburg
2005-Sep-19 12:29 UTC
Re: Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
On 9/19/05, Lugovoi Nikolai <meadow.nnick-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Michael Schuerig wrote: > > On Monday 19 September 2005 11:39, Lugovoi Nikolai wrote: > > > >>As for me it''s better to make one more constraint check query to > >>database than handling weird exception messages (or even exposing > >>them to end users). > > > > > > Before reinventing anything, have a look at > > > > http://dev.rubyonrails.org/ticket/861 > > > > Michael > > > > Tnahks, but JS client-side validation can be bypassed, and server-side > then is also required. And after all there are still schema constraints > that DBMS has to check :) > > I wouldn''t rely on JS client-side validation, though it can be more user > friendly and use less traffic. > > WBR, Lugovoi Nikolai > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >There has been a few long threads on the philosophy of dbms constraints versus application-logic constraints. IIRC correctly, application-logic validation was the philosophical direction, being DHH''s preference. Part of the influence on this was, I think, MySQL''s only rather recent support of constraints in the DBMS. The other was that an OO domain model can be hard to constrain in a RDMS constraint model. Nick -- Nicholas Van Weerdenburg
Grant Johnson
2005-Sep-19 13:38 UTC
Re: Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
> > > >There has been a few long threads on the philosophy of dbms >constraints versus application-logic constraints. IIRC correctly, >application-logic validation was the philosophical direction, being >DHH''s preference. > >Part of the influence on this was, I think, MySQL''s only rather recent >support of constraints in the DBMS. > >The other was that an OO domain model can be hard to constrain in a >RDMS constraint model. > >Nick > >That said, would it make sense, when generating a model, to place any database constraints and relationships in the model? not null=validates presense of, char(2)= validates length of, foriegn key=belongs to, etc.
Alex Young
2005-Sep-19 13:51 UTC
Re: Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
Lugovoi Nikolai wrote:> Basically, there are two choices where to put business logic and how > much of it will go to DB or app code (in any case, portions will be > present in both)But we should minimise the amount in one, and maximise it in the other...> So, if one''s model is primarily an Object-Oriented one, constraints are > defined and handled by classes and methods, and if data model grounds > mainly, say, on relational theory, constraints are defined and handled > by RDBMS (yes, we don''t yet have one, only SQL DBMS ;-( ).That''s what I''m saying, really. Given that AR requires that we put some model definition in ruby, why not have the option to put it all there? That way we can compensate for dodgy back-ends when we have to, without endangering the data.> My view of validations - keep software more fool-proof (but that''s the > primary goal of constraint) and more luser-friendly.I find that the latter comes through clarity and consistency of expression.> Clearer? Maybe, as for developer. > More reliable? I doubt, unless such constraints are pushed from AR to > DB.That''s exactly what I''m proposing, although I can now see a slight hitch: What should happen when there''s a constraint already defined on the schema, and the AR model has a conflicting definition? My instinct says to fail hard and as early as possible under those circumstances.> And, as for me, it''s rather scary to develop serious apps against poor > DBMS, though even SQLite can be appropriate sometimes.I couldn''t agree more. I''d like to be able to develop on a rock-solid platform, but let the user be able to run a shoddy back-end if their circumstances dictate it, and suffer as little as possible for it. To take a very pertinent example, I''m currently developing a PHP site that''s going to be using MySQL 4.0.17 because the host that my client uses refuse to upgrade, and they refuse to go with a different host. I can''t use InnoDB tables throughout for a number of reasons, which means transactions and foreign key constraints can''t be reliably supported by the DB. Does this mean they shouldn''t have them? Hell no! -- Alex
Michael Schuerig
2005-Sep-19 14:11 UTC
Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
On Monday 19 September 2005 15:38, Grant Johnson wrote:> That said, would it make sense, when generating a model, to place any > database constraints and relationships in the model? not > null=validates presense of, char(2)= validates length of, foriegn > key=belongs to, etc.For what it''s worth, that''s what I''m doing in BoilerPlate[*] for validates_presence_of and validates_length_of Michael [*] http://www.schuerig.de/michael/boilerplate/ -- Michael Schuerig Nothing is as brilliantly adaptive mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org as selective stupidity. http://www.schuerig.de/michael/ --A.O. Rorty, The Deceptive Self
Lugovoi Nikolai
2005-Sep-19 14:25 UTC
Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
Alex Young wrote: > Going the other way, it would be *extremely* nice if the DB wrappers > could push AR-defined constraints down to the DB level if it detected > that they were available (ALTER TABLE... et al), but signal back that > they were not if necessary, so that AR could handle it at the > application level for those running on a feature-poor backend. > Basically, there are two choices where to put business logic and how much of it will go to DB or app code (in any case, portions will be present in both), and the answer to "So, what''s right, what to choose?" is simple: "it depends" :) Agile tools could provide options to inject constraints from DB schema to ORM-based model and vice versa, and developer can choose where to take the gun to shoot himself. The rule is simple: for one application there has to be single data model, and therefore single place to put constraints (if your model isn''t constrained, you must be The God or an ignorant idiot :). So, if one''s model is primarily an Object-Oriented one, constraints are defined and handled by classes and methods, and if data model grounds mainly, say, on relational theory, constraints are defined and handled by RDBMS (yes, we don''t yet have one, only SQL DBMS ;-( ). My view of validations - keep software more fool-proof (but that''s the primary goal of constraint) and more luser-friendly.> Am I totally off-base here? I can''t help but think that being able to > say :dependent in AR is far clearer than having to say ''FOREIGN KEY...'' > in SQL, in part because you''re then defining the cascade in the same > place as the relationship itself, and with a possible push-down from the > wrapper layer, you get the best of both worlds. >Clearer? Maybe, as for developer. More reliable? I doubt, unless such constraints are pushed from AR to DB. Data survives apps and often is reused through different apps, unless one isn''t aware. And, as for me, it''s rather scary to develop serious apps against poor DBMS, though even SQLite can be appropriate sometimes. WBR, Lugovoi Nikolai
Lugovoi Nikolai
2005-Sep-19 14:29 UTC
Re: DRY for ActiveRecord w/PostgreSQL : defaults and validations
Grant Johnson wrote:> That said, would it make sense, when generating a model, to place any > database constraints and relationships in the model? not > null=validates presense of, char(2)= validates length of, foriegn > key=belongs to, etc.well, i think it can be easily done w/o manually written additional code in your model. WBR, Lugovoi Nikolai