Carl Lerche
2007-May-21 20:36 UTC
[rspec-users] Rails'' fixtures suck! But what about something like this?
Sorry about the very long email, but this is a hairy topic that''s been annoying me for some time and I decided to try to do something about. Also, if you got this twice, I apologize too, but it didn''t seem to have successfully gone out the first time. Background: ---------- I''ve been dealing with Rails for about a year and a half now. I''ve been using Rails'' built in testing framework, and it''s fine... until I really have to start dealing with the fixtures. I am currently working on an application that really focuses on selecting data using complex logic. The only way to test the application in a meaningful way is by using a large number of fixtures. This has become a real nightmare to manage and I almost am spending more time keeping the fixtures up to date than I am coding or testing. So, I took a moment, stepped back, and took a good look at how I could I could improve fixtures to make my life a lot easier. I have gotten a quick and dirty prototype of my solution running, but before I really roll up my sleeves and start polishing it up, I thought I would try to get some suggestions from the community. Overview: --------- In my experience, the current fixture framework has three main problems: - There is no way to create different version of fixtures. What I mean by this is that you only can have one users.yml file even if your tests depend on different initial conditions. What this usually means is that you will just keep tagging on to the end of users.yml even though it is not the most efficient way to do it. - Managing relationships between tables is a really big pain. One must manually keep track of IDs. This can quickly become a major headache. - There is no clean way to quickly generate data. If I want 50 records that I could easily generate in a loop, I still gotta write them by hand (at least, I wasn''t able to figure out how to add loops to my yaml files in a clean way). - There is a lot of repetition. No way to follow DRY. Solution: --------- + Versioned Fixtures First, to address the fixture "versions", I thought the easiest way to do this would be scenarios (I was inspired from fixture-scenarios). The way fixtures could be laid out is as follows: + specs |- fixtures |- global |- only_one_signed_up_user |- lots_of_posts_in_los_angeles |- ... All global fixtures (fixtures that should be loaded before every spec) go into global, then, for each different scenario, there is a directory created with the relevant fixtures in there. Then, in the specs, you could do the following ------------------------------- require File.dirname(__FILE__) + ''/../spec_helper'' describe User, :when => :only_one_signed_up_user do it "should be the only user" do User.count.should == 1 end end ------------------------------- The key part here would be the :when => :scenario_name option to the describe method. + Writing the fixtures Writing fixtures with YAML is quite ugly. I think a better way to do it would be using a DSL, but coming up with a good syntax is hard. This is where I am the most hesitant. I have two "options" and I was wondering if people could comment on them and give me some feedback and suggestions. - The first option is how I started out: http://pastie.caboo.se/63359 In this option, fixtures for multiple tables can be written in a single file. You would start out by using the _create_ method to tell the parser what table the fixtures are for and you could also specify :with, which would set default attributes for fixtures. You could also nest fixtures in order to easily scope them (see the last user fixture how I specified nested post fixtures). - The second option is what I currently prefer: http://pastie.caboo.se/63361 In this option, I keep the one table per file concept that is currently used. As such, we don''t need to specify what table we want to use before hand. I make available a with_options method which lets you set default attributes for fixtures. It''s also easy to create loops to automatically generate fixtures, as I show in the second half of the file. The f method is the actual method for creating a fixture. it takes a name as a string and a block in which the attributes of the fixtures are specified. However, the method_missing method is implemented to handle any missing methods and use that to create fixtures too as seen with fixture_name { ... } + Dealing with relationships I think what I hate the most about fixtures right now is having to deal with table relationships. Having to keep track of IDs myself is horrible. So, I thought that the easiest way to do this is making the table_name(:fixture_name) method available straight inside the fixtures. So, if I have a user model that has a location associated with it (let''s say a zip code, longitude, and latitude), instead of having to copy / paste that data across fixtures, I can do the following: ------------------------------- local_guy { zip_id zips(:portland) longitude zips(:portland).longitude latitude zips(:portland).latitude } ------------------------------- This is much easier to deal with than the other way around. This also introduces dependencies between fixtures, I thought the easiest way to handle it would be adding a fixture_order configuration: ------------------------------- Spec::Runner.configure do |config| config.fixture_order = :zips, :categories, :users, :posts end ------------------------------- + What''s not solved with this I haven''t figured out a good way to handle counter_cache columns. Right now, this would still require you to manually count how many fixtures you have associated with a parent and add that to the counter_cache column. Any ideas of a better way to do this? Well, that''s all I came up with. I''m going to keep hacking away at this and hopefully you all have some feedback to improve this concept. -- EPA Rating: 3000 Lines of Code / Gallon (of coffee)
Chris Hoffman
2007-Jul-22 12:39 UTC
[rspec-users] Rails'' fixtures suck! But what about something like this?
Have you tried just using fixture scenarios? Did it work? On 5/21/07, Carl Lerche <carl.lerche at gmail.com> wrote:> Sorry about the very long email, but this is a hairy topic that''s been > annoying me for some time and I decided to try to do something about. > Also, if you got this twice, I apologize too, but it didn''t seem to > have successfully gone out the first time. > > Background: > ---------- > > I''ve been dealing with Rails for about a year and a half now. I''ve > been using Rails'' built in testing framework, and it''s fine... until I > really have to start dealing with the fixtures. I am currently working > on an application that really focuses on selecting data using complex > logic. The only way to test the application in a meaningful way is by > using a large number of fixtures. This has become a real nightmare to > manage and I almost am spending more time keeping the fixtures up to > date than I am coding or testing. So, I took a moment, stepped back, > and took a good look at how I could I could improve fixtures to make > my life a lot easier. > > I have gotten a quick and dirty prototype of my solution running, but > before I really roll up my sleeves and start polishing it up, I > thought I would try to get some suggestions from the community. > > Overview: > --------- > > In my experience, the current fixture framework has three main problems: > > - There is no way to create different version of fixtures. What I mean > by this is that you only can have one users.yml file even if your > tests depend on different initial conditions. What this usually means > is that you will just keep tagging on to the end of users.yml even > though it is not the most efficient way to do it. > - Managing relationships between tables is a really big pain. One must > manually keep track of IDs. This can quickly become a major headache. > - There is no clean way to quickly generate data. If I want 50 > records that I could easily generate in a loop, I still gotta write > them by hand (at least, I wasn''t able to figure out how to add loops > to my yaml files in a clean way). > - There is a lot of repetition. No way to follow DRY. > > Solution: > --------- > > + Versioned Fixtures > > First, to address the fixture "versions", I thought the easiest way to > do this would be scenarios (I was inspired from fixture-scenarios). > The way fixtures could be laid out is as follows: > > + specs > |- fixtures > |- global > |- only_one_signed_up_user > |- lots_of_posts_in_los_angeles > |- ... > > All global fixtures (fixtures that should be loaded before every spec) > go into global, then, for each different scenario, there is a > directory created with the relevant fixtures in there. Then, in the > specs, you could do the following > > ------------------------------- > require File.dirname(__FILE__) + ''/../spec_helper'' > > describe User, :when => :only_one_signed_up_user do > > it "should be the only user" do > User.count.should == 1 > end > end > ------------------------------- > > The key part here would be the :when => :scenario_name option to the > describe method. > > + Writing the fixtures > > Writing fixtures with YAML is quite ugly. I think a better way to do > it would be using a DSL, but coming up with a good syntax is hard. > This is where I am the most hesitant. I have two "options" and I was > wondering if people could comment on them and give me some feedback > and suggestions. > > - The first option is how I started out: http://pastie.caboo.se/63359 > > In this option, fixtures for multiple tables can be written in a > single file. You would start out by using the _create_ method to tell > the parser what table the fixtures are for and you could also specify > :with, which would set default attributes for fixtures. > > You could also nest fixtures in order to easily scope them (see the > last user fixture how I specified nested post fixtures). > > - The second option is what I currently prefer: http://pastie.caboo.se/63361 > > In this option, I keep the one table per file concept that is > currently used. As such, we don''t need to specify what table we want > to use before hand. I make available a with_options method which lets > you set default attributes for fixtures. It''s also easy to create > loops to automatically generate fixtures, as I show in the second half > of the file. The f method is the actual method for creating a fixture. > it takes a name as a string and a block in which the attributes of the > fixtures are specified. However, the method_missing method is > implemented to handle any missing methods and use that to create > fixtures too as seen with fixture_name { ... } > > + Dealing with relationships > > I think what I hate the most about fixtures right now is having to > deal with table relationships. Having to keep track of IDs myself is > horrible. So, I thought that the easiest way to do this is making the > table_name(:fixture_name) method available straight inside the > fixtures. So, if I have a user model that has a location associated > with it (let''s say a zip code, longitude, and latitude), instead of > having to copy / paste that data across fixtures, I can do the > following: > > ------------------------------- > local_guy { > zip_id zips(:portland) > longitude zips(:portland).longitude > latitude zips(:portland).latitude > } > ------------------------------- > > This is much easier to deal with than the other way around. This also > introduces dependencies between fixtures, I thought the easiest way to > handle it would be adding a fixture_order configuration: > > ------------------------------- > Spec::Runner.configure do |config| > config.fixture_order = :zips, :categories, :users, :posts > end > ------------------------------- > > + What''s not solved with this > > I haven''t figured out a good way to handle counter_cache columns. > Right now, this would still require you to manually count how many > fixtures you have associated with a parent and add that to the > counter_cache column. Any ideas of a better way to do this? > > Well, that''s all I came up with. I''m going to keep hacking away at > this and hopefully you all have some feedback to improve this concept. > > -- > EPA Rating: 3000 Lines of Code / Gallon (of coffee) > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
court3nay
2007-Jul-22 16:55 UTC
[rspec-users] Rails'' fixtures suck! But what about something like this?
Please see http://dev.rubyonrails.org/tickets/6424 for further code on fixtures and foreign keys. You might also be interested in the ar_fixtures plugin from topfunky to dump fixtures from dev to yaml. In most cases I''m using mocks and stubbing rather than canned data. Its much easier IMO to create a bunch of objects as you need them. That being said, fixtures are far from a "solved problem" and I look forward to your plugin. ------- Courtenay On Jul 22, 2007, at 5:39 AM, "Chris Hoffman" <bosshoff at gmail.com> wrote:> Have you tried just using fixture scenarios? Did it work? > > On 5/21/07, Carl Lerche <carl.lerche at gmail.com> wrote: >> Sorry about the very long email, but this is a hairy topic that''s >> been >> annoying me for some time and I decided to try to do something about. >> Also, if you got this twice, I apologize too, but it didn''t seem to >> have successfully gone out the first time. >> >> Background: >> ---------- >> >> I''ve been dealing with Rails for about a year and a half now. I''ve >> been using Rails'' built in testing framework, and it''s fine... >> until I >> really have to start dealing with the fixtures. I am currently >> working >> on an application that really focuses on selecting data using complex >> logic. The only way to test the application in a meaningful way is by >> using a large number of fixtures. This has become a real nightmare to >> manage and I almost am spending more time keeping the fixtures up to >> date than I am coding or testing. So, I took a moment, stepped back, >> and took a good look at how I could I could improve fixtures to make >> my life a lot easier. >> >> I have gotten a quick and dirty prototype of my solution running, but >> before I really roll up my sleeves and start polishing it up, I >> thought I would try to get some suggestions from the community. >> >> Overview: >> --------- >> >> In my experience, the current fixture framework has three main >> problems: >> >> - There is no way to create different version of fixtures. What I >> mean >> by this is that you only can have one users.yml file even if your >> tests depend on different initial conditions. What this usually means >> is that you will just keep tagging on to the end of users.yml even >> though it is not the most efficient way to do it. >> - Managing relationships between tables is a really big pain. One >> must >> manually keep track of IDs. This can quickly become a major headache. >> - There is no clean way to quickly generate data. If I want 50 >> records that I could easily generate in a loop, I still gotta write >> them by hand (at least, I wasn''t able to figure out how to add loops >> to my yaml files in a clean way). >> - There is a lot of repetition. No way to follow DRY. >> >> Solution: >> --------- >> >> + Versioned Fixtures >> >> First, to address the fixture "versions", I thought the easiest way >> to >> do this would be scenarios (I was inspired from fixture-scenarios). >> The way fixtures could be laid out is as follows: >> >> + specs >> |- fixtures >> |- global >> |- only_one_signed_up_user >> |- lots_of_posts_in_los_angeles >> |- ... >> >> All global fixtures (fixtures that should be loaded before every >> spec) >> go into global, then, for each different scenario, there is a >> directory created with the relevant fixtures in there. Then, in the >> specs, you could do the following >> >> ------------------------------- >> require File.dirname(__FILE__) + ''/../spec_helper'' >> >> describe User, :when => :only_one_signed_up_user do >> >> it "should be the only user" do >> User.count.should == 1 >> end >> end >> ------------------------------- >> >> The key part here would be the :when => :scenario_name option to the >> describe method. >> >> + Writing the fixtures >> >> Writing fixtures with YAML is quite ugly. I think a better way to do >> it would be using a DSL, but coming up with a good syntax is hard. >> This is where I am the most hesitant. I have two "options" and I was >> wondering if people could comment on them and give me some feedback >> and suggestions. >> >> - The first option is how I started out: http://pastie.caboo.se/63359 >> >> In this option, fixtures for multiple tables can be written in a >> single file. You would start out by using the _create_ method to tell >> the parser what table the fixtures are for and you could also specify >> :with, which would set default attributes for fixtures. >> >> You could also nest fixtures in order to easily scope them (see the >> last user fixture how I specified nested post fixtures). >> >> - The second option is what I currently prefer: http://pastie.caboo.se/63361 >> >> In this option, I keep the one table per file concept that is >> currently used. As such, we don''t need to specify what table we want >> to use before hand. I make available a with_options method which lets >> you set default attributes for fixtures. It''s also easy to create >> loops to automatically generate fixtures, as I show in the second >> half >> of the file. The f method is the actual method for creating a >> fixture. >> it takes a name as a string and a block in which the attributes of >> the >> fixtures are specified. However, the method_missing method is >> implemented to handle any missing methods and use that to create >> fixtures too as seen with fixture_name { ... } >> >> + Dealing with relationships >> >> I think what I hate the most about fixtures right now is having to >> deal with table relationships. Having to keep track of IDs myself is >> horrible. So, I thought that the easiest way to do this is making the >> table_name(:fixture_name) method available straight inside the >> fixtures. So, if I have a user model that has a location associated >> with it (let''s say a zip code, longitude, and latitude), instead of >> having to copy / paste that data across fixtures, I can do the >> following: >> >> ------------------------------- >> local_guy { >> zip_id zips(:portland) >> longitude zips(:portland).longitude >> latitude zips(:portland).latitude >> } >> ------------------------------- >> >> This is much easier to deal with than the other way around. This also >> introduces dependencies between fixtures, I thought the easiest way >> to >> handle it would be adding a fixture_order configuration: >> >> ------------------------------- >> Spec::Runner.configure do |config| >> config.fixture_order = :zips, :categories, :users, :posts >> end >> ------------------------------- >> >> + What''s not solved with this >> >> I haven''t figured out a good way to handle counter_cache columns. >> Right now, this would still require you to manually count how many >> fixtures you have associated with a parent and add that to the >> counter_cache column. Any ideas of a better way to do this? >> >> Well, that''s all I came up with. I''m going to keep hacking away at >> this and hopefully you all have some feedback to improve this >> concept. >> >> -- >> EPA Rating: 3000 Lines of Code / Gallon (of coffee) >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users