I started learning Rails for about a month now and I am working on a fairly simple project to improve my skills. It's a blog where editors can add articles and users can subscribe to a newsletter by adding their email. The homepage is an index view and their is a footer that shows up across all pages.
Here is my problem: I would like to include the form_for the newsletter on the footer that exists inside the application layout. That form has a specific model: Subscriber, which stores users emails.
What I've done so far is include the following on the Articles controller:
def index
#articles = Article.order(created_at: :desc).all
#subscriber = Subscriber.new
end
def create
#subscriber = Subscriber.new(subscriber_params)
if #subscriber.save
redirect_to '/home'
else
render 'new'
end
end
def subscriber_params
params.require(:subscriber).permit(:email)
end
In application.html.erb :
<%= form_for(#subscriber) do |f| %>
<%= f.email_field :email, :placeholder => "email address" %>
<%= f.submit 'Sign up', :id => "signup" %>
<% end %>
The form is displayed correctly on the index page only and it doesn't save to the database (without error).
I have tried using a before filter on the application controller as well as rendering a partial without any success.
Edit
Subscribers controller code:
def new
#subscriber = Subscriber.new
end
def create
#subscriber = Subscriber.new(subscriber_params)
if #subscriber.save
redirect_to '/home'
else
render 'new'
end
end
private
def subscriber_params
params.require(:subscriber).permit(:email)
end
Application controller:
before_action :create_new_subscriber
def create_new_subscriber
#subscriber = Subscriber.new
end
I am trying to embed a video in my app and am using the gem panda '1.6.0' for this. I followed the pandastream integrate with rails guide and have the following code:
controllers:
-------------
class BoothVideosController < ApplicationController
before_filter :init_booth_video
def show
#original_video = #video.panda_video
#h264_encoding = #original_video.encodings["h264"]
render :template => 'booth_video/booth_video', :layout =>
'sponsored_group_manage_sub_menu'
end
def new
#video = BoothVideo.new
render :layout => 'sponsored_group_manage_sub_menu'
end
def create
#video = BoothVideo.create!(params[:video])
redirect_to :action => :show, :id => #video.id
end
def init_booth_video
#group = Group.find(params[:group_id])
#video=#group.booth_video
end
end
class PandaController < ApplicationController
def authorize_upload
payload = JSON.parse(params['payload'])
upload = Panda.post('/videos/upload.json', {
file_name: payload['filename'],
file_size: payload['filesize'],
profiles: "h264",
})
render :json => {:upload_url => upload['location']}
end
end
model:
------
class BoothVideo < ActiveRecord::Base
belongs_to :group
validates_presence_of :panda_video_id
def panda_video
#panda_video ||= Panda::Video.find(panda_video_id)
end
end
View
-----
<% content_for(:page_title, "Create a New Booth Video") -%>
<% form_for(:booth_video,:url => group_booth_video_path, :html => {:id =>
"new_booth_video", :multipart => true}) do |f| %>
<h5>New Booth Video</h5>
<fieldset>
<input type="hidden" name="panda_video_id"/>
<label for="title">Title</label><br />
<%= f.text_field :title, :size => 32, :placeholder => "Title for the new booth
video" %><br /><br />
<div class='progress'>
<span id="progress-bar" class='bar'></span>
</div>
<div id="browse">Choose file</div>
<%=f.submit "Upload video" %>
<% end %>
I have also included the js for panda uploader in my application.js file.
I am able to see my file uploader but when I actually browse and select a file, it does not actually get loaded - also no progress is indicated in the progress bar.
Why is the video not getting uploaded?
The create action mentions params[:video] in the tutorial but there is no param in the
'new' form for the uploaded video-how will it recognize this?
Please help! Thanks in advance...
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?
# encoding: utf-8
class UsersController < ApplicationController
def index
#search = User.search(params[:search])
#users = #search.paginate :per_page => 20, :page => params[:page]
end
end
<h2>User search</h2>
<%= form_for #search, :url => users_path, :html => { :method => :get } do |f| %>
#some form elements
<% end %>
<% #users.each do |user| %>
# show user info
<% end %>
Now how to test view with rspec 2?
# encoding: utf-8
require 'spec_helper'
describe "users/index.html.erb" do
before(:each) do
#####user = stub_model(User)
######User.stub!(:search).and_return(#post)
How to mock? If not mock(or stubed), it will got a nil error when rspec test.
end
it "renders a list of users" do
render
rendered.should contain("User search")
end
end
it "renders a list of users" do
assign(:search, stub_model(???)) # see note
assign(:users, [stub_model(User)]
render
rendered.should contain("User search")
end
assign allows the spec to refer to the instance variables that would be expected in a normal view. #users needs to be an array, thus the brackets.
Note: replace the question marks with whatever type of object gets returned from User.search.
EDIT
Well this is trickier than it appears at first blush. I could not find an easy way to mock up an object that can respond to the necessary messages to get this spec to pass. The quick and dirty way is to just use a real object:
it "renders a list of users" do
assign(:search, User.search)
assign(:users, [stub_model(User)]
render
rendered.should contain("User search")
end
The disadvantage is that this needs a database connection to work. We could hack up our own helper object:
class MetaSearchTestHelper
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :model_name
def initialize(options={})
#model_name = options[:model_name] || nil
end
def singular
#model_name ? #model_name.downcase : nil
end
def persisted?
false
end
end
it "renders a list of users" do
assign(:search, MetaSearchTestHelper.new(:model_name=>"User")
assign(:users, [stub_model(User)]
render
rendered.should contain("User search")
end
Which works for this spec -- but will it work for others?
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.