4

In my Rails 4 app, which I am using on Heroku for production, I have set up a seeds.rb file for the initial set of data. As it is critical that a few records have specific primary ids, say in the 'activities' table, I have set those. All goes well.

UNTIL. Until I need to add a new activity to the activities table. Adding a new record will fail as many times as there are records in that table, while the sequencer catches up. (e.g. if I have three records, new record creation fails three times, succeeds on the fourth).

QUESTION: How can I get the primary ID counter on the Heroku postgres database to start after the highest primary id set in my seeds.rb file? If you don't know, what terms should I be googling?

Ideally, this solution would be automatic, and not something I have to open psql for each time I seed the database.

Thanks in advance.

steel
  • 11,883
  • 7
  • 72
  • 109

4 Answers4

3

The :id column is an auto-increment column. As such, you don't have control over what value it uses -- it just always uses the next id available in its internal counter. If this were me I'd find some other way to identify those few records than by :id. For example, maybe you can refer to the records by a :name or some other special attribute? (Don't forget to index the attribute you're finding by if you do go this route!)

Then, I'd use the seed_fu gem to perform idempotent seeding, as you seem to be wanting. With idempotent seeding, if a record exists already then it will be updated (if there are any updates to be performed; if not then it's a no-op). This way you don't have exceptions and you can always add new or update existing records.

pdobb
  • 17,688
  • 5
  • 59
  • 74
  • You wrote > it just always uses the next id available in its internal counter Question is, how do I force postgres or rails to use the next value after the highest primary key value, instead of using it's own counter? – Anwar Oct 16 '15 at 10:14
1

Auto Increment

Further to pdobb's answer, you'll benefit from reading up about auto_increment - when you insert data into a table with auto_increment columns, it will just add a number to the end of the last one. In normal circumstances, you don't have to be concerned with it at all

If you need to include items in particular ids, such as if you're referencing a specific record or something, my best advice is to change your system to not deal with specific primary_keys

--

Example

We use an options table with the CMS we created, and reference role_id's from the options table (which we seed):

enter image description here

This is populated from our db/seeds.rb file:

Option.create({name: "role", value: "admin"})
Option.create({name: "role", value: "moderator"})
Option.create({name: "role", value: "author"})

Notice no reference to id?

This means we can reference the role as follows:

#app/models/profile.rb
belongs_to :role, -> { where(name: "role") }, class_name: "Option", foreign_key: "role"

def role
  role_name = Option.find self[:role]
  role_name.value
end

So we store the role id in our role column; and access that with a custom instance method which overrides the getter made by Rails!

So if I call

current_user.profile.role # -> in DB, will have id stored, but will output "value" of that

--

This means we're able to assign an id automatically!

This works because even if you remove the preceding items, the id of the role records will still remain the same. You don't need to worry about your auto_increment column

Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • I'm very intrigued but I don't quite get what the conclusion is here. I don't understand the Profile/Option relationship... namely the `foreign_key` option and how this implies being able to "assign an `id` automatically." I get that... Profile points to a role which comes from the Option class scoped by a `name` of `"role"`. But then what? Is there a Role model still that this reference somehow alludes to? – pdobb May 28 '14 at 12:10
  • Is there a *profiles.role* column which contains one of `%w[admin moderator author]`? But then... what? You still have an Option object as your "role" as a Profile, right? – pdobb May 28 '14 at 12:17
  • Sorry I forgot to put in the rest (it's some cool coding wizardry we did) – Richard Peck May 28 '14 at 12:28
  • Updated for you dear sir – Richard Peck May 28 '14 at 12:33
  • Thanks, @Rich! Still trying to figure it out, though. So you said "which overrides the **setter** made by Rails" -- do you mean **getter**? Given the `role` method you've defined, what's the point of the `belongs_to`? Does the `belongs_to` interact with your custom `role` method due to the `:foreign_key` option having the same name? Or is the `belongs_to` just used as a setter? And finally, is there a _Role_ model or no? (Is the role just a string value in the end?) – pdobb May 28 '14 at 12:48
  • LOL sorry, I actually need to work out the difference. You're right - the **getter** (blushes) – Richard Peck May 28 '14 at 12:50
  • Sorry, another oversight of mine. We have an `Option` model (works exactly the same as `wp_options` in Wordpress), and we store the `roles` in there. – Richard Peck May 28 '14 at 12:50
  • So the user's `profile` `belongs_to :role`; but `role` is inside `Option`. We use the custom method to take the `value` option of the `Option` record & output that instead – Richard Peck May 28 '14 at 12:51
  • No role model - the `Option` model acts as the model for it. The reason I used this example is because we use another model for an association, and rely on the `id` (`primary_key`) staying intact. I'm trying to demonstrate *why* you don't need to worry about setting the `primary_key` with `auto_increment` – Richard Peck May 28 '14 at 12:52
  • You sure? If you need any more clarification, just ask1 – Richard Peck May 28 '14 at 13:18
1

I ran into this problem with the acts_as_taggable_on gem for rails when it would throw duplicate tag error.

After I rails db:seed I run RAILS_ENV=development rake db:reset_pk_sequence so that the keys are reset.

# lib/tasks/tag_cleaner.rake
# run using 'RAILS_ENV=development rake db:reset_pk_sequence'

namespace :db do
  desc "reset the pk sequence"
  task :reset_pk_sequence => :environment do
    ActiveRecord::Base.connection.tables.each do |t|
      ActiveRecord::Base.connection.reset_pk_sequence!(t)
    end
  end
end

This seems to fix it but then I haven't tried in production as I don't use seed data for production.

Jay Killeen
  • 2,832
  • 6
  • 39
  • 66
0

The issue ended up being that I was running seeds.rb from

$ rake db:setup

db:setup recreates the database, and thus resets the counter. By switching to:

$ rake db:migrate
$ rake db:seed

I am able to modify my seed data in the production database without resetting the Postgres primary ID counter.

On a side note, so that this never happens accidentally, I have also created a rakefile that raises an error when rake db:setup is entered.

steel
  • 11,883
  • 7
  • 72
  • 109