Here's my code I'm spec'ing:
def vote_up
get_vote
#vote.value += 1 unless #vote.value == 1
#vote.save
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
Seems pretty straightforward. This is what I'm trying to spec it with :
it "should vote up" do
#mock_cat = Factory.create(:category)
Category.stub(:mock_cat)
#mock_post = Factory.create(:post)
Post.stub(:current_post).and_return(#mock_post)
#vote = Factory(:vote)
get :vote_up, :id => #vote
#vote.reload.value.should == 1
end
It's returning this :
undefined method `to_i' for #<Vote:0x1052a4af8>
I can't really figure out why though. If I stubbed my mock_vote as (:vote), wouldn't it run through the controller method and get +1 attributed to it?
Update
Here's the private method from my posts_controller.rb
private
def get_vote
current_post = Post.all.detect{|r| r.id == params[:id].to_i}
#post = current_post
#vote = current_post.votes.find_by_user_id(current_user.id)
unless #vote
#vote = Vote.create(:user_id => current_user.id, :value => 0)
current_post.votes << #vote
end
end
Answer:
it "should vote up" do
#mock_cat = Factory.create(:category)
Category.stub(:mock_cat)
#post = Factory(:post)
get :vote_up, :id => #post.id
#post.reload.vote_score.should == 1
end
I recommend to move logic from controller to model. Models are much easier to spec. Instead of:
def vote_up
get_vote
#vote.value += 1 unless #vote.value == 1
#vote.save
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
Your controller should looks like this:
def vote_up
#vote = Vote.vote_up(params[:id])
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
or, if you really need get_vote (may be you should move it to before_filter?)
def vote_up
#vote.vote_up
respond_to do |format|
format.js { render :action => "vote", :layout => false }
end
end
If necessary. add exceptions (may be with rescue_from)
Then, you will need specs only in model, and some integration (rspec,steac, cucumber)
It's hard to follow what exactly your stubs are doing because you didn't post the code for get_vote. But I think you're over-using stubs when you could just be taking advantage of the factories you're already creating.
it "should vote up" do
# Does your Vote belong to a post or a category or anything? I don't know.
# Modify as needed -- Factory(:vote, :post => Factory(:post))
#vote = Factory(:vote)
get :vote_up, :id => #vote
#vote.reload.value.should == 1
end
Note the reload. Your controller's modifying a record we've already pulled from the database, so we need to reload it to check its new value.
Related
Here is the relevant code:
def index
#customer = Customer.new
if(session[:admin_id])
#allJobs = Job.where(:merchant_id => session[:admin_id].to_s).paginate(:per_page => 15, :page => params[:page])
else
#allJobs = Job.all(:order => :_id.desc).paginate(:per_page => 15, :page => params[:page])
end
respond_to do |format|
##allJobs = Job.where(:merchant_id => session[:admin_id].to_s)
format.html
#format.csv {send_data to_csv(#all_customers)}
format.csv { render :csv => #allJobs }
When I click export - I get a csv of #allJobs... for just the 15 items displayed.
As you can see, in the format block I tried to redefine #allJobs to just everything with the merchant_id, but that throws an error:
No comma format for class Plucky::Query defined for style {}
I tried changing the var name to something else - same error. I just want to export all the jobs that match the merchant_id statement.
If anyone lands here after googling comma errors like these:
No comma format for class String defined for style {}
No comma format for class TrueClass defined for style {}
Then check to make sure you're not abusing the .to_comma method. You do not need to call this method in your rails controller.
Incorrect:
respond_to do |format|
format.html
format.csv { render :csv => #things.to_comma}
end
Correct:
respond_to do |format|
format.html
format.csv { render :csv => #things}
end
One way of working around pagination's limit for particular request types, such as CSV for an export, uses controller checking the request object before calling paginate. For example:
def index
#customer = Customer.new
if(session[:admin_id])
#allJobs = Job.where(:merchant_id => session[:admin_id].to_s)
unless request.format == "csv"
#allJobs= #allJobs.paginate(:per_page => 15, :page => params[:page])
end
else
if request.format == "csv"
#allJobs = Job.all(:order => :_id.desc)
else
#allJobs = Job.all(:order => :_id.desc).paginate(:per_page => 15, :page => params[:page])
end
end
respond_to do |format|
##allJobs = Job.where(:merchant_id => session[:admin_id].to_s)
format.html
#format.csv {send_data to_csv(#all_customers)}
format.csv { render :csv => #allJobs }
Note that the :admin_id case, we can chain the pagnation method while in the non admin_id case the .all method returns an array that we can't call paginate on.
I have no idea why this works, but:
format.csv { render :csv => #allJobs.paginate }
Just adding .paginate without calling any options made this export all Jobs regardless of the 15 displayed per page.
Full updated code:
def index
#customer = Customer.new
if(session[:admin_id])
#allJobs = Job.where(:merchant_id => session[:admin_id]
else
#allJobs = Job.all(:order => :_id.desc).paginate(:per_page => 50, :page => params[:page])
end
respond_to do |format|
##allJobs = Job.where(:merchant_id => session[:admin_id].to_s)
format.html do
#allJobs = #allJobs.paginate(:per_page => 15, :page => params[:page])
end
format.csv { render :csv => #allJobs.paginate }
I tried your solution and couldnt get it to work. In the end, which I am sure is not optimal, I generated another instance variable without the pagination and called that for the export. Example code below
...
#allJobs = Job.all(:order => :_id.desc).paginate(:per_page => 50, :page => params[:page])
#allJobs_xls = Job.all(:order => :_id.desc)
Here i send the user.id as params dd
<h3><%= link_to("Lend Asset", {:controller => 'empassets', :action=> 'index', :dd => user.id})%></h3>
In controller empassets i fetch it by
def index
#id = params[:dd]
#empassets = Empasset.where(:ad => #id)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #empassets }
end
end
def show
#id = params[:dd]
#empasset = Empasset.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #empasset }
end
end
def new
#id = params[:dd]
#empasset = Empasset.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #empasset }
end
end
def edit
#id = params[:dd]
#empasset = Empasset.find(params[:id])
end
I need this #id in all new show edit method. But it takes in index only as i mention it in index. How can i make such that if Lend asset is click then #id= params[:id] have value in all methods. How can it is possible to make it available for another #id = params[:id] is not send in that controller?
Maybe will be better if you store the current user in session and after that, capture the user model with a filter in the controller, like this:
# controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_current_user_in_model
private
def current_user
#current_user ||= User.find(params[:dd]) || User.new
end
# This method save the current user in the user model, this is useful to have access to the current user from a model and not from the controller only
def set_current_user_in_model
User.current_user current_user if not current_user.nil?
end
end
# models/user.rb
class User < ActiveRecord::Base
#...
# This is useful to get the current user inside a model
def self.current_user(user = nil)
##current_user = (user || ##current_user)
end
#...
end
Basically, my idea is store that information inside a model with a filter, you can use session if you wanth to get the information (user id).
def index
session[:user_id] = params[:dd]
#empassets = Empasset.where(:ad => session[:user_id])
respond_to do |format|
format.html # index.html.erb
format.json { render json: #empassets }
end
end
def show
#empasset = Empasset.find(session[:user_id] || params[:dd])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #empasset }
end
end
Note I used session[:user_id] || params[:dd] because maybe, the session information was not stablished and you give it :dd parameter. But if you want to stablish the #id variable, you can use a filter like before.
But I don't know what is the main problem.
Edit
# controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_dd_param, :except => :index
def index
session[:dd] = params[:dd] # Here you write the session
#current_user ||= User.find(params[:dd]) || User.new
end
# ...
protected
def set_dd_param
params[:dd] = session[:dd] || -1 # Here you read the session a write the params variable
end
end
Sorry for the delay.
Unless you want to store it to the session, there is no way to automatically make #id available to every method in the controller.. but you can add the param to each link/form, like so:
<%= link_to("New Asset", {:controller => 'empassets', :action=> 'new', :dd => #id})%>
<%= link_to("Show Asset", {:controller => 'empassets', :action=> 'show', :dd => #id})%>
where these links are in the index view and #id is set in the index method.
Not exactly sure what the goal is here, but something like this may work.
def index
$id = params[:dd]
#empassets = Empasset.where(:ad => #id)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #empassets }
end
Here's my controller method
def check
#added_word = Word.where(:word => params[:word][:word]).first
respond_to do |format|
format.js
end
end
In the view I have the following code:
$("#added_words").append("<%= #added_word.word %>, ");
How should i change the controller in order not to render the view if no record found ( false returned)?
Not very idiomatic, but it answers your question.
def check
#added_word = Word.where(:word => params[:word][:word]).first
if #added_word.present?
respond_to {|f| f.js}
else
render :text => '' # or whatever
end
end
The following attempt_login method is called using Ajax after a login form is submitted.
class AccessController < ApplicationController
[...]
def attempt_login
authorized_user = User.authenticate(params[:username], params[:password])
if authorized_user
session[:user_id] = authorized_user.id
session[:username] = authorized_user.username
flash[:notice] = "Hello #{authorized_user.name}."
redirect_to(:controller => 'jobs', :action => 'index')
else
[...]
end
end
end
The problem is that redirect_to doesn't work.
How would you solve this ?
Finally, I just replaced
redirect_to(:controller => 'jobs', :action => 'index')
with this:
render :js => "window.location = '/jobs/index'"
and it works fine!
There is very easy way to keep the flash for the next request. In your controller do something like
flash[:notice] = 'Your work was awesome! A unicorn is born!'
flash.keep(:notice)
render js: "window.location = '#{root_path}'"
The flash.keep will make sure the flash is kept for the next request.
So when the root_path is rendered, it will show the given flash message. Rails is awesome :)
I think this is slightly nicer:
render js: "window.location.pathname='#{jobs_path}'"
In one of my apps, i use JSON to carry on the redirect and flash message data. It would look something like this:
class AccessController < ApplicationController
...
def attempt_login
...
if authorized_user
if request.xhr?
render :json => {
:location => url_for(:controller => 'jobs', :action => 'index'),
:flash => {:notice => "Hello #{authorized_user.name}."}
}
else
redirect_to(:controller => 'jobs', :action => 'index')
end
else
# Render login screen with 422 error code
render :login, :status => :unprocessable_entity
end
end
end
And simple jQuery example would be:
$.ajax({
...
type: 'json',
success: functon(data) {
data = $.parseJSON(data);
if (data.location) {
window.location.href = data.location;
}
if (data.flash && data.flash.notice) {
// Maybe display flash message, etc.
}
},
error: function() {
// If login fails, sending 422 error code sends you here.
}
})
Combining the best of all answers:
...
if request.xhr?
flash[:notice] = "Hello #{authorized_user.name}."
flash.keep(:notice) # Keep flash notice around for the redirect.
render :js => "window.location = #{jobs_path.to_json}"
else
...
def redirect_to(options = {}, response_status = {})
super(options, response_status)
if request.xhr?
# empty to prevent render duplication exception
self.status = nil
self.response_body = nil
path = location
self.location = nil
render :js => "window.location = #{path.to_json}"
end
end
I didn't want to modify my controller actions so I came up with this hack:
class ApplicationController < ActionController::Base
def redirect_to options = {}, response_status = {}
super
if request.xhr?
self.status = 200
self.response_body = "<html><body><script>window.location.replace('#{location}')</script></body></html>"
end
end
end
This is a continuation of Confused as to which Prototype helper to use. My code has been updated to reflect other user's suggestions:
(model) message.rb:
class Message < ActiveRecord::Base
after_create :destroy_old_messages
def old_messages
messages = Message.all(:order => 'updated_at DESC')
if messages.size >= 24
return messages[24..-1]
else
return []
end
end
protected # works without protected
def destroy_old_messages
messages = Message.all(:order => 'updated_at DESC')
messages[24..-1].each {|p| p.destroy } if messages.size >= 24
end
end
(view) index.html.erb:
<div id="messages">
<%= render :partial => #messages %>
</div>
<%= render :partial => "message_form" %>
(view) _message.html.erb:
<% div_for message do %>
<%= h message.created_at.strftime("%X") %> - <%= h message.author %><%= h message.message %>
<% end %>
(view) _message_form.html.erb:
<% remote_form_for :message, :url => { :action => "create" }, :html => { :id => 'message_form'} do |f| %>
<%= f.text_area :message, :size => "44x3" %><br />
<%= submit_to_remote 'submit_btn', 'submit', :url => { :action => 'create' } %><br />
<% end %>
(view) create.rjs:
page.insert_html :top, :messages, :partial => #message
page[#message].visual_effect :grow
page[:message_form].reset
flash[:notice]
flash.discard
# #old_messages.each do |m|
# page.remove(m.id)
# end
(controller) messages_controller.rb:
class MessagesController < ApplicationController
def index
#messages = Message.all(:order => "created_at DESC")
respond_to do |format|
format.html
format.js
end
end
def new
#message = Message.new
respond_to do |format|
format.html
end
end
def create
#message = Message.new(params[:message])
# #old_messages = Message.old_messages
respond_to do |format|
if #message.save
flash[:notice] = 'message created.'
format.html { redirect_to(messages_url) }
format.js
else
format.html { render :action => "new" }
end
end
end
def update
#message = Message.find(params[:id])
respond_to do |format|
if #message.update_attributes(params[:message])
flash[:notice] = 'message updated.'
format.html { redirect_to(messages_url) }
format.js
else
format.html { render :action => "edit" }
end
end
end
def destroy
#message = Message.find(params[:id])
#message.destroy
respond_to do |format|
format.html { redirect_to(messages_url) }
format.js
end
end
end
With the exception of the old_messages method in the model, all of the commented code were recommended changes from the previous post to make this work. But as soon as I uncomment the last three lines from create.rjs and #old_messages = Message.old_messages from the controller, I can't even submit messages with the message_form partial. Can anyone see what's wrong here? I'm just trying to create a basic app to help further my understanding of rails and rjs. I would greatly appreciate any suggestions or corrections you have to share, thank you for reading my post.
it's not what you're asking for, but i have a suggestion...
to get the older messages you can use named_scope.
in your case, (if i understood what you want) i think it would be something like:
# model
named_scope :limit, lambda { |num| { :limit => num } }
named_scope :order, lambda { |ord| { :order => ord } }
and
#controller
Message.order("updated_at DESC").limit(24)
the problem is that old_messages is an instance method, and you're calling from a class.
if you do
def self.old_messages
# ...
end
it's now a class method.
this blog has a good explanation about class and instance methods.