Search code examples
sqlruby-on-railsrubysequel

Generate primary keys without creating the record


I want to be able to do something like

@foo = MyClass.new
5.times do
  @foo.things.build
end

But my @foo needs to have a primary key for this to work, Soo what is the best way to generate primary keys without creating the object?

The purpose for this is to be able to use nested forms more easely

form_builder.fields_for :things do ...

Solution

  • What you probable want is NestedAttributes

    Nested attributes allow you to save attributes on associated records through the parent. By default nested attribute updating is turned off, you can enable it using the accepts_nested_attributes_for class method. When you enable nested attributes an attribute writer is defined on the model.

    The implementation is different between each ORM, here is for sequel and ActiveRecord

    NOTE: Full tutorial also available at Nerdgem

    Sequel impementation

    Imagine there is a Project class that has many tasks

    class Project < Sequel::Model
      one_to_many :tasks
    end
    
    class Task < Sequel::Model
      many_to_one :project
    end
    

    To enable the nested attributes you will need include two plugins for the Project class

    • Sequel::Plugins::NestedAttributes: allows you to create, update, and delete associated objects directly by calling a method on the current object. Nested attributes are defined using the nested_attributes class method:

    • Sequel::Plugins::InstanceHooks: which is a dependency of NestedAttributes

    You can find really good doc on the plugin site

    Project.plugin :instance_hooks
    Project.plugin :nested_attributes
    

    After that is done you can call the nested_attributes method on the desired class

    Project.nested_attributes :tasks
    

    Now you can do this

    p = Project.new(:title=>'Project')
    p.tasks_attributes = [{:title=>'First Task'}, {:title=>'Second Task'}]
    puts p.tasks.inspect
    # It will output this
    # [#<Task @values={:title=>"First Task"}>, #<Task @values={:title=>"Second Task"}>]
    

    When you save the project it will save both the project and the tasks.

    If you can even to edit many tasks at the same.

    ActiveRecord implementation

    Here is how to use it.

    Imagine there is a Project class that has many tasks

    Project.rb

    class Project < ActiveRecord::Base
      attr_accessible :title
      has_many :tasks
      accepts_nested_attributes_for :tasks
    end
    

    Task.rb

    class Tasks < ActiveRecord::Base
      attr_accessible :title, :project_id
      belongs_to :project
    end
    

    Now you can do this.

    p =  Project.new
    p.tasks_attributes=[{title: "First Task"}]
    
    p.things
    # Would output this
    #=> [#<Thing id: nil, title: "First Task", created_at: nil, updated_at: nil, bar_id: nil>]
    p.save
    

    When you save the project it will save both the project and the tasks.

    If you want to edit many project tasks at the same time you can to this

    p.tasks_attributes=[{title: "First Task"},{title: "Second Task"}]
    

    NOTE: there is also a Railscasts that can help you out with nested forms. Orginal Railscast, Revised Railscast