I am using the mailboxer gem and I wanted to go about making the conversation (show.html.haml) JSON format and also the mailbox (index.html.haml) JSON format.
I tried putting it a normal respond_to block like this
respond_to do
format.json { render :json => #conversation }
end
but it didn't work. It says the stack level is too deep. Is there something I'm missing?
These are the controller code
def mailbox
#mailbox ||= current_user.mailbox
end
def conversation
#conversation ||= mailbox.conversations.find(params[:id])
end
I ended up figuring out what the problem was, I needed to add a show and index into my conversations controller so that I could then render in json. For anyone else that may have this problem this was what I did and it worked fine.
def index
#mailbox ||= current_user.mailbox
respond_to do |format|
format.html
format.json { render :json => #mailbox }
end
end
def show
#conversation ||= mailbox.conversations.find(params[:id])
respond_to do |format|
format.html
format.json { render :json => #conversation }
end
end
Hope this helps!
Related
I have a rails controller where every action has the same respond_to block for every action, eg:
def some_action
respond_to do |format|
format.html { redirect_to :back }
format.js { render layout: false }
end
end
Is there a way that I can set this as the default response for all actions? I know that I can use
respond_to :html, :js
at the top of the controller, but can this be used to set the specific responses for each format?
Going though respond_with and respond_to documentation and source code. You can either
Use respond_to
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to(#user) }
format.xml { render xml: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user }
end
end
end
OR respond_with
respond_to :html, :xml
def create
#user = User.new(params[:user])
flash[:notice] = 'User was successfully created.' if #user.save
respond_with(#user)
end
A work around is to create your own custom respond method, or manually check for the mime type as follows:
****NOTE: this is a really bad practice, I recommend sticking to conventions.
def some_action
render json: {"a" => "s"} if request.format.json?
render :some_action if request.format.html?
end
If you want all actions to respond exactly the same, move the respond_to block into a method.
def some_action
# do things
respond
end
def another_action
# do more things
respond
end
def special_action
# do special things
respond
end
private
def respond
respond_to do |format|
format.html { redirect_to :back }
format.js { render layout: false }
end
end
This will DRY up your controller, which I assume was the question.
I'm fairly new to Rails and I have a Ruby on Rails 3.2 application and I've integrated the Youtube_it gem seen here https://github.com/kylejginavan/youtube_it. The gem works fine and I'm able to upload the video to youtube, but it takes a while to process the video. I would like to be able to run that as a background job and redirect the user to the thank you page I have created.
I'm not sure where to call the delay method. I would like to call the delay method and then have the user redirect to the page_path('thank-you') page.
Any help would be greatly appreciated. I've searched all over for an answer.
VideosController
def upload
#video = Video.create(params[:video])
if #video
#upload_info = Video.delay.token_form(params[:video], save_video_new_video_url(video_id: #video.id))
else
respond_to do |format|
format.html { render "/videos/new" }
end
end
end
def save_video
#video = Video.find(params[:video_id])
if params[:status].to_i == 200
#video.update_attributes(youtube_id: params[:id].to_s, is_complete: true, user_id: current_user.id, approved: false)
Video.delete_incomplete_videos
else
Video.delete_video(#video)
end
#redirect_to videos_path, notice: "video successfully uploaded"
redirect_to page_path('thank-you')
end
Here is my controller.
class VideosController < ApplicationController
before_filter :authenticate_user!, only: [:new, :upload, :save_video, :destroy]
def index
if params[:category]
Video.yt_session
#videos = Video.approved.where(category_id: params[:category])
else
Video.yt_session
#videos = Video.approved
end
respond_to do |format|
format.html # index.html.erb
format.json { render json: #videos }
end
end
def show
#video = Video.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #video }
end
end
def new
#video =Video.new
#categories = Category.all
respond_to do |format|
format.html # new.html.erb
format.json { render json: #video }
end
end
def upload
#video = Video.create(params[:video])
if #video
#upload_info = Video.token_form(params[:video], save_video_new_video_url(video_id: #video.id))
else
respond_to do |format|
format.html { render "/videos/new" }
end
end
end
def save_video
#video = Video.find(params[:video_id])
respond_to do |format|
if #video.update_attributes(:youtube_id => params[:id].to_s, :is_complete => true,:user_id=>current_user.id,:approved=>false)
format.html { redirect_to page_path('thank-you') }
format.json { head :no_content }
else
format.html { Video.delete_video(#video) }
format.json { render json: #category.errors, status: :unprocessable_entity }
end
end
end
def destroy
#video = Video.find(params[:id])
if Video.delete_video(#video)
flash[:notice] = "Video deleted."
else
flash[:error] = "We were unable to delete this video."
end
redirect_to videos_path
end
def vote_up
#video = Video.find(params[:id])
#video.update_attribute(:votes_up, (#video.votes_up.to_i + 1))
redirect_to #video
end
protected
def collection
#videos ||= end_of_association_chain.completes
end
end
You can always use background workers to do that using the like of delayed_job, but personally speaking I prefer to use Resque/beanstalkd/RabbitMQ since I can run multiple workers, concurrently.
To make your life even easier, you just try Sidekiq (https://github.com/mperham/sidekiq).
You will move your worker logic to some worker.
Include Sidekiq to your Gemfile
gem 'sidekiq'
At your controller add something like:
VideoSaverWorker.perform_async(#video.id)
The VideoSaverWorker must look something like:
class VideoSaverWorker
include Sidekiq::Worker
sidekiq_options queue: "high"
def perform(video_id)
video = Video.find(video_id)
.....
.....
end
end
Please note using this you will do the work at a background thread, but it wont redirect you to the related page.
You will need to do some workarounds, something like periodically refrishing the page till you see the changes at your view html page, or maybe you can push the changes to your webpage using Faye or Node.js.
In the Update action of Rails controllers usually there is code that looks like this:
def update
#book = Book.find(params[:id])
if #book.update_attributes(params[:book])
redirect_to(#book)
else
render :edit
end
end
In the else case, this will render the edit template. But what if I wanted to use a respond_to, exactly the same way that I have in the edit action, as:
def update
#book = Book.find(params[:id])
if #book.update_attributes(params[:book])
redirect_to(#book)
else
respond_to do |format|
format.html # edit.html.erb
format.json { render :json => #team }
end
end
end
So, if the Update fails, be sure you are returning a json or html depending on the requested format. Does that makes sense? If so, how would you avoid the error: "Render and/or redirect were called multiple times in this action"
Makes sense to me. The answer should be simple, just return after redirect_to.
def update
#book = Book.find(params[:id])
if #book.update_attributes(params[:book])
redirect_to(#book)
return
else
respond_to do |format|
format.html # edit.html.erb
format.json { render :json => #team }
end
end
end
Not sure exactly how you're rendering multiple times, but assuming you are, a well-placed return should tell RAILS to stop processing any further renders after redirecting. If that's all true, it's likely that there's an after_filter interfering from somewhere.
I have a Ruby on Rails application where you can create 'posts'. I started of by using the scaffold generator to give generate the title which is a string and the body which is the content.
Each 'post' has a url of the id, for example /1, /2, /3, etc.
Is there a way to change that to a string of random characters, for example /49sl, /l9sl, etc?
Update
Here is what I have for the posts_controller.rb
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
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render json: #post, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
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.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
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 }
format.json { head :no_content }
end
end
end
Rails uses the to_param method of an ActiveRecord object in order to resolve it into a URL.
Assuming you have a way to generate these unique ids (referring to it as IdGenerator) you can do the following:
1- Generate this id whenever you persist a Post object and save it to the database, let's say under the column url_id
class Post < ActiveRecord::Base
before_create :generate_url_id
def generate_url_id
self.url_id = IdGenerator.generate_id
end
end
2- Inside your Post model override the to_param method:
class Post < ActiveRecord::Base
def to_param
return url_id
end
end
Now post_path(#post) will resolve to /posts/url_id
By the way, you can use SecureRandom.urlsafe_base64 or look here if you don't have an ID generator yet.
Read more on the documentation for to_param.
I hope these two resources are going to help you :
The gem , named obfuscate_id . It represents the ID in a format like :
http://tennisfans.eu/products/4656446465
Another gem - masked_id . It provides a similar functionality . You are in control with a format of the url creation , defining it in a class . Looking at the source it appears , that this gem uses a strategy of obfuscate_id gem .
You can give your posts random URLs by following these 3 steps:
1- In your model (Post.rb), generate a random string for each post before it is saved. For example,
class Post < ActiveRecord::Base
before_create :generate_url_id
def generate_url_id
self.url_id = SecureRandom.urlsafe_base64
end
end
2- In your model (Post.rb), supply a to_param method to override Rails default URL generation. For example:
class Post < ActiveRecord::Base
def to_param
self.url_id
end
end
3- In your controller (PostsController.rb), use a dynamic finder to find your post by its random string. For instance,
class PostsController < ApplicationController
def show
#post = Post.find_by_url_id(params[:id])
...
end
end
I went ahead and put together a complete example and posted it to Github.
Next to Erez manual way you can use the friendly_id gem, with a unique id as your slug.
class Post < ActiveRecord::Base
# FriendlyId
friendly_id :uid
# Set a unique user id on create
before_save :set_uid, on: :create
def set_uid
self[uid] = rand(36**8).to_s(36)
end
end
Please note that the setting of the uid here does not ensure uniqueness. You certainly need to add some kind of validation, but that whole topic is a different one to google.
Friendly_id is a good solution, if you want to use a gem for it.
Follow this screencast:
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid
(either video or asciicast, as you prefer)
Screencasts by Ryan Bates are really well done.
If you still want another option for id generation, you can try using UUIDs:
https://en.wikipedia.org/wiki/Universally_unique_identifier
And a ruby gem to generate them easily:
https://github.com/assaf/uuid
However, I would ask: Why do you want to make them random anyway? If what you are trying to do
is to avoid one of your users from typing another id in the url and accessing data that is not theirs, then probably you would want to limit access in the controller by scoping the finder, with something like this:
def show
#post = current_user.posts.where(:id => params[:id]).first
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
In this case, current_user is a function that returns the current authenticated user (from session, or whatever you application logic dictates).
How to make redirect_to works in those filters?
I'm trying to change
def start
....
redirect_to index
end
def end
...
redirect_to index
end
to
around_filter :around
def around
...
yield
redirect_to index
end
def start
..
end
def stop
...
end
After the action is complete it renders the template automatically, thus you cannot render / redirect after the request is complete. You could solve this by putting the redirect_to at the end of the actions that you need it for. This is not what around_filters were designed to do.
Presumably, your actions already have a redirect_to or render call. You cannot call these methods twice per request.
You can change response.location, which has the same effect as calling redirect_to. An example with an after_filter (the same could be done with around):
after_filter :different_redirect, only:[:create]
def different_redirect
if self.status == 302
response.location = other_thing_path
end
end
def create
#my_thing = MyThing.new(params[:my_thing])
respond_to do |format|
if #my_thing.save
format.html { redirect_to(my_things_path) }
else
format.html { render :action => "new" }
end
end
end