Rails current_page? "fails" when method is POST - ruby-on-rails-3

I have a really simple problem. I have a page of reports and each report has its own tab. I'm using current_page? to determine which tab should be highlighted. When I submit any report, current_page? doesn't seem to work anymore, apparently because the request method is POST.
Is this the intended behavior of current_page? I have a hard time imagining why that would be the case. If it is, how do people normally get around this problem?
Here's an example of a current_page? call:
<li><%= link_to "Client Retention", reports_client_retention_path, :class => current_page?(reports_client_retention_path) ? "current" : "" %></li>

All right, it looks like I figured out the answer to my own question about 5 minutes after putting up a bounty. It looks like current_page? will always return false on POST.
Here's the source code for current_page?:
# File actionpack/lib/action_view/helpers/url_helper.rb, line 588
def current_page?(options)
unless request
raise "You cannot use helpers that need to determine the current " "page unless your view context provides a Request object " "in a #request method"
end
return false unless request.get?
url_string = url_for(options)
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
if url_string.index("?")
request_uri = request.fullpath
else
request_uri = request.path
end
if url_string =~ %r^\w+:\/\//
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
else
url_string == request_uri
end
end
I don't really understand why they would have gone out of their way to make current_page? work only for GET requests, but at least now I know that that's the way it is.

You could create a new current_path? method in your ApplicationHelper:
def current_path?(*paths)
return true if paths.include?(request.path)
false
end
Pass in one or more paths and it returns true if any match the user's current path:
current_path?('/user/new')
current_path?(root_path)
current_path?(new_user_path, users_path '/foo/bar')
Or, you can create a new current_request? helper method to check the Rails controller + action:
def current_request?(*requests)
return true if requests.include?({
controller: controller.controller_name,
action: controller.action_name
})
false
end
Pass in one or more controller + action and it returns true if any match the user's current request:
current_request?(controller: 'users', action: 'new')
current_request?({controller: 'users', action: 'new'}, {controller: 'users', action: 'create'})
==UPDATE==
Ok, I decided to make using current_request? a little less verbose by not requiring that you type out the controller when you are trying to match multiple actions:
def current_request?(*requests)
requests.each do |request|
if request[:controller] == controller.controller_name
return true if request[:action].is_a?(Array) && request[:action].include?(controller.action_name)
return true if request[:action] == controller.action_name
end
end
false
end
Now you can do this:
current_request?(controller: 'users', action: ['new', 'create'])

I was having the same problem when using POST. My solution was to do something like this
def menu_item link_text, link_path
link_class = (request.original_url.end_with? link_path) ? 'active' : ''
content_tag :li, link_to(link_text, link_path), class: link_class
end
where link_path is just url_for(action: 'action', controller: 'controller')

Related

Guard clause in Rails unless

I am using Rubocop in my rails application and it suggests Use a guard clause instead of wrapping the code inside a conditional expression for this. Plz suggest a clean way to rewrite it.
def exist
#account = Account.find_by(id: params[:id])
unless #account.present?
render json: { error: 'Account is not available' }, status: :not_found
end
end
RuboCop is suggesting a change such as this:
def exist
#account = Account.find_by(id: params[:id])
render json: { error: 'Account is not available' }, status: :not_found unless #account.present?
end
But whether this is more 'clean' is subjective.

Process form data before creating table entry

Backstory: I'm building a site that takes in a Soundcloud URL as part of a post. Currently, I store the link they provide and, when a user loads their feed view, I retrieve the associated image / title / favorite count etc. via my post_helper. I have quickly come to realize that this is not scalable and is hurting load times.
So, what I think I should do (feel free to tell me that there is a better way), is to retrieve the SC/YT metadata on form submit and store it along with the other post data (id, user, content etc.) in the posts' table entry. How would I go about calling the helper methods to retrieve such on form submit and include the metadata in the submitted params?
post_helper.rb excerpt:
def soundcloud_info(soundcloud_url, type)
begin
resolve = scClient.get('/resolve', :url => soundcloud_url)
track_info = scClient.get("/tracks/#{resolve.id}")
rescue Soundcloud::ResponseError => e
%Q{ Error: #{e.message}, Status Code: #{e.response.code} }
end
if type == "title"
%Q{#{track_info['title']}}
elsif type == "image"
%Q{#{track_info['artwork_url']}}
elsif type == "favCount"
%Q{Favorite count: #{track_info['favoritings_count']}}
end
end
post_controler.rb excerpt:
def create
#post = current_user.posts.build(params[:post])
if #post.save
flash[:success] = "Your post was successful!"
redirect_to root_url
else
#feed_items = current_user.feed.paginate(page: params[:page])
render 'static_pages/home'
end
end
So apparently it's pretty straight forward... all I need to do is modify the parameters in the controller before I call #post = current_user.posts.build(params[:post]). My issue was that I was trying to do so in the helper.
I haven't quite adapted the whole thing to get all my required fields, but here's an example of how I have adapted the create method to pull the api URL out if someone submits SoundCloud's embed iframe.
micropost_controller.rb excerpt:
def create
#url = params[:post][:link_html]
if #url[/^.*src="(https|http):\/\/w.soundcloud.com\/player\/\?url=(.*)">/]
params[:post][:link_html] = CGI::unescape($2)
end
#post = current_user.posts.build(params[:post])
if #post.save
flash[:success] = "Your post was successful!"
redirect_to root_url
else
#feed_items = current_user.feed.paginate(page: params[:page])
render 'static_pages/home'
end
end

Destroy method failing in controller test

I'm experiencing a bizarre issue testing a destroy method. I'm using FactoryGirl and Rspec.
Here's a look at the method in question. As you can see, it doesn't actually destroy the dealer, just set it and it's dependent object's active attributes to false:
dealers_controller.rb
def destroy
#dealer = Dealer.find(params[:id])
#dealer.active = false
#dealer.save!
#dealer.leads.each { |lead|
lead.active = false
lead.save!
}
#dealer.users.each { |user|
user.active = false
user.save!
}
redirect_to dealers_path
end
When I run this method in the application it does exactly what it should do. Now, on to the test.
dealers_controller_spec.rb
describe "#destroy" do
context "when deleting a valid record" do
let(:dealer) { FactoryGirl.create(:dealer_with_stuff) }
before do
#user = FactoryGirl.build(:admin_user)
login_user
delete :destroy, :id => dealer.id
end
it { should assign_to(:dealer).with(dealer) }
it { should redirect_to(dealers_path) }
it { should set_the_flash }
it "is no longer active" do
dealer.active.should be_false
end
it "has no active users" do
dealer.users.each do |user|
user.active.should be_false
end
end
it "has no active leads" do
dealer.leads.each do |lead|
lead.active.should be_false
end
end
end
end
The first 3 tests pass, but the last 3 all fail (weirdly, the user.active.should be_false test only fails if I put a sleep(10) after delete :destroy up above, but let's not get into that issue now). So when I check the test log, it goes through the entire destroy process, but then does a ROLLBACK, so for some reason it doesn't save any of the records; but it doesn't give me any more information than that.
Does anyone have any thoughts on this? I've tried everything I can possibly think of.
What if you reload the dealer? The dealer in your tests is different from the #dealer object in the controller (ActiveRecord doesn't do identity maps).
before do
#user = FactoryGirl.build(:admin_user)
login_user
delete :destroy, :id => dealer.id
dealer.reload # << add this
end

Rails 3: Controller params default value

I am using a remote form_for for my show action to retrieve content based on the params passed by this form.
= form_tag modelname_path(#modelname), :id=>"select_content_form", :remote => true, :method => 'get' do
= text_field_tag :content_type, params[:content_type], :id=>"select_content_type"
= submit_tag "submit", :name => nil, :id=>"select_content_submit"
And I alter the content in controller as follows:
# Default params to "type1" for initial load
if params[:content_type]
#content_type = params[:content_type];
else
#content_type = "type1"
end
case #content_type
when "type1"
# get the content
#model_content = ...
when "type1"
# get the content
#model_content = ...
My question is, whether the above approach is the only we can set defaults for params or can we do it in a better manner. This works but I would like to know if this is the right approach.
UPDATE
Based on the suggestion below, I used the following and got an error on defaults.merge line:
defaults = {:content_type=>"type1"}
params = defaults.merge(params)
#content_type = params[:content_type]
A good way of setting default options is to have them in a hash, and merge your incoming options onto it. In the code below, defaults.merge(params) will overwrite any values from the params hash over the default ones.
def controller_method
defaults = {:content=>"Default Content", :content_type=>"type1"}
params = defaults.merge(params)
# now any blank params have default values
#content_type = params[:content_type]
case #content_type
when "type1"
#model_content = "Type One Content"
when "type2"
#etc etc etc
end
end
If there is a static list of types you could make it a dropdown box and just don't include a blank option so that something is always selected. But if you're stuck with a textbox you could clean up the controller action by using a before filter:
class FoosController < ActionController::Base
before_filter :set_content_type, :only => [:foo_action]
def foo_action
...
end
protected
def set_content_type
params[:content_type] ||= "type1"
end
end
I wanted to add to this discussion a working way to set default params:
defaults = { foo: 'a', bar: 'b' }
params.replace(defaults.merge(params))
This avoids assigning a local variable via "params =".

validates_acceptance_of still saves the record

I am using ruby 1.9.2-p180, rails 3.0.7. I have used validates_acceptance_of since the user has to agree to our terms and conditions. We don't have a column for this, but I understand that "If the database column does not exist, the terms_of_service attribute is entirely virtual. " from http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000082
Anyway, I double checked this by smoke testing the app manually and I see from the logs that the record is still inserted into the db, which is weird because upon submitting the form, I am redirected back to the form with the error: "Must agree to terms and conditions"(which made me think it worked before)
Am I doing something wrong here?
_form.haml:
%label.checkbox-label{:for => "operator_terms_and_conditions"}
= f.check_box :terms_and_conditions
I agree to
= link_to "Terms and Conditions", operator_terms_path, :target => "_blank"
operators_controller:
def create
user_params = params[:operator][:user]
user_params.merge!(:login => user_params[:email])
#password = params[:operator][:user][:password]
Operator.transaction do # don't save User if operator is invalid
#operator = Operator.create(params[:operator])
end
respond_to do |format|
unless #operator.new_record?
UserMailer.operator_confirmation_email(#operator, #password).deliver
UserMailer.operator_registration_admin_notification_email(#operator).deliver
UserSession.create(#operator.user)
format.html {redirect_to new_operator_aircraft_path}
else
format.html { render :action => "new" }
end
end
end
and in the model:
validates_acceptance_of :terms_and_conditions
Found the answer. The problem was not with validates_acceptance_of but rather with how I was saving the data. When an operator was created, a user was also created that was tied to it and it was this user that was being inserted into the db.
This happens because although the operator was being rolled back(because it wasn't valid) the user was still created(because it was not in a transaction).
I solved this by using nested_transactions:
operator model:
...
User.transaction(:requires_new => true) do
create_user
raise ActiveRecord::Rollback unless self.valid?
end
...