Rails rendering JSON causing helper method to be called twice - ruby-on-rails

I'm getting strange behavior when I render JSON from my Rails app. A helper method is run twice when render :json is called. Here's the controller and method:
class UsersController < ApplicationController
def current
render json: { :errors => "Incorrect credentials" }, :status => :bad_request
end
end
I have the following helper module, with a puts statement for debugging:
module SessionsHelper
def current_user
puts "current_user"
if encrypted_id = request.headers[:user_id]
user = User.find_by(id: EncryptionService.decrypt(encrypted_id))
if user && user.authenticated?(request.headers[:remember_token])
#curent_user = user
end
end
end
end
The SessionsHelper is included in the Application Controller
class ApplicationController < ActionController::API
include SessionsHelper
end
When sent the request, I get the following:
Started GET "/user/current" for ::1 at 2021-02-12 22:06:47 -0800
Processing by UsersController#current as */*
Parameters: {"user"=>{}}
current_user
[active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (0.06ms)
Completed 400 Bad Request in 1ms (Views: 0.7ms | ActiveRecord: 6.6ms | Allocations: 383)
current_user was printed, even though the function was never called. When I comment out the render json: statement, leaving:
class UsersController < ApplicationController
def current
end
end
I get the following:
Started GET "/user/current" for ::1 at 2021-02-12 22:09:43 -0800
Processing by UsersController#current as */*
Parameters: {"user"=>{}}
Completed 204 No Content in 0ms (ActiveRecord: 4.2ms | Allocations: 78)
current_user is not printed. Why would render json: call current_user? In my actual application, this is causing the database to be hit twice (although Rails wisely caches the result).
UPDATE: I'm onto something here. I ran puts caller[0] to see who was calling the function. The result:
/Users/izaguirrejoe/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/active_model_serializers-0.10.12/lib/action_controller/serialization.rb:40:in 'serialization_scope'
def serialization_scope
return unless _serialization_scope && respond_to?(_serialization_scope, true)
send(_serialization_scope)
end
Any ideas?

I see that you are using active_model_serializers, if you check out their docs it says here, the default serialisation scope is :current_user. It also emphasizes that
IMPORTANT: Since the scope is set at render, you may want to customize it so that current_user isn't called on every request. This was also a problem in 0.9.
This causes that the current_user method is always invoked. If you want to avoid this behaviour, you can set the serialization_scope in the controller for example:
class UsersController < ApplicationController
serialization_scope nil # also you can pass a custom method here
def current
render json: { :errors => "Incorrect credentials" }, :status => :bad_request
end
end
or in some cases only by calling self.class.serialization_scope nil.

Related

Shopify App causing return code 302

I have this piece of code which is triggered when a new order is created in my Shopify store:
class CustomwebhooksController < ShopifyApp::WebhooksController
#the line below is to avoid the CSR error
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
include ShopifyApp::WebhookVerification
# This method is triggered when the user completes the checkout process
# Gets the order number and see the message "Your order is confirmed"
# I should use this as the trigger to update the other SKU for the DT solution:
def orders_create
request.body.rewind
data = JSON.parse(request.body.read)
#redirect_to controller: "home", action: "update_stock", status: :found
#MyHomeController = HomeController.new
#MyHomeController.update_stock
#do not remove line below
head :ok
end
end
This is the code in my home controller:
class HomeController < ShopifyApp::AuthenticatedController
def update_stock
#products = ShopifyAPI::Product.find(:all, :params => {:title => 'DT382'})
#products.each do |product|
puts(product.id)
end
end
end
Somehow the call to #MyHomeController.update_stock generates a 302 as shown below:
Rendered inline template (0.4ms)
Completed 302 Found in 689ms (Views: 0.9ms | ActiveRecord: 0.0ms)
Am I missing anything?
I would like to challenge your design for a minute.
Firstly, if you're going to redirect to a different controller/action, why aren't you using redirect_to update_stock_path where the path is specified in your routes file?
Secondly, why redirect at all? You're receiving a webhook with some payload and then you need to hit the Shopify API to get product data. To me, this is far more suitable as a background job. Furthermore, designing this as receive webhook -> schedule background job -> do work in the job will be more scalable than what you're doing now.
I managed to figure out the solution after carefully reading the Shopify API documentation.
class HomeController < ShopifyApp::AuthenticatedController
def update_stock
shop_url = "https://#{ENV['API_KEY']}:#{ENV['API_SECRET']}#myshopifyshop.myshopify.com/admin"
ShopifyAPI::Base.site = shop_url
#products = ShopifyAPI::Product.find(:all, :params => {:title => 'DT382'})
#products.each do |product|
puts(product.id)
end
ShopifyAPI::Base.clear_session
end
end

using strong parameters for a server-side autocomplete resource

playing around with this railscast, except I'm using Rails 5 where attr_accessible is deprecated. VenueSuggestion is a resource for suggesting venues in the db as user types something in the related field of a form. the problem I'm having right now is that as I start typing things that match db contents, there are no search results.
the model:
class VenueSuggestion < ApplicationRecord
# should anything go in place of attr_accessible?
def self.terms_for(prefix)
suggestions = where("term like ?", "#{prefix}_%")
suggestions.order("popularity desc").limit(10).pluck(:term)
end
def self.index_venues
Venue.find_each do |venue|
index_term(venue.name)
index_term(venue.address)
venue.name.split.each { |t| index_term(t) }
end
end
def self.index_term(term)
where(term: term.downcase).first_or_initialize.tap do |suggestion|
suggestion.increment! :popularity
end
end
end
the controller:
class VenueSuggestionsController < ApplicationController
#is this right?
def create
VenueSuggestion.create(params[:venue_suggestion])
end
def index
render json: VenueSuggestion.terms_for(params[:term])
end
# is this right?
private
def venue_suggestion_params
params.require(:venue_suggestion).permit(:term, :popularity)
end
end
the rake task:
namespace :venue_suggestions do
desc "Generate venue suggestions for event form"
task :index => :environment do
VenueSuggestion.index_venues
end
end
what the log shows:
Started GET "/venue_suggestions?term=sp" for ::1 at 2016-05-25 21:27:31 -0400
Processing by VenueSuggestionsController#index as JSON
Parameters: {"term"=>"sp"}
(1.4ms) SELECT "venue_suggestions"."term" FROM "venue_suggestions" WHERE (term like 'sp_%') ORDER BY popularity desc LIMIT $1 [["LIMIT", 10]]
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (0.06ms)
Completed 200 OK in 4ms (Views: 0.6ms | ActiveRecord: 1.4ms)
The strong params features provides an interface for protecting attributes from end-user assignment. This makes Action Controller parameters forbidden to be used in Active Model mass assignment until they have been whitelisted.
class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end
Keep in mind that you will need to specify term inside venue_suggestions with something like
params = ActionController::Parameters.new(venue_suggestion: { term: "something" })
Source: http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html

Rails controller rescue exception in `lib` directory

I'm using a wrapper around a Wikipedia OmniAuth gem in my Devise initializer that is periodically throwing JWT::DecodeErrors. I'm trying to handle these errors with a custom error message, rather than throwing a 500. (Stick with me here.)
In my Devise initializer, I set up the strategy as such:
config.omniauth :mediawiki,
Figaro.env.wikipedia_token,
Figaro.env.wikipedia_secret,
strategy_class: CustomStrategy,
client_options: {
site: "https://#{Figaro.env.wiki_language}.wikipedia.org"
}
My CustomStrategy inherits from the default MediaWiki strategy and adds some data to the request object to check for it later in the ErrorsController:
# lib/custom_strategy.rb
class LoginError < StandardError; end
class CustomStrategy < OmniAuth::Strategies::Mediawiki
def parse_info(jwt_data)
begin
super
rescue JWT::DecodeError
request.env['JWT_ERROR'] = true
request.env['JWT_DATA'] = jwt_data
raise ::LoginError.new("\nA JWT::DecodeError occurred. Here is the web token data:\n#{jwt_data.body.inspect}")
end
end
end
So here I try to catch the JWT::DecodeError by raising a LoginError that I'm hoping to catch in my controller:
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_login_error
...
protected
def handle_login_error
redirect_to errors_login_error_path
end
end
The problem is that rescue_from only handles exceptions thrown in the controller.
Since this exception is thrown in my strategy, I don't have the opportunity to rescue it, and Rails just treats it as a 500:
[2016-01-26 12:10:02.183 FATAL] StandardError (
A JWT::DecodeError occurred. Here is the web token data:
"{\"error\":\"mwoauthdatastore-access-token-not-found\",\"message\":\"No approved grant was found for that authorization token.\"}"):
lib/custom_strategy.rb:9:in `rescue in parse_info'
lib/custom_strategy.rb:6:in `parse_info'
[2016-01-26 12:10:02.251 INFO ] Processing by ErrorsController#internal_server_error as HTML
[2016-01-26 12:10:02.251 INFO ] Parameters: {"oauth_verifier"=>"XXXXXXXXXXXXXXXX", "oauth_token"=>"XXXXXXXXXXXXXXX"}
[2016-01-26 12:10:02.971 INFO ] Rendered errors/internal_server_error.html.erb within layouts/application (3.8ms)
[2016-01-26 12:10:03.204 INFO ] Rendered shared/_head.html.haml (10.1ms)
[2016-01-26 12:10:03.215 INFO ] Rendered shared/_nav.html.haml (9.7ms)
[2016-01-26 12:10:03.218 INFO ] Rendered shared/_foot.html.haml (2.3ms)
[2016-01-26 12:10:03.219 INFO ] Completed 500 Internal Server Error in 967ms (Views: 265.8ms | ActiveRecord: 0.0ms)
So here, Rails treats it as a 500, and I'm using custom error pages, not the Rails defaults.
For reference, here's my ErrorsController:
class ErrorsController < ApplicationController
respond_to :html, :json
def file_not_found
render status: 404
end
def unprocessable
render status: 422
end
def internal_server_error
if request.env['JWT_ERROR']
return login_error
end
render status: 500
end
def incorrect_passcode
render status: 401
end
def login_error
#return render status: 599
end
end
What's the best way to handle this exception that happens in the jwt-ruby gem, which is a dependency of the oauth-mediawiki gem that I'm wrapping with a custom strategy that hopes to catch the JWT::DecodeError and handle it within my app? If this specific error arises, I want to render a special view, not just the 500 view.
Edit 2: Further Articulation of the Problem
The problem is that I can't figure out a way to rescue the exception I've rescued and re-raised (JWT::DecodeError) in a way that allows me to render a custom view. As you can see from the log above, it just renders ErrorsContoller#internal_server_error.

Rails: How to read the value of a select without a form

I'm running into a perplexing issue that I can only resolve partway, and hopefully, someone more experienced can tell me whether I can achieve what I wish, or if I'm barking up the wrong tree.
I have a Rails 4 application which uses Devise and CanCan. I'd like to make a small subset of application functionality available to guest users (not logged in). I can achieve this by specifying a get route to a controller method and using link_to to reach that method. I cannot, however, figure out how to get the value of a select box to pass along as parameters on that page without making that view a form using form_tag (there is no model associated with this view).
I can pass hardcoded params along like so:
<%= link_to "Month", activities_search_month_path(:resource_id => 4) %>
but I'd rather have something like:
<%= link_to "Month", activities_search_month_path(:foo => :resource_id) %>
where the second symbol refers to the value of a select_tag. This second example delivers a literal value of "resource_id" when I dump the :foo key unless I convert my view to a form.
If I turn the view into a form by enclosing all the erb in a form_tag, I get a 401 Forbidden error, after which the Devise sign in form is rendered. My guess is that any time you want to process a form, Rails (or Devise) demands authentication on some level. The behavior is the same when I use button_to rather than link_to, since button_to wraps itself in a form under the covers.
How can I set that resource_id argument in my link_to, or will I be forced to create a guest user access level and silently log in guest users? It's important for the UX that users can access this functionality with the least amount of effort possible.
Thanks in advance.
Addendum: quick_search method from controller
def quick_search
puts "quick search 0"
if(params[:time_period] == 'today')
#resource = Resource.find(params[:resource_id])
#site = Site.find(params[:site_id])
#time_period_string = "Activities for #{localize_date(Date.today)} at #{#resource.name}, #{#site.name}"
puts "quick search 1"
if user_signed_in?
puts "quick search 2a"
#activities = Activity.where("system_id = ? and start_date = ? and activity_status_id = ? and resource_id = ?", current_system_id, #today, 2, params[:resource_id])
else
puts "quick search 2b"
if(Setting["#{current_subdomain_not_signed_in}.quick_search_guest_access"] == 'true')
puts "quick search 3a"
current_system_id = current_system_id_not_signed_in
#activities = Activity.where("system_id = ? and start_date = ? and activity_status_id = ? and resource_id = ?", current_system_id, #today, 2, params[:resource_id])
else
puts "quick search 3b"
redirect_to '/users/sign_in'
end
end
end
Note: the quick_search method is never entered. CanCan (or maybe Devise) steps in immediately and redirects to sign in:
Console output:
Started GET "/activities/quick_search" for 127.0.0.1 at 2015-04-12 18:01:58 -0700
Processing by ActivitiesController#quick_search as HTML
(0.2ms) SELECT DISTINCT "systems"."subdomain" FROM "systems"
Completed 401 Unauthorized in 1ms
Started GET "/users/sign_in" for 127.0.0.1 at 2015-04-12 18:01:58 -0700
Processing by Devise::SessionsController#new as HTML
(0.2ms) SELECT DISTINCT "systems"."subdomain" FROM "systems"
Rendered layouts/_header.html.erb (0.8ms)
Rendered devise/shared/_links.html.erb (4.1ms)
Rendered devise/sessions/new.html.erb within layouts/application (14.7ms)
Rendered layouts/_footer.html.erb (0.0ms)
Completed 200 OK in 285ms (Views: 282.3ms | ActiveRecord: 0.2ms)
Ability.rb
can :quick_search, Activity
can :search_day, Activity
can :search_week, Activity
can :search_month, Activity
The odd thing is that link_to quick_search fails with a 401, but link_to the other three methods works fine -- I just can't get parameters to them dynamically.
If you are using CanCan(Can?) you can define a special ability for guests.
How does your Ability-model look?
Which controller are handling the action that you want to view?
How do you authenticate with CanCan in this controller?
https://github.com/CanCanCommunity/cancancan/wiki/CanCan-2.0
Under the "Defining Abilities" you can see a non-user example.
Fixing CanCan is probably the best option, if you do not want to:
For the part with the link and select box it would be easiest to handle as a form and then handle the redirect in the controller, it could also be done with a remote ajax form.
http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html
This should work:
<% form_tag Activity, activity_quick_search_path, remote: true do %>
<%= select_tag :resource_id...%>
<%= submit_tag %>
<%end%>
Edit after comments:
The culprit here is(was) an:
before_action :authenticate_user!
Causing Devise to redirect to sign in page.
However, if you have CanCan you shouldn't need the authenticate_user.
Short example:
With only Devise I would do:
class NewsController < ApplicationController
before_action :authenticate_user!
before_action :set_news, except: [ :index, :new ]
def index
#news = News.all
end
def show
end
def new
#news = News.new
end
def edit
end
def create
#news = News.new(news_params)
flash[:notice] = 'News created' if #news.save!
redirect_to #news
end
def update
#news.update! news_params
redirect_to #news
end
def destroy
#news.destroy!
redirect_to News
end
private
def news_params
params.require(:news).permit(some_attributes)
end
def set_news
#news = News.find(params[:id])
end
end
How it looks with CanCanCan:
class NewsController < ApplicationController
load_and_authorize_resource
def index
end
def show
end
def new
end
def edit
end
def create
flash[:notice] = 'News created' if #news.save!
redirect_to #news
end
def update
#news.update! news_params
redirect_to #news
end
def destroy
#news.destroy!
redirect_to News
end
private
def news_params
params.require(:news).permit(some_attributes)
end
end
Which I find super neat 😄
Hope that this can help as well.

Backbone Mongoid Rails set up will not let me update a record

Can someone help me understand how to make Ryan Bate's screencast on Backbone.js work with MongoDB as my database while using the Mongoid gem.
This is what I am seeing.
When I create a new entry via the console, similar to how Ryan did it in the video with entry.create, Rails adds that entry just fine. Below is my Ruby log and my JavaScript headers log from Chrome Inspector.
Ruby Log
Started POST "/api/entries" for 127.0.0.1 at 2012-02-12 17:31:24 -0600
Processing by EntriesController#create as JSON
Parameters: {"name"=>"Heather", "entry"=>{"name"=>"Heather", "action"=>"create", "controller"=>"entries"}}
MONGODB w_market_development['system.namespaces'].find({})
MONGODB w_market_development['entries'].insert([{"_id"=>BSON::ObjectId('4f384bcc504b9348be000003'), "name"=>"Heather"}])
Completed 201 Created in 11ms (Views: 2.4ms)
Headers Log
Request URL:http://0.0.0.0:3000/api/entries
Request Method:POST
Status Code:201 Created
Request Headers (14)
Request Payload
{"name":"Heather"}
As you can see it posted fine. Now let me show you an update via the entry.save() example Ryan showed us.
Ruby Log
Started POST "/api/entries" for 127.0.0.1 at 2012-02-12 17:34:25 -0600
Processing by EntriesController#create as JSON
Parameters: {"_id"=>"4f38152c504b9345dc000005", "name"=>"Bloip", "winner"=>true, "entry"=>{"_id"=>"4f38152c504b9345dc000005", "name"=>"Bloip", "winner"=>true, "action"=>"create", "controller"=>"entries"}}
MONGODB w_market_development['system.namespaces'].find({})
MONGODB w_market_development['entries'].insert([{"_id"=>BSON::ObjectId('4f38152c504b9345dc000005'), "name"=>"Bloip", "winner"=>true}])
Completed 201 Created in 12ms (Views: 2.7ms)
Headers Log
Request URL:http://0.0.0.0:3000/api/entries
Request Method:POST
Status Code:201 Created
Request Headers (14)
Request Payload
{"_id":"4f38152c504b9345dc000005","name":"Bloip","winner":true}
As you can see when I complete the entry.save() on a current entry, which should be an update, the JSON is showing a POST instead of a PUT, which Mongoid is doing nothing with and the DB shows no changes. After Googling I found the following articles but nothing really helped.
https://github.com/codebrew/backbone-rails/issues/8
http://lostechies.com/derickbailey/2011/06/17/making-mongoid-play-nice-with-backbone-js/
When I was going through the RailsCast as described above. I was using the entries controller that Ryan put together. After much searching, copying, pasting, and retrying I found that I need a completely new Controller set up. Below is what I originally had.
class EntriesController < ApplicationController
respond_to :json
def index
respond_with Entry.all
end
def show
respond_with Entry.find(params[:id])
end
def create
respond_with Entry.create(params[:entry])
end
def update
respond_with Entry.update(params[:id], params[:entry])
end
def destroy
respond_with Entry.destroy(params[:id])
end
end
This is the Controller code the fixed the issue for me.
class EntriesController < ApplicationController
def index
render :json => Entry.all
end
def show
render :json => Entry.find(params[:id])
end
def create
entry = Entry.create! params
render :json => entry
end
def update
entry = Entry.find(params[:id])
entry.update_attributes!(params[:entry])
render :json => entry
end
def destroy
render :json => Entry.destroy(params[:id])
end
end
Thanks All!
Travis

Resources