Reputation: 3522
Greetings StackOverflow community!
I have been learning Rails for the past few days and extending the basic blog example in the official Rails guide. I wanted to learned more about one-to-one connections since I'm planning on extending the Devise login system with a custom user data table and the rules of normalization dictate that I should make a different table for the actual auth data (the devise user table) and another for the app-specific user info (my own user table).
But now back to simple rails example. The example basically describes an app that can create blog posts, tag them and accept comments (comment's don't really matter right now). A post has_many
tags and a tag belongs_to
a post. Pretty straightforward. (same thing can be said about the comments, too, but I'll just stick with the tags for this example)
When a user wishes to create a NEW post, the controller has a call to @post = Post.new
which prepares an empty post to be edited and created. It also needs to call post.tags.build to make sure that at least one 'baby' tag which would belong to the post is ready for editing 5.times do @post.tags.build end
, for instance would prepare not one but five tags for editing.
In the controller's create method, it's enough to create a new post from params[:post]
and @post.save
it. It saves the tags automatically without any extra function calls needing to be made.
Here's where I started extending. I just wanted to add another table, called post_data, that would get linked one-to-one to the original posts table. post_data contains a foreign key to the post it belongs to, as well as a belongs_to :post
instruction in its model. The post has_one :post_data
. It also accepts_nested_attibutes_for :post_data
.
Again, in the new method of the post controller, post_data needs to be initialized.@post.build_post_data
does just this. The form displays just fine the two different models - one as post[title]...etc and the other as post_data[data_field_name_here].
However, in the create name, the post_data entry needs to be manually saved: @post.create_post_data(params[:post_data])
in order for it to be entered into the db. Otherwise it just doesn't get saved.
Now my question is this - how come the has_many objects of the post, the tags, just need to be prepared in the controller's new method, and then get saved in create automatically, while has_one links also need to be manually saved?
I'm just wandering as to why Rails would work like this.
Thanks in advance for your time and patience! :)
class PostsController < ApplicationController
# GET /posts
# GET /posts.json
def index
@posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @posts }
end
end
# GET /posts/1
# GET /posts/1.json
def show
@post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @post }
end
end
# GET /posts/new
# GET /posts/new.json
def new
@post = Post.new
@post.build_post_data
5.times do @post.tags.build end
end
# GET /posts/1/edit
def edit
@post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
# This works now - it creates all the needed resources automatically
@post = Post.new(params[:post])
respond_to do |format|
if @post.save
format.html { redirect_to @post, :notice => 'Post was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
def update
@post = Post.find(params[:id])
respond_to do |format|
if @post.update_attributes(params[:post])
format.html { redirect_to @post, :notice => 'Post was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
@post = Post.find(params[:id])
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
end
end
end
<%= form_for(@post) do |post_form| %>
<% if @post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% @post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= post_form.label :name %><br />
<%= post_form.text_field :name %>
</div>
<%= post_form.fields_for(:post_data) do |pdf| %>
<div class="field">
<%= pdf.label :herp, "DERP POST DATA" %><br />
<%= pdf.text_field :herp %>
</div>
<% end %>
<div class="field">
<%= post_form.label :title %><br />
<%= post_form.text_field :title %>
</div>
<div class="field">
<%= post_form.label :content %><br />
<%= post_form.text_area :content %>
</div>
<h2>Tags</h2>
<%= render :partial => 'tags/form',
# send some custom variables to the
# rendered partial's context
:locals => { :form => post_form } %>
<div class="actions">
<%= post_form.submit %>
</div>
<% end %>
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => { :minimum => 5 }
has_many :comments, :dependent => :destroy
attr_accessible :post_data_attributes, :name, :title, :content
#let's just assume this makes sense, k?
has_one :post_data
accepts_nested_attributes_for :post_data
# TAGS
has_many :tags
accepts_nested_attributes_for :tags, :allow_destroy => :true,
# reject if all attributes are blank
:reject_if => lambda { |attrs| attrs.all? { |k, v| v.blank? } }
end
Fixed up all the code, syncing it with my working code! :D If anybody from the distant future still has this issue and my code doesn't work, send me a personal message and we'll sort it out! :)
Upvotes: 2
Views: 451
Reputation: 1349
I believe this:
<%= fields_for(:post_data) do |pdf| %>
Should be this:
<%= post.fields_for(:post_data) do |pdf| %>
Can you try that?
Upvotes: 2