I've been having some difficulty in understanding the source of a problem.
Below is a listing of the model classes. Essentially the goal is to have the ability to add sentences to the end of the story, or to add stories to an existing sentence_block. Right now, I'm only attempting to allow users to add sentences, and automatically create a new sentence_block for the new sentence.
class Story < ActiveRecord::Base
has_many :sentence_blocks, :dependent => :destroy
has_many :sentences, :through => :sentence_blocks
accepts_nested_attributes_for :sentence_blocks
end
class SentenceBlock < ActiveRecord::Base
belongs_to :story
has_many :sentences, :dependent => :destroy
end
class Sentence < ActiveRecord::Base
belongs_to :sentence_block
def story
#sentence_block = SentenceBlock.find(self.sentence_block_id)
Story.find(#sentence_block.story_id)
end
end
The problem is occurring when using the show method of the Story. The Story method is as follows, and the associated show method for a sentence is also included.
Sentence.show
def show
#sentence = Sentence.find(params[:id])
respond_to do |format|
format.html {redirect_to(#sentence.story)}
format.xml { render :xml => #sentence }
end
end
Story.show
def show
#story = Story.find(params[:id])
#sentence_block = #story.sentence_blocks.build
#new_sentence = #sentence_block.sentences.build(params[:sentence])
respond_to do |format|
if #new_sentence.content != nil and #new_sentence.sentence_block_id != nil and #sentence_block.save and #new_sentence.save
flash[:notice] = 'Sentence was successfully added.'
format.html # new.html.erb
format.xml { render :xml => #story }
else
#sentence_block.destroy
format.html
format.xml { render :xml => #story }
end
end
end
I'm getting a "couldn't find Sentence_block without and id" error. So I'm assuming that for some reason the sentence_block isn't getting saved to the database. Can anyone help me with my understanding of the behavior and why I'm getting the error? I'm trying to ensure that every time the view depicts show for a story, an unnecessary sentence_block and sentence isn't created, unless someone submits the form, I'm not really sure how to accomplish this. Any help would be appreciated.
edit, the view:
<p>
<b>Title:</b>
<%=h #story.title %>
<% #story.sentence_blocks.each do |b| %>
<% b.sentences.each do |s| %>
<br />
<%=h s.content %>
<% end %>
<% end %>
</p>
<% form_for #new_sentence do |s| %>
<p>
<%= s.label :sentence %><br />
<%= s.text_field :content %>
</p>
<p>
<%= s.submit "Create" %>
</p>
<% end %>
<%= link_to 'Edit', edit_story_path(#story) %>
<%= link_to 'Back', stories_path %>
ruby-debug will be very helpful:
gem install ruby-debug
script/server --debugger
Add the following after setting the #story in your show method:
def show
#story = Story.find(params[:id])
debugger
#sentence_block = #story.sentence_blocks.build
Then go to /stories/1 or whatever id you're using to test, the page will not finish loading, go to the terminal you're using for your server and you will see an irb prompt. You can run arbitrary code from that prompt and you can step through your code. Very useful.
Related
I have looked at various answers to similar questions and haven't quite cracked it.
A wine model is defined with has_one :register, :dependent => :destroy and rightly or wrongly I have added accepts_nested_attributes_for :register. A register is defined with belongs_to :wine.
The code within wines_controller.rb for create is:
def new
#wine = Wine.new
#register = Register.new
def create
#wine = Wine.new(wine_params)
#register = #wine.registers.build(register_params)
respond_to do |format|
if #wine.save
#success
else
format.json { render json: #wine.errors, status: :unprocessable_entity }
format.json { render json: #register.errors, status: :unprocessable_entity }
end
end
end
My form for creating a new wine has the following code:
<%= simple_form_for #wine do |f| %>
# various working elements
<div class="field">
<% f.fields_for :register do |r| %>
<%= r.label :short_name %>
<%= r.text_field :short_name %>
<%= r.label :barcode %>
<%= r.text_field :barcode %>
<% end %>
</div>
When this form is called up no fields are created from the f.fields_for command but this block is executed because I can add test buttons within it to prove it is accessed.
If I try to create a wine I get the following error message:
undefined method `registers' for #<Wine:0x007f1204375330> Did you mean? register register= register_id
I believe that using .build is there to ensure data integrity: I don't want to create a wine that does not have a corresponding register. I have tried thinking about it nested attributes but that seems to be considered a bad plan by many. This current approach feels correct but I think I am missing some understanding of syntax at the very least.
At a later date it will be necessary to have other models linked to register that will not be associated to wines. I was considering a similar approach but I am happy to be told to rethink!
If I understand you correctly you have 2 issues:
Firstly fields for register aren't being displayed - this is partly because #wine.register is nil.
You should change your new action to:
def new
#wine = Wine.new
#wine.register = Register.new
In addition because you are using simple_form_for you will need to use simple_fields_for instead of fields_for
Your second issue that results in the exception tells you everything... you are trying to access #wine.registers, and not #wine.register
Change in your create method to:
#register = #wine.register.build(register_params)
This will fix that issue ... however ... all you really need to do is build the #wine object from your params - your params should be configured to permit the right nested attributes - if it is set up correctly the register object will also be built when building the #wine object.
Your model is already set to accept_nested_attributes and thus will also validate and save the register object when calling #wine.save - no need to explicitly save the register object.
You should have something like:
def wine_params
params.require(:wine).permit(
:attribute1, :attribute2,
register_attributes: [:id, :short_name, :barcode])
end
Try this
Wine and Register models
class Wine < ApplicationRecord
has_one :register, inverse_of: :wine, :dependent => :destroy
accepts_nested_attributes_for :register
end
class Register < ApplicationRecord
belongs_to :wine, inverse_of: :register
validates_presence_of :wine
end
Wines Controller
class WinesController < ApplicationController
def new
#wine = Wine.new
#wine.build_register
end
def create
#wine = Wine.new(wine_params)
if #wine.save
redirect_to #wine
else
render :new
end
end
private
def wine_params
params.require(:wine).permit(:name, register_attributes: [:simple_name])
end
end
My wine_params are specific for
rails g model wine name:string
rails g model register name:string wine_id:integer
Lastly wine form should look like this
<%= form_for #wine do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name%>
</p>
<%= f.fields_for :register do |r|%>
<p>
<%= r.label :simple_name%>
<%= r.text_field :simple_name%>
</p>
<% end %>
<%= f.submit %>
<% end %>
So you can modify wine_params and form partial for your application specifics
I am in the process of creating a forum using Ruby on Rails (I'm pretty new at this) and have managed to get myself utterly stuck.
**Version Ruby on Rails 4.0 **
A forum software can have many Categories, and within these Categories you can have multiple forums.
The main page would look similar to this:
Category 1
Forum 1
Forum 2
Category 2
Forum 3
Forum 4
Forum 5
When you create a forum, you should have a drop down menu that allows you to select which category you wish to place it in.
At first I created two different scaffolds- One for Categories and one for Forums. I used a foreign key to connect the two. I do not know if this is the best method, but I could not get them to interact at all. I ended up screwing up my code so badly I have very little to show for it.
I tried using Adding Sub-categories in Rails4 and categories and sub-categories model rails for solutions but both ended up causing errors.
Here is some of my code. It's not much, but maybe you can tell me where to even begin. If there is a better way of doing this (not using two tables), let me know. I would love to hear the best possible way of doing this without using gems
WARNING: my code is an utter mess.
Migration
class AddForeignToForums < ActiveRecord::Migration
def change
add_column :forums, :category_id, :integer
end
end
Forum Controller (I know I am missing something that will allow me to connect to the Category, I just don't know what)
class ForumsController < ApplicationController
before_action :set_forum, only: [:show, :edit, :update, :destroy]
# GET function. view/forums/index.html.erb
def index
#forums = Forum.all
end
# GET /forums/1. view/forums/show.html.erb
def show
#forum = Forum.find(params[:id])
end
# GET /forums/new. view/forums/new.html.erb
# Be able to list all the Categories.
def new
#forum = Forum.new
#categories = Category.all
end
# GET /forums/1/edit
# Be able to list all the categories.
def edit
#forum = Forum.find(params[:id])
#categories = Category.all
end
# POST /forums
# Allows the creation of a new forum
# Lindsey note: how to save category_idea. Assign to category.
def create
#forum = Forum.new(forum_params)
respond_to do |format|
if #forum.save
#forum = Forum.new(:name => params[:forum][:name],
:category_id => params[:forum][:category_id])
format.html { redirect_to #forum, notice: 'Forum was successfully created.' }
format.json { render action: 'show', status: :created, location: #forum }
else
format.html { render action: 'new' }
format.json { render json: #forum.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /forums/1
# Allows the update of forums.
def update
respond_to do |format|
if #forum.update(forum_params)
format.html { redirect_to #forum, notice: 'Forum was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #forum.errors, status: :unprocessable_entity }
end
end
end
# DELETE /forums/1
def destroy
#forum.destroy
respond_to do |format|
format.html { redirect_to forums_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_forum
#forum = Forum.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def forum_params
params.require(:forum).permit(:name, :description, :category_id)
end
end
Forum Model
class Forum < ActiveRecord::Base
belongs_to :category
end
Category Model
class Category < ActiveRecord::Base
has_many :forums, :dependent => :destroy,
end
Category Index.html.erb
<tbody>
<% #categories.each do |category| %>
<tr>
<td><%= link_to category.name, category %></td>
<td><%= link_to ' (Edit', edit_category_path(category) %></td>
<td><%= link_to '| Destroy)', category, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% category.forums.each do |forum| %>
<tr>
<td><li><%= link_to forum.name, forum %></li></td>
</tr>
<% end %>
<% end %>
</tbody>
Forum _form.html.erb
<%= form_for(#forum) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<%= f.label :category_id %><br />
<%= f.select :category_id, Category.all.map{|c| [c.name, c.id]} %>
<div class="actions">
<%= f.submit %>
</div>
You probably want a table for forums, a table for categories, and a join table that includes a forum_id and category_id - name this forum_categories
class Forum < ActiveRecord::Base
has_many :forum_categories
has_many :categories, :through => :forum_categories
end
And, with categories, you'll do the reverse
class Categories < ActiveRecord::Base
has_many :forum_categories
has_many :forums, :through => :forum_categories
end
For adding categories in the view, you can use checkboxes or a multiple select box. The name of this input will be either
f.check_box 'category_ids[]'
or
f.select 'category_ids[]'
This will submit a param in an array format that will allow you to update the forum.category_ids with a simple
forum.create(params[:forum])
In your view, instead of #forums, you'll list category.forums
<% category.forums.each do |forum| %>
<%= forum.name %>
<% end %>
Hopefully this will get you started.
EDIT
For a single category on Forum, you've done well. Just a few smaller changes:
class Category < ActiveRecord::Base
# belongs_to :category - this can be removed
has_many :forums # Do you want to delete the forums if the category is removed? You don't need the classname option.
end
In the drop down - you'll do something like this...
f.select :category_id, Category.all.map{|c| [c.name, c.id]}
I'm trying to get the text from a text_area field in a form to save to a database in a different Model with the current Model's ID.
Currently, this works but only will save integers. If I put text into the 'Notes' field, then its saves it as a '0'. I suspect this is working correctly but I'm missing a piece to my puzzle. This is because I only want the 'Ticket' to save the note_id because I will have multiple 'Notes' per 'Ticket.
How can I get the Note to save in the Note Model, with an ID, and associate that note_id with this specific ticket?
Form - /app/views/tickets/_form.html.erb
<%= form_for(#ticket) do |f| %>
<% if #ticket.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#ticket.errors.count, "error") %> prohibited this ticket from being saved:</h2>
<ul>
<% #ticket.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note, :size => "101x4", :placeholder => "Leave notes here." %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Tickets_controller.rb
class TicketsController < ApplicationController
# GET /tickets
# GET /tickets.json
def index
#tickets = Ticket.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #tickets }
end
end
# GET /tickets/1
# GET /tickets/1.json
def show
#ticket = Ticket.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/new
# GET /tickets/new.json
def new
#ticket = Ticket.new
#ticket.notes.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/1/edit
def edit
#ticket = Ticket.find(params[:id])
end
# POST /tickets
# POST /tickets.json
def create
#ticket = Ticket.new(params[:ticket])
respond_to do |format|
if #ticket.save
format.html { redirect_to #ticket, notice: 'Ticket was successfully created.' }
format.json { render json: #ticket, status: :created, location: #ticket }
else
format.html { render action: "new" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# PUT /tickets/1
# PUT /tickets/1.json
def update
#ticket = Ticket.find(params[:id])
respond_to do |format|
if #ticket.update_attributes(params[:ticket])
format.html { redirect_to #ticket, notice: 'Ticket was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# DELETE /tickets/1
# DELETE /tickets/1.json
def destroy
#ticket = Ticket.find(params[:id])
#ticket.destroy
respond_to do |format|
format.html { redirect_to tickets_url }
format.json { head :no_content }
end
end
end
Note.rb
class Note < ActiveRecord::Base
belongs_to :ticket
attr_accessible :note, :ticket_id
end
Ticket.rb
class Ticket < ActiveRecord::Base
attr_accessible :notes_attributes
accepts_nested_attributes_for :notes
end
It is because note_id is an integer type.
Use nested models:
Refer this for Nested Models
Model:
class Ticket < ActiveRecord::Base
has_many :notes
attr_accessible :note_id, :notes_attributes
accepts_nested_attributes_for :notes
end
View:
<%= form_for(#ticket) do |f| %>
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
What you have is a nested association, with Ticket as the "parent". The association is governed by the link from note_id in the Note model to the id (primary key) of the Ticket. What you're presently doing right now is manually manipulating that numeric association. Rails, knowing that the note_id column is supposed to be an integer, is taking the text you're trying to insert and turning it in to a number (zero in this case). You've probably got a bunch of orphaned rows right now because of this.
Ultimately, in order to accomplish what you're trying to do, your form will need to provide fields for that associated model. One way you can handle this is by using the accepts_nested_attributes_for in your Ticket model. Like so:
class Ticket < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
And in your form, you can easily create a nested form like so:
<%= form_for(#ticket) do |f| %>
<div class="field">
<%= f.fields_for :notes do |f_notes|%>
<%= f_notes.label :note %><br />
<%= f_notes.text_area :note, :size => "101x4", :placeholder => "Please leave notes here."%>
<% end %>
</div>
<% end %>
Edit Almost forgot: Check out this Railscast from Ryan Bates dealing with Nested Attributes
Edit 2 As codeit pointed out, you don't need the attr_accessible :note_id in Ticket. Since you've indicated that a Ticket has many Notes, and that Note belongs to Ticket, the foreign key column will appear in the Note model as ticket_id, which you already have. Having note_id in the ticket model is useless, and also nonsensical since has_many describes a plural relationship (which can't be expressed with a single column).
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! :)
Edit: The code files!
posts_controller
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
posts/_form.html.erb
<%= 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 %>
and the posts.rb model
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
Final edit:
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! :)
I believe this:
<%= fields_for(:post_data) do |pdf| %>
Should be this:
<%= post.fields_for(:post_data) do |pdf| %>
Can you try that?
Edit: Added the update action, and on what line the error occurs
Model:
class Match < ActiveRecord::Base
has_and_belongs_to_many :teams
has_many :match_teams
has_many :teams, :through => :match_teams
accepts_nested_attributes_for :match_teams, :allow_destroy => true
end
Controller:
def new
#match = Match.new
#match_teams = 2.times do
#match.match_teams.build
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #match }
end
end
def update
#match = Match.find(params[:id])
respond_to do |format|
if #match.update_attributes(params[:match])
format.html { redirect_to #match, notice: 'Match was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
Nested model:
class MatchTeam < ActiveRecord::Base
belongs_to :match
belongs_to :team
end
Association:
class Team < ActiveRecord::Base
has_and_belongs_to_many :matches
end
View:
<%= form_for(#match) do |f| %>
<%= f.fields_for :match_teams, #match_teams do |builder| %>
<%= builder.collection_select :team_id, Team.all, :id, :name, :include_blank => true %>
<% end %>
<% unless #match.new_record? %>
<div class="field">
<%= f.label :winning_team_id %><br />
<%= f.collection_select :winning_team_id, #match.teams, :id, :representation %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Params:
Processing by MatchesController#update as HTML
Parameters: {"utf8"=>"Ô£ô", "authenticity_token"=>"QIJChzkYOPZ1hxbzTZS8H3AXc7i
BzkKv3Z5daRmlOsQ=", "match"=>{"match_teams_attributes"=>{"0"=>{"team_id"=>"1", "
id"=>""}, "1"=>{"team_id"=>"3", "id"=>""}}, "winning_team_id"=>"3"}, "commit"=>"
Update Match", "id"=>"2"}
Creating a new match with 2 teams work fine, the edit view also shows the correct values, but the update action gives me this error.
undefined method `to_sym' for nil:NilClass
app/controllers/matches_controller.rb:65:in `block in update'
line 65: if #match.update_attributes(params[:match])
I've figured it out. I read that a join table like MatchTeams doesn't need an ID. I'm guessing this is true when not doing any nested forms. I redid my migration removing the exclusion of the id column, and now everything works fine. Don't we all love this stupid errors? :)
Without seeing the offending to_sym in your code, just know that the thing it's attached to has not been defined properly. If this is a variable such as #var.to_sym, you most likely:
Haven't set #var at all
Set it but it's returning nil because there are no matches (e.g. #var = #project.companies.first but #project has no companies tied to it).
You are missing a relevant bit of data in your params. If your to_sym is relying on data submitted through the form, it won't work if the user leaves out the bit of data you're assuming. In this case, you should test first to see if the data was entered before running .to_sym on it.