52

I don't want to add schema.rb to .gitignore, because I want to be able to load a new database schema from that file. However, keeping it checked in is causing all sorts of spurious conflicts that are easily resolved by a fresh db:migrate:reset.

Basically I want a way to:

  1. Keep schema.rb in the repository for deploy-time database setup
  2. Keep schema.rb in '.gitignore' for general development

There would be one or two people responsible for updating schema.rb and knowing that it was correct.

Is there a way I can have my cake and eat it, too?

Paul
  • 16,255
  • 3
  • 33
  • 25
Otto
  • 18,761
  • 15
  • 56
  • 62
  • Why are there conflicts? Do other developers have different schemas? – Josiah I. Apr 10 '09 at 15:14
  • 7
    Developer A adds a migration, tests his stuff. Developer B adds another migration, tests his stuff. I merge both of them. At the very least it conflicts on the :version of the schema.rb. – Otto Apr 10 '09 at 15:17
  • doesn't a `db:migrate:reset`destroy all your data? – locoboy Jul 01 '14 at 05:47
  • @locoboy yep, but that's generally what I want in my dev environment. I generally have some seed data that will get me from empty schema to usable, known good, data set for testing purposes. – Otto Jul 02 '14 at 14:30
  • This is a pain in rails. Have you thought about using mergetool to resolve the conflicts? They usually are pretty simple, but the auto-merge doesn't know what to do with the version number change on on e line. – morefromalan Jul 10 '14 at 23:08
  • 1
    @morefromalan really, the issue with conflicts is lazy "other guys" who don't even bother resolving conflicts before committing the file. I suppose this makes for a technical solution to a personnel problem, but at least it works. :) – Otto Jul 14 '14 at 14:31
  • Sounds like what might really be needed is a process answer then - like code reviews where you encourage good (flag bad) merge practices – morefromalan Jul 14 '14 at 18:47
  • @morefromalan at the time, yeah. I like where I ended up, though. The initial migration that gets updated when we "roll up" migrations works rather well and never has conflict issues. – Otto Jul 15 '14 at 19:00
  • Can you elaborate on what you mean? I don't quite follow initial migrations and roll up migrations – morefromalan Jul 15 '14 at 21:23
  • @morefromalan The currently accepted answer describes it. The gist is, we ditch schema.rb and create a migration called 0_initial_schema.rb. It's just a script that loads the real initial schema. That way everyone regenerates schema.rb, but it's not thousands of migrations that are bound to break. – Otto Jul 17 '14 at 15:25
  • Please look at the answer I posted below. It is the "magic bullet" that lets you have your cake and eat it too. We've been using it with great success for several months with a dev team of about 30 engineers and I haven't seen any conflicts come up since we started using the gem. – jakeonrails Feb 24 '15 at 19:02
  • 2
    Possible duplicate of [What is the right approach to deal with Rails db/schema.rb file in GIT?](https://stackoverflow.com/questions/6450263/what-is-the-right-approach-to-deal-with-rails-db-schema-rb-file-in-git) – AMIC MING Sep 28 '17 at 21:53

9 Answers9

21

I'm afraid the magic solution you're looking for does not exist. This file is normally managed in version control, then for any conflicts on the version line just choose the later of the two dates. As long as you're also running all of the associated migrations nothing should get out of sync this way. If two developers have caused modifications to a similar area of schema.rb and you get conflicts in addition to the version then you are faced with a normal merge conflict resolution, but in my opinion these are normally easy to understand and resolve. I hope this helps some!

Adam Alexander
  • 15,132
  • 5
  • 42
  • 41
  • 2
    `schema.rb` went in ignore. You're right, there's no magic solution. Anything depending on files sometimes ignored vs sometimes not had all kinds of issues. I hope this doesn't bite me later. – Otto Aug 23 '09 at 17:32
9

One other thing you can do is use:

git update-index --assume-unchanged /path/schema.rb

This will keep the file in the repository but won't track changes. you can switch the tracking anytime by using:

git update-index --no-assume-unchanged /path/schema.rb
Headshota
  • 21,021
  • 11
  • 61
  • 82
  • I like this solution. My issue is that I'll frequently have migrations in multiple branches, and the order of the columns is out of order between developers (might only be a MySQL issue? Columns shouldn't have an order.) The only issue is remembering to turn it back on in the master branch. – Clinton Mar 26 '13 at 19:55
2

What has worked really well for me is to delete and .gitignore schema.rb and then have it regenerated for each developer when they rake db:migrate.

You can still achieve what you wanted without migrating from 0 and risking broken migrations from years ago by simply doing a "roll-up" of the migrations periodically. You can do this by:

  1. Run all outstanding migrations with rake db:migrate
  2. Taking the contents of your schema.rb in the ActiveRecord::Schema.define block
  3. Paste it into your initial_schema migration inside def up (overwriting what's already there)
  4. Delete all other migrations

Now your initial_schema migration is your starting point for new systems and you don't have to worry about conflicts in schema.rb that may not be resolved correctly. It's not magical, but it works.

Tom Harris
  • 270
  • 2
  • 4
  • Tom and @otto - I would argue that you should keep the migrations. And that if the point of Schema.db is as an alternate from migration sequences - which for some reason might be error prone -- then creating a schema.db equivalent from a series of migrations is counter to purpose. If you want to generate your own -- why not use whatever DBMS capability for exporting a schema as .SQL? – morefromalan Jul 17 '14 at 18:32
  • 2
    I'm not sure I follow. `schema.rb` (not .db) is used internally by rails for setting up the test db and doing a reset. Simply exporting to sql doesn't solve anything. Keeping years worth of migrations is also a non-starter. For various reasons, apps will not always migrate from 0 after many years of changes and hundreds of migrations. – Tom Harris Aug 04 '14 at 14:48
  • I would not do this. It's valuable to see changes to db/schema.rb in code review. – MattyB Apr 29 '16 at 00:18
  • Valuable or not, saying "Don't do that" doesn't answer the question asked. – Tom Harris May 08 '16 at 18:20
  • @MattyB I don't understand why changes to db/schema.rb are valuable to see in a code review. What do those changes tell you that seeing the migration won't? – dmur Feb 24 '17 at 01:43
  • @dmur db/schema.rb should represent what is in your production database. It's loaded every time you start your dev database from scratch, making old migrations eventually worthless. Further, every time you run a migration locally, rails just dumps the db contents in to db/schema. If you're working on multiple branches at a time, this means you could have extra or missing entries in db/schema.rb merged in to master. – MattyB Feb 27 '17 at 14:50
1

You could define a merge strategy. I've found this solution, but dont remember the source

[merge "railsschema"]
name = newer Rails schema version
driver = "ruby -e '\n\
    system %(git), %(merge-file), %(--marker-size=%L), %(%A), %(%O), %(%B)\n\
    b = File.read(%(%A))\n\
    b.sub!(/^<+ .*\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n=+\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n>+ .*/) do\n\
      %(ActiveRecord::Schema.define(:version => #{[$1, $2].max}) do)\n\
    end\n\
    File.open(%(%A), %(w)) {|f| f.write(b)}\n\
    exit 1 if b.include?(%(<)*%L)'"

put this "somewhere" and

git-config --global core.attributesfile "somewhere"
andrea
  • 3,515
  • 2
  • 22
  • 14
1

I built a gem to solve this problem.

It sorts columns, index names and foreign keys, removes excess whitespace and runs Rubocop for some formatting to unify the output of your schema.rb file.

https://github.com/jakeonrails/fix-db-schema-conflicts

After you add it to your Gemfile you just run rake db:migrate or rake db:schema:dump like normal.

jakeonrails
  • 1,885
  • 15
  • 37
1

Would it be sufficient to do a rake db:dump in a pre-commit git hook?

The following won't necessarily fix (1) or (2), but it might take care of the merging issue, and then maybe (1) and (2) go away.

dbrown0708
  • 4,544
  • 4
  • 25
  • 22
  • Not really, because when you create a migration and run db:migrate, you automatically get db:dump and it updates the schema.rb. The issue is that in day-to-day development everyone is changing the database in some way and ultimately you're stuck resolving conflicts in a generated file. – Otto Jul 29 '13 at 21:27
1

Instead of using .gitignore, use separate branches: Develop which omits schema.rb and Test and Deploy which include schema.rb. Only make code changes in the Develop branches and never merge from Test into Develop. Keep schema.rb in a separate branch:

Developer A             
    Develop      --------             
    Local Schema          \           Your Repo
    Test                    --------->    Dev A
                            --------->    Dev B
Developer B               /               Master
    Develop      --------                 Schema
    Local Schema                          Test
    Test                                  Deploy

In Git, branches are pointers to collections of file contents, so they can include or exclude particular files as well as track file versions. This makes them flexible tools for building your particular workflow.

Paul
  • 16,255
  • 3
  • 33
  • 25
  • Doesn't this mean that git merge develop from either test or deploy makes a merge commit vs. being able to always be a fast-forward? I like the idea, I'm just not 100% there with seeing how it works, yet. – Otto Apr 11 '09 at 14:39
  • Hi Otto. Your developers would pull your latest Master before pushing back to your integration repo, so your merges should be fast-forward (unless they have unresolved conflicts between them). Test & Deploy should simply be Master + Schema in your repo or Develop + Local Schema on the developers'. – Paul Apr 11 '09 at 15:39
  • 1
    IMHO the cure is worse than the disease here. I've tried git workflows where branches have different files in them, and it's never been worth the hassle. I'm sure there are cases where a heavy-handed approach like this is necessary, but managing schema.rb is not it. – gtd Sep 11 '10 at 22:12
0
  1. Commit schema.rb file.
  2. Run git pull (or continue with what you're doing)

Every time you migrate the database, the schema.rb file updates and appears in git status. When working on something and occasionally doing git pull, this can be annoying because you have to commit schema.rb file before pulling to resolve conflict. This means that every time you migrate the database, you need to commit schema.rb file.

Chemist
  • 967
  • 2
  • 15
  • 28
0

schema.rb should be tracked Git, of course.

I've just released this gem that can solve an issue with "conflicts" between branches for good.

The idea of that gem is simple. It keeps all migrated migrations inside tmp folder so that Git ignores them. It's just only your local story. These files are needed to roll back the "unknown" migrations being in another branch. Now, whenever you have an inconsistent DB schema due to running migrations in some other branch just run rails db:migrate inside the current branch and it will fix the issue automatically. The gem does all this magic automatically for you.

ka8725
  • 2,788
  • 1
  • 24
  • 39