API Controller Specifically for shared methods - ruby-on-rails

Would it be best practice to create some sort of 'general api controller' for shared methods among different models in my RoR application? For example I'm including email validation for Customers - once the user finishes typing in the email address (before submit), an $.ajax POST request is sent to the customers_controller where an external API is being called. After parsing the response I return a response back depending on certain conditions. The validation is handled in real time on the front end before submitting so that the user can know if an email address is valid or not before submitting. The controller method is below.
def validate_email_address
response = ValidateEmailAddressJob.perform_now(params[:email], "Agentapp")
data = JSON.parse response.body
if data['result']['verdict'] == "Valid" || data['result']['verdict'] == "Risky"
html = "<div class='text-success'><i class='fa fa-check'></i> This email appears to be valid</div>"
render json: {html: html, result: data['result']}, status: 200
elsif data['result']['verdict'] == "Invalid"
html = "<div class='text-warning'><i class='fa fa-exclamation'></i> This email may be invalid</div>"
render json: {html: html, result: data['result']}, status: 200
else
render json: {}, status: 503
end
end
I'm trying to keep my code as DRY as possible, so if I wanted to share this email_validation controller method with VendorContacts for example, I'm thinking I could just point the other VendorContact API call to /customers/validate_email_address endpoint even if it's not technically a customer that's needing the validation? Or I could just copy and paste the controller method into the vendor_contacts_controller? Or I could create a separate general api controller with an endpoint such as /validation/emailaddress.
Thanks ahead of time for any help, I'm just trying to learn what the appropriate way to handle this situation would be.

I don't know if this would be considered "best practice" but as a long-time Rails dev, my approach would be to create an EmailController controller, with a single action create, and an Email model (not inheriting from ActiveRecord::Base).
class EmailController < ApplicationController
def create
email = Email.new(params[:email])
if email.valid?
render partial: :valid_email_response
else
render partial: :invalid_email_response
end
end
end
So the Email model has an initialize method, and a valid? method, and the html response is contained in separate view files. Of course, nothing is saved to the database here. The EmailController#create action is invoked any time you wish to validate an email address.
My rationale is that controllers should implement CRUD methods, and in this case a model should handle the validation. I believe this is entirely consistent with the "Rails Way".
IMHO Rails controllers can become catch-all classes for all kinds of application logic, and that coercing the logic into MVC format makes the cleanest code.

Related

How to validate my controller action was called from within my Rails app

I have a simple user registration form.
When the user fills out the form and clicks "Submit" I have a JavaScript event that intercepts the submit and uses AJAX to call the validate method below to check the form data before submitting. If the form data is ok it continues with the submit, but if it's not it cancels the Submit and displays a warning on the page. It's a simple way of doing real-time client side validations.
# app/controllers/users_controller.rb
# Validates the form data. Returns an error message response in the format
# { response: "Invalid email format!" }
#
# A blank response means success
#
def validate
if request.xhr? || <request came from another method in this controller>
# Run various validations on input data and determine a response
# response_text = ...
render json: { response: response_text }
else
redirect_to root_path
end
end
def create
if JSON.parse(validate)["response"].blank?
User.create(...)
# Other stuff
end
end
However when the submit does eventually pass and go through, it sends a POST to the create action to create a new User. That method calls validate again (the cost of re-validating is minimal) to ensure that no one bypassed the form and submitted a malicious request directly to the server.
So my validate method has to respond to both (a) AJAX calls and (b) "internal" calls from within the app. All other calls to the validate action should just redirect to the root path.
I can tell whether the call was an a AJAX call pretty simple using request.xhr?.
How do I check whether the action was called internally and not by a user?
Taking a step back, is this a good general approach for validation? Any thoughts to improve on it?
Thanks!
Rails generates an authenticity token whenever a user views a form and stores it as a random string in the HTML. That token is also stored in the session and is therefore invisible to the user. Upon receiving a request, your application will compare the tokens to verify whether the request was generated from your form.
TL;DR: Don't worry, you're already protected.
I don't have a specific answer, but do have some information which could help. I'll gladly delete this if required...
AJAX calls and "internal" calls
You have to understand that XHR requests can only come from your app -- CORS (cross origin resource sharing).
Ajax calls can only come from your own domain, so don't think a malicious hacker could run some scraper or whatever -- you choose which domains are permitted to send XHR requests.
So when you're calling...
if request.xhr?
... as part of a validation, you need to scope when XHR will be used.
On the same note, what validation are you performing?
If you're validating input data, Rails handles this at the model layer. We've done something similar (click login/register at top):
The base structure of Rails is MVC, which means that your controller is only responsible for taking a request & building the appropriate data objects out of it.
Thus, if you're "validating" an input, what is there to validate apart from the data itself (handled by the model)?
As #MarsAtomic specified, too, Rails uses a CSRF token to provide some level of authentication for a form submission.
--
You could easily use the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
respond_to :js, :json, :html
def create
#user = User.new user_params
respond_with #user.save
end
end
If you then sent the request from your front-end as json, you'd get back the created user OR the errors:
#app/views/users/new.html.erb
<%= form_for #user, remote: true, format: :json do |f| %>
<%= f.text_field .... %>
<%= f.submit %>
<% end %>
#app/assets/javascripts/application.js
$(document).on("ajax:error", "#new_user", function(xhr, status, error) {
// do stuff with response
});
In your create method, you want to return a #user if it's not valid (if they skip the ajax validate).
If that's the case, you don't want to do User.create, instead, create an instance and try to save it. If you do all this, you're doing the normal controller action for create and there is no need to call validate internally.
def create
#user = User.new(user_params)
if #user.save
# Other stuff
end
end
And, it's not a good practice to create a json response and parse it to see if an object is valid. Instead, either test with valid? or just try to save and get back a true or false.
In other words - keep your validate method separate from your create method. They'll both rely on valid? eventually.

Render and return from helper function

I have multiple actions in different controllers making 3rd party API calls through Faraday using a helper function defined in application_helper.
Now in case of a successful response, they have different views to render, but need to send json which was recieved if there was any error.
The following code works when used directly in an action,
if (r[:error] && r[:code]==403)
Rails.logger.info "Exiting"
render :json => body and return
end
But if I move it to a helper function and call the helper function from the action, the execution doesn't stop there and continues till the end of the action where it raises DoubleRendererError.
Could anything be done to make the controller stop processing and return itself from the helper (to avoid placing these 4 lines in every action, where I make the 3rd party API call)
def blank_fields?(params)
if params["user"]["email"].blank? || params["user"]["password"].blank? || params["user"]["password_confirmation"].blank?
render json: { status: 422, message: "Your email, password or password confimation is blank. Please check that all fields are filled in and try again!" }, status: 422
return true # pass back to guard clause
end
end
do something like this, and call this method from your controller method, aka:
return if blank_fields?(params)
Rails' helpers are intended for calling from views, and they way you're using render is to be called from a controller. You can call helpers from within a controller but it's generally not necessary. If you want some function to be available to all controllers, you would typically place it in your ApplicationController.
When you call render within a view, it is going to try to render a partial at the point in the template you called it.

How to ask two queries from a single controller

I am trying to do everything on a single page. The page is called users/index.html.erb.
On this page I return a simple data base query in json format.
I would like to also return a seperate query in non json format from the same database.
I'm trying to avoid partials for the moment as they make variable handling a bit more complicated that I want for my beginner level.
This is what I have:
class UsersController < ApplicationController
def index
#users = User.including_relationships
#followas= User.find_by name: params[:name]
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users }
end
end
end
but it doesnt work unsurprisingly. How can I get it to work in one action?
Not sure about your purpose of returning two formats of query from a single controller call. It usually happens when you use Javascript heavily (eg. Angular.js). Controller first returns the HTML page including all the HTML template and javascript, then data in JSON is returned in the second round.
So in your users/index.html.erb, you might have something like:
<p ng-repeat='user in users'>{{user.name}}</p>
<script>
// do a AJAX call to retrieve users in JSON format
</script>
Then you will call users/index.json again to retrieve users information in JSON.
In short, your users/index is called twice, once for html, once for JSON.
It might seem unwise to call the same controller method twice just to render different format. In such case, you might consider to embed the JSON data in HTML. In such way, you will only render users/index.html.erb once and parse the data in JSON through Javascript on the client side.

Respond to remote form_for with html or json?

I've currently got several partials that contain forms for different AR models. I'm using these to build a dashboard-style pages so I can do an ajax post to CRUD controllers (rather than posting to a bunch of different actions within a dashboard controller).
Since I'm using AR to validate the data, I'd like to be able to just render the correct partial with the correct object in the CRUD controller and use that as my response. Something like this:
if #note.save?
flash[:notice] = "Note successfully saved!"
render '_note_form', :layout => false
else
flash[:notice] = "Something went wrong. Note was not saved!"
flash[:error] = #note.errors.full_messages.to_sentence
render '_note_form', :layout => false
end
Then I'm adding a js function to handle that response by replacing the form with the response like so:
$(function() {
$('form#new_note_form').bind('ajax:success', function(evt, data, status, xhr){
$("#new_note_form").replaceWith(xhr.responseText);
});
});
This gives me validation the same type of validation that I would see if I did a standard post, but only refreshes the partial rather than the entire page.
I can't help but to feel like there is a better way to handle this. Should I just be passing the error/success messages via json and handling them appropriately on the front-end? Is it bad practice to just replace the entire form?

Rails, How to render a view/partial in a model

In my model I have:
after_create :push_create
I push_create I need to render a view. I'm trying to do that like so:
def push_event(event_type)
X["XXXXX-#{Rails.env}"].trigger(event_type,
{
:content => render( :partial =>"feeds/feed_item", :locals => { :feed_item => self })
}
)
end
This angers rails as it doesn't like me rendering a view in the model but I need it there.
Error:
NoMethodError (undefined method `render' for #<WallFeed:0x1039be070>):
Suggestions? Should I render it somewhere else somehow? Or how can I render in the model to set content? Thanks
proper solution
Well, "they" are right. You really have to do the rendering in a controller -
but it's fair game to call that controller from a model! Fortunately, AbstractController
in Rails 3 makes it easier than I thought. I wound up making a simple
ActionPusher class, working just like ActionMailer. Perhaps I'll get ambitious and
make this a proper gem someday, but this should serve as a good start for anyone else in my shoes.
I got the most help from this link: http://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/
in lib/action_pusher.rb
class ActionPusher < AbstractController::Base
include AbstractController::Rendering
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::AssetPaths
include Rails.application.routes.url_helpers
helper ApplicationHelper
self.view_paths = "app/views"
class Pushable
def initialize(channel, pushtext)
#channel = channel
#pushtext = pushtext
end
def push
Pusher[#channel].trigger('rjs_push', #pushtext )
end
end
end
in app/pushers/users_pusher.rb. I guess the require could go somewhere more global?
require 'action_pusher'
class UsersPusher < ActionPusher
def initialize(user)
#user = user
end
def channel
#user.pusher_key
end
def add_notice(notice = nil)
#notice = notice
Pushable.new channel, render(template: 'users_pusher/add_notice')
end
end
Now in my model, I can just do this:
after_commit :push_add_notice
private
def push_add_notice
UsersPusher.new(user).add_notice(self).push
end
and then you'll want a partial, e.g. app/views/users_pusher/add_notice.js.haml, which could be as simple as:
alert('#{#notice.body}')
I guess you don't really need to do it with Pushable inner class and the .push
call at the end, but I wanted to make it look like ActiveMailer. I also have a
pusher_key method on my user model, to make a channel for each user - but this
is my first day with anything like Pusher, so I can't say for sure if that's the right
strategy. There's more to be fleshed out, but this is enough for me to get started.
Good luck!
(this was my first draft answer, leaving it in because it might help someone)
I've got the general outline of a solution working. Like this, in your model:
after_create :push_new_message
private
def render_anywhere(partial, assigns = {})
view = ActionView::Base.new(ActionController::Base.view_paths, assigns)
view.extend ApplicationHelper
view.render(:partial => partial)
end
def push_new_message
pushstring = render_anywhere('notices/push_new_message', :message_text => self.body)
Pusher[user.pusher_key].trigger!('new_message', pushstring)
end
that is definitely working - the template is rendering, and gets eval()'ed on the client side successfully. I'm planning to clean it up, almost certainly move render_anywhere somewhere more general, and probably try something like this
I can see that pushes will need their own templates, calling the generally available ones, and I may try to collect them all in one place. One nice little problem is that I sometimes use controller_name in my partials, like to light up a menu item, but I'll obviously have to take a different tactic there. I'm guessing I might have to do something to get more helpers available, but I haven't gotten there yet.
Success! Hooray! This should answer your question, and mine - I'll add more detail if it seems appropriate later. Good luck!!!!
original non-answer from an hour ago left for clarity
I don't have an answer, but this timely question deserves more clarification, and I'm hoping to get closer to my answer by helping ask :)
I'm facing the same problem. To explain a little more clearly, Pusher asynchronously sends content to a connected user browser. A typical use case would be a showing the user they have a new message from another user. With Pusher, you can push a message to the receiver's browser, so they get an immediate notification if they are logged in. For a really great demo of what Pusher can do, check out http://wordsquared.com/
You can send any data you like, such as a JSON hash to interpret how you like it, but it would be very convenient to send RJS, just like with any other ajax call and eval() it on the client side. That way, you could (for example) render the template for your menu bar, updating it in its entirety, or just the new message count displayed to the user, using all the same partials to keep it bone-DRY. In principle, you could render the partial from the sender's controller, but that doesn't make much sense either, and there might not even be a request, it could be triggered by a cron job, for example, or some other event, like a stock price change. The sender controller just should not have to know about it - I like to keep my controllers on a starvation diet ;)
It might sound like a violation of MVC, but it's really not - and it really should be solved with something like ActionMailer, but sharing helpers and partials with the rest of the app. I know in my app, I'd like to send a Pusher event at the same time as (or instead of) an ActionMailer call. I want to render an arbitrary partial for user B based on an event from user A.
These links may point the way towards a solution:
http://blog.choonkeat.com/weblog/2006/08/rails-calling-r.html
How to render a Partial from a Model in Rails 2.3.5
http://mattwindsurfs.wordpress.com/2008/06/19/rails-render-in-a-model/
http://davetroy.blogspot.com/2008/02/actsasrenderer-brings-output-to-models.html
https://github.com/asapnet/acts_as_renderer
http://ethilien.net/archives/render-rails-templates-anywhere-even-in-a-model/
The last one looks the most promising, offering up this tantalizing snippet:
def render_anywhere(partial, assigns)
view = ActionView::Base.new(Rails::Configuration.new.view_path, assigns)
ActionView::Base.helper_modules.each { |helper| view.extend helper }
view.extend ApplicationHelper
view.render(:partial => partial)
end
As does this link provided by another poster above.
I'll report back if I get something working
tl;dr: me too!
I just do this:
ApplicationController.new.render_to_string(partial: 'messages/any', locals: { variable: 'value' })
Rails 5 way
In Rails 5 rendering outside a controller became pretty straightforward due to implemented render controller class method:
# render template
ApplicationController.render 'templates/name'
# render action
FooController.render :index
# render file
ApplicationController.render file: 'path'
# render inline
ApplicationController.render inline: 'erb content'
When calling render outside of a controller, one can assign instance variables via assigns option and use any other options available from within a controller:
ApplicationController.render(
assigns: { article: Article.take },
template: 'articles/show',
layout: false
)
Request environment can be tailored either through default options
ApplicationController.render inline: '<%= users_url %>'
# => 'http://default_host.com/users'
ApplicationController.renderer.defaults[:http_host] = 'custom_host.org'
# => "custom_host.org"
ApplicationController.render inline: '<%= users_url %>'
# => 'http://custom_host.org/users'
or explicitly by initializing a new renderer
renderer = ApplicationController.renderer.new(
http_host: 'custom_host.org',
https: true
)
renderer.render inline: '<%= users_url %>'
# => 'https://custom_host.org/users'
Hope that helps.
You can use ActionView directly and render partials to string without having a controller. I find that pattern useful to create models that encapsulate some javascript generation, for instance.
html = ActionView::Base.new(Rails.configuration.paths['app/views']).render(
partial: 'test',
formats: [:html],
handlers: [:erb],
locals: { variable: 'value' }
)
Then, just put your _test.html.erb in you view folder and try it out!
Rails 6.0.0 compatible answer, since I ended up on this page while searching for a solution:
lookup_context = ActionView::LookupContext.new(Rails.configuration.paths["app/views"])
renderer = ActionView::Base.new(lookup_context)
renderer.extend(Rails.application.helpers)
renderer.render \
template: "foo/bar",
formats: [:html],
handlers: [:erb],
locals: { user: User.new }
I'm fairly sure the answers you seek lie within Crafting Rails Applications where Jose Valim goes into great detail about how and why you would want to render views straight from your db
Sorry I can't be of more help yet because I've just started reading it myself tonight.
You might find some help here - it's a blog post about doing this sort of thing, albeit using different methods than yours
the "proper" way to do this is to push an object in serialized form(json), and then have the view deal with it once the event is received. Perhaps you want to use Handlebars to render the object.
Edit: I originally wrote about how, despite my answer, I was going to follow your example. But I just realized there is a HUGE gotcha with your approach when it comes to push notifications.
In your problem, you are doing push notifications to one user. For me, I was broadcasting out to a set of users. So I was going to render html with a presumption of a "current_user" and all that comes with it(eg logic, permissions, etc). This is NO BUENO as each push notification will be received by a different "current user".
Therefore, really, you need to just send back the data, and let each individual view handle it.
You should call all render methods from a controller. So, in this case, you can notify the controller that the object has been created and the controller can then render the view. Also, since you can render only once, I think you can wait for all your server side operations to complete before invoking the render.
The render methods are defined on the ActiveController class and its progeny. Inherently you do not have access to it on the model, nor is it a class method so you can't use it without an instance of the controller.
I've never tried to instantiate a controller for the express purpose of simply stringifying a partial, but if you can get your hands on a controller, render_to_string seems to be the way to go.
I will chime in by saying that if you're going down this path you're taking RoR "off the Rails". This is a violation of MVC and fundamentally poor program design.This doesn't mean I think you're a bad person :P Sometimes life drives us off the rails, so to speak.
I can't speak to the details that have driven you to do this, but I'd strongly suggest you rethink your approach.
I have created a gist for this.
I needed something similar, where the models don't necessarily (or in my case, ever) get updated via a controller, so the logic can't sit there.
Created a server-push based controller:
https://gist.github.com/4707055

Resources