I'm learning to program and got a form running in my Rails 3 app. Now I'm attempting to add ajax to the form so the page doesn't reload after submitting.
I've followed the numerous tutorials but can't quite seem to figure out how to bring it together. The form adds new Objects to the Profile through the following model:
class Profile < ActiveRecord::Base
has_many :objects
end
class Object < ActiveRecord::Base
belongs_to :profile
end
My form in views/profiles/_object_form.html.erb:
<%= form_for(#object, :remote => true) do |f| %>
<% end %>
Where the form and its created objects are rendered in my views/profiles/_about.html.erb:
<div id="newObjects">
<%= render :partial => 'object_form' %>
</div>
<div id="objectList">
<%= render :partial => 'object', :collection => #profile.objects, :locals => {:object_count => #profile.objects.length) %>
</div>
In my objects_controller.rb I have the following create action:
def create
#object = Object.new(params[:object].merge(:author_id => current_user.id))
respond_to do |format|
if #object.save!
format.html {redirect_to profile_path(#object.profile) }
format.js { render }
else
format.html { redirect_to #profile, :alert => 'Unable to add object' }
end
end
end
In views/objects/create.js.erb:
$('#objectList').append("<%= escape_javascript(render #profile.object)) %>");
So I have a form calling an action in another controller to which I want to add ajax. What happens at the moment is that I need to reload the profile to show the newly created object. What am I doing wrong?
CLARIFICATION: Other than the create action in the ObjectsController, I only reference #object once elsewhere. That's in the ProfilesController's show action:
def show
#profile = Profile.find(params[:id])
#superlative = #profile.superlatives.new`
end
Not sure if this is a full code snippet for your create action, but looks like you are trying to call render on an instance variable that doesn't exist... #profile is never set in the create method in the ObjectController...
Perhaps you meant to type $('#objectList').append("<%= escape_javascript(render #object)) %>");
Also noticed that in your existing code you're making a call to render #profile.object, but the Profile class has a has_many relationship with your Object class, so if that was the right code, then you should type render #profile.objects (plural, not singular).
But I would think you would likely want the code I mentioned above, since you are appending onto the list of objects, not rendering the list again?
Related
I've started to work on old project with new tools. This time - Rails on Ruby. I managed to make some progress, and now i want to improve one element of my code.
Whole project is about bugtracking with full history search of all tracked bugs. Now i'm on stage where user is entering bugs. Every bug belong to table which belongs to projects.
Only problem - now - is autocompletion of table name when i'm using completely new name (with tables that are already present in database it's working just fine, fills table_id in Bug entry).
Part of view responsible for entering (or selecting from existing) table looks like this:
<div class="control-group">
<%= f.label :table_name, :class => 'control-label' %>
<div class="controls">
<%= f.autocomplete_field :table_name, autocomplete_table_name_bugs_path %>
</div>
</div>
Nothing unusual. This one goes to the model (Bug.rb)
class Bug < ActiveRecord::Base
attr_accessible :bugid, :fixdate, :fixnote, :fixstate_id, :milestone, :newtarget, :notes, :oldtarget, :project_id, :bugreason, :reason_id, :regressiondate, :regressionstate_id, :source, :stringid, :table_id
belongs_to :project
belongs_to :table
belongs_to :reason
belongs_to :state
def table_name
table.name if table
end
#def table_name=(name)
# self.table = Table.find_or_create_by_name(name) unless name.blank?
#end
end
No validation for now as you can see. table_name=(name) commented as it's apparently doing nothing in my code.
This is working with bugs controller (bugs_controller.rb)
def create
if params[:bug][:table_id].nil?
if Table.find_or_create_by_name(params[:bug][:table_name])
params[:bug][:table_id] = Table.find_by_name(params[:bug][:table_name]).id
params[:bug].delete :table_name
end
end
#bug = Bug.new(params[:bug])
respond_to do |format|
if #bug.save
format.html { redirect_to bugs_path, notice: 'Bug was successfully created.' }
format.json { render json: #bug, status: :created, location: #bug }
else
format.html { render action: "new" }
format.json { render json: #bug.errors, status: :unprocessable_entity }
end
end
end
I put here only part responsible for saving new bugs, i'll manage to handle update part when i do this part right.
What i want to improve is first part. Now it's responsible not only for changing table_name to table_id but for creation of new table if it doesn't exist. I'm aware that this part should be handled by model, but i've no idea how to do that, could use some help.
Another part, as btw. is my dropdown menu where user can select active project. It's handled by partial:
<% #projects.each do |project| %>
<% if project.id == session[:current_project].to_i %>
<li class="disabled"><%= link_to project.name, '#' %></li>
<% else %>
<li><%= link_to project.name, choose_project_path(project.id) %></li>
<% end %>
<% end %>
But it works fine only when used from projects controller. How - by the book - i can handle this from other controllers? To be exact, i want it working same way in whole project.
For now i'm handling it by snippet in every controller, but i'm pretty sure that RoR gods are not happy with me for that.
before_filter :projects
def projects
#projects = Project.all
end
How it should be done in proper way? :)
So i managed to move logic from controller to model. Also, it's saving all data i need - table_id to bugs table and project_id (stored in session) to tables table when creating new table.
So part of form render is not changed:
<div class="control-group">
<%= f.label :table_name, :class => 'control-label' %>
<div class="controls">
<%= f.autocomplete_field :table_name, autocomplete_table_name_bugs_path %>
</div>
</div>
Model now looks like this:
class Bug < ActiveRecord::Base
attr_accessible :table_name, :bugid, :fixdate, :fixnote, :fixstate_id, :milestone, :newtarget, :notes, :oldtarget, :project_id, :bugreason, :reason_id, :regressiondate, :regressionstate_id, :source, :stringid, :table_id
belongs_to :project
belongs_to :table
belongs_to :reason
belongs_to :state
def table_name
table.name if table
end
def table_name=(name)
self.table = Table.find_or_create_by_name(name, project_id: self.project_id ) unless name.blank?
end
end
So only change is uncommenting table_name= method and adding project_id to creation of new table (and lack of project_id was the reason it didn't work earlier).
Controller looks like this:
def create
#bug = Bug.new(params[:bug])
#bug.project_id = session[:current_project]
respond_to do |format|
if #bug.save
format.html { redirect_to bugs_path, notice: 'Bug was successfully created.' }
format.json { render json: #bug, status: :created, location: #bug }
else
format.html { render action: "new" }
format.json { render json: #bug.errors, status: :unprocessable_entity }
end
end
end
It's working like charm. Still i've question remaining, but will post it as separate one.
I have a Rails app where I have Article and Like models. I want someone to be able to create a Like record, similar to Facebook, where the database records the new record without redirecting them. The two requirements I have are: create a new Like record without redirecting the user and have a flash or js message that confirms the record was created.
I tried putting this in my view. However, when I do this, it creates two identical records:
<%= form_for #like, :remote=> true do |f| %>
<%= f.hidden_field(:article_id, :value => article.id) %>
<%= f.submit "Like" %>
<% end %>
I also, tried this which resulted in one record creating, but it took me to localhost:3000/likes:
<%= form_for #like do |f| %>
<%= f.hidden_field(:article_id, :value => article.id) %>
<%= f.submit "Like" %>
<% end %>
and then in the Like Controller commenting out the format.html and json:
def create
#like = Like.new(params[:like])
respond_to do |format|
if #like.save
# format.html { redirect_to #like, notice: 'Like was successfully created.' }
# format.json { render json: #like, status: :created, location: #like}
else
format.html { render action: "new" }
format.json { render json: #like.errors, status: :unprocessable_entity }
end
end
end
What's the best way to meet my two requirements? Thank you!
I assume Articles and Likes are related. When you want to 'like' an article you are actually updating that article, right? In other words, you would have to change the redirect within the update action of the articles controller.
P.S. I would use some javascript for submitting a form, for example:
<script type="text/javascript">
$(document).ready(function() {
$('.edit_[here_comes_the_name] input[type=checkbox]').click(function() {
$(this).parent('form').submit();
});
});
</script>
This example submits the input from a particular form (the parent) when clicking on the check_box. Of course you would have to adjust it to fit your needs.
I have a really long form which I would like to break up into about 5 partials. When the user hits 'Next' at the bottom of each partial I want to use AJAX to load the next partial until the last partial submits the entire form into the database. Also, if the user hits 'Previous' I need the fields to be populated with what the user filled in previously.
So far I have this which is not working:
users/new.html.erb
<%= form_for(#user, :html => { :class => "form-horizontal" }, remote: true) do |f| %>
...
<%= f.submit "Next" %>
users_controller.rb
def create
#user = User.new(params[:user])
if #user.save
respond_to do |format|
format.html { flash[:success] = "Welcome to Friends First!"
redirect_to #user }
format.js
end
else
render :new
end
end
create.js.erb
$("#site_content").html("<%= escape_javascript(render('layouts/partial2')) %>");
I would put each of the 5 parts into separate divs (display: none) and only show (display: block) the first. When the user clicks "next", I would show the second, etc. The final submit to the create action can also be done via jquery through
$.ajax(
url: '/users',
type: 'post',
data: $("form").serialize()
)
I hope, that helps.
I am trying to create a custom POST action for my article object.
In my routes.rb, I have set the action in the following way:
resources :articles do
member do
post 'update_assigned_video'
end
end
In my articles_controller.rb I have:
def update_assigned_video
#article = Articles.find(params[:id])
#video = Video.find(:id => params[:chosenVideo])
respond_to do |format|
if !#video.nil?
#article.video = #video
format.html { redirect_to(#article, :notice => t('article.updated')) }
else
format.html { render :action => "assign_video" }
end
end
Then in my view I make a form like this:
<%= form_for #article, :url => update_assigned_video_article_path(#article) do |f|%>
[...]
<%= f.submit t('general.save') %>
The view renders (so I think he knows the route). But clicking on the submit button brings the following error message:
No route matches "/articles/28/update_assigned_video"
rake routes knows it also:
update_assigned_video_article POST /articles/:id/update_assigned_video(.:format) {:action=>"update_assigned_video", :controller=>"articles"}
What am I doing wrong?
Is this the wrong approach to do this?
Your form_for will do a PUT request rather than a POST request, because it's acting on an existing object. I would recommend changing the line in your routes file from this:
post 'update_assigned_video'
To this:
put 'update_assigned_video'
Fairly new to Rails 3 and have been Googling every which way to no avail to solve the following problem, with most tutorials stopping short of handling errors.
I have created a Rails 3 project with multiple content types/models, such as Articles, Blogs, etc. Each content type has comments, all stored in a single Comments table as a nested resource and with polymorphic associations. There is only one action for comments, the 'create' action, because there is no need for the show, etc as it belongs to the parent content type and should simply redisplay that page on submit.
Now I have most of this working and comments submit and post just fine, but the last remaining issue is displaying errors when the user doesn't fill out a required field. If the fields aren't filled out, it should return to the parent page and display validation errors like Rails typically does with an MVC.
The create action of my Comments controller looks like this, and this is what I first tried...
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#commentable, :notice => 'Comment was successfully created.') }
else
format.html { redirect_to #commentable }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
end
end
When you fill nothing out and submit the comments form, the page does redirect back to it's appropriate parent, but no flash or nothing is displayed. Now I figured out why, from what I understand, the flash won't persist on a redirect_to, only on a render. Now here's where the trouble lies.
There is only the 'create' action in the comment controller, so I needed to point the render towards 'blogs/show' (NOTE: I know this isn't polymorphic, but once I get this working I'll worry about that then). I tried this in the "else" block of the above code...
else
format.html { render 'blogs/show' }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
Anyway, when I try to submit an invalid comment on a blog, I get an error message saying "Showing [...]/app/views/blogs/show.html.erb where line #1 raised: undefined method `title' for nil:NilClass."
Looking at the URL, I think I know why...instead of directing to /blogs/the-title-of-my-article (I'm using friendly_id), it's going to /blogs/the-title-of-my-article/comments. I figure that extra "comments" is throwing the query off and returning it nil.
So how can I get the page to render without throwing that extra 'comments' on there? Or is there a better way to go about this issue?
Not sure if it matters or helps, but the route.rb for comments / blogs looks like this...
resources :blogs, :only => [:show] do
resources :comments, :only => [:create]
end
I've been plugging away at this over the last few weeks and I think I've finally pulled it off, errors/proper direction on render, filled out fields remain filled in and all. I did consider AJAX, however I would prefer to do it with graceful degradation if at all possible.
In addition, I admit I had to go about this a very hacky-sack way, including pulling in a way to pluralize the parent model to render the appropriate content type's show action, and at this stage I need the code to simply work, not necessarily look pretty doing it.
I KNOW it can be refactored way better, and I hope to do so as I get better with Rails. Or, anyone else who thinks they can improve this is welcomed to have at it. Anyway, here is all my code, just wanted to share back and hope this helps someone in the same scenario.
comments_controller.rb
class CommentsController < ApplicationController
# this include will bring all the Text Helper methods into your Controller
include ActionView::Helpers::TextHelper
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#commentable, :notice => 'Comment was successfully created.') }
else
# Transform class of commentable into pluralized content type
content_type = find_commentable.class.to_s.downcase.pluralize
# Choose appropriate instance variable based on #commentable, rendered page won't work without it
if content_type == 'blogs'
#blog = #commentable
elsif content_type == 'articles'
#article = #commentable
end
format.html { render "#{content_type}/show" }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
end
end
private
# Gets the ID/type of parent model, see Comment#create in controller
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
end
end
articles_controller.rb
class ArticlesController < ApplicationController
def show
#article = Article.where(:status => 1).find_by_cached_slug(params[:id])
#comment = Comment.new
# On another content type like blogs_controller.rb, replace with appropriate instance variable
#content = #article
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #article }
end
end
end
show.html.erb for articles (change appropriate variables for blog or whatever)
<h1><%= #article.title %></h1>
<%= #article.body.html_safe %>
<%= render :partial => 'shared/comments', :locals => { :commentable => #article } %>
shared/_comments.html.erb (I'm leaving out the displaying of posted comments here for simplification, just showing the form to submit them)
<%= form_for([commentable, #comment]) do |f| %>
<h3>Post a new comment</h3>
<%= render :partial => 'shared/errors', :locals => { :content => #comment } %>
<div class="field">
<%= f.label :name, :value => params[:name] %>
<%= f.text_field :name, :class => 'textfield' %>
</div>
<div class="field">
<%= f.label :mail, :value => params[:mail] %>
<%= f.text_field :mail, :class => 'textfield' %>
</div>
<div class="field">
<%= f.text_area :body, :rows => 10, :class => 'textarea full', :value => params[:body] %>
</div>
<%= f.submit :class => 'button blue' %>
<% end %>
shared/_errors.html.erb (I refactored this as a partial to reuse for articles, blogs, comments, etc, but this is just a standard error code)
<% if content.errors.any? %>
<div class="flash error">
<p><strong><%= pluralize(content.errors.count, "error") %> prohibited this page from being saved:</strong></p>
<ul>
<% content.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
I slightly refactored #Shannon answer to make it more dynamic. In my 'find_parent' method I'm grabbing the url path and fetching the controller name. In the 'create' method I'm creating an 'instance_variable_set' which creates a dynamic variable for either Articles (#article) or Blogs (#blog) or what ever it may be.
Hopefully you'll like what I've done? Please let me know if you have any doubts or if something can be improved?
def create
#comment = #commentable.comments.new(params[:comment])
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
content_type = find_parent
instance_variable_set "##{content_type.singularize}".to_sym, #commentable
#comments = #commentable.comments
render "#{content_type}/show"
end
end
def find_parent
resource = request.path.split('/')[1]
return resource.downcase
end
You're getting an error because the blogs/show view likely refers to the #blog object, which isn't present when you render it in the comments controller.
You should go back to using the redirect_to rather than render. It wasn't displaying a flash when you made an invalid comment because you weren't telling it to set a flash if the comment wasn't saved. A flash will persist till the next request.