Rails 5.2 Service objects with non current user - ruby-on-rails

I am trying to forward an action to another controller through the service object methodology in rails 5.2.
The create action should pass the user id for the create action but I am failing at passing that param appropriately.
Business logic is the following: a user rents out an asset, the potential renter makes a request, when the asset owner agrees to a visit, the rent user is created as a client in another controller to organise a visit.
I am trying to address the create action in the client controller as follows:
In the rent controller :
private
def visit(room, rent)
#newclient = NewclientService.create(params)
if #newclient.save
rent.Approved!
...
else
rent.Declined!
...
end
and then in the app/service/newclient_service.rb
module NewclientService
class << self
def create(params)
#rent = Rent.find_by(id: params[:id])
user = #rent.user_id
name = #rent.user.fullname
email = #rent.user.email
Client.create(user_id: user, name: name, email: email)
end
end
end
This code does the job. The db is filled up, validations and strong params seem to work and it seems to me robust/secure enough.
Question: is the service object (my way ?) route the most preferred way for forwarding that action ?
Thanks for your help,

I like the pattern in principle and it has really cleaned up the apps which I produce. There are a couple of nice gems that I typically use to get the job done and keep the controllers clean.
I use the mutations gem and simple_command. These two together give you a nice (almost completely) consistent API. The mutations gem in particular is what I use for digesting and resolving JSON input data from params which can then handle processes for me.
This is a good pattern in the sense that it encapsulates the logic of discrete functionality very well. For example, if you have a RegisterUser mutation, you can use that in a controller or you can use it to digest a whole list of objects etc. You can even use the builder option for attributes to process deeply nested json.
I would recommend checking it out.
https://github.com/cypriss/mutations
For those times where I am not processing JSON from an API and want to create discrete encapsulated functionality I generally use simple_command https://github.com/nebulab/simple_command. This approach is also great because it allows you to use the same components from any context. For example, a command called GetLatestTweets.call() could be used equally well from a controller as it could from the REPL.
Both of these libraries provide you with a result object which you can then process as appropriate
def create
outcome = NewClientMutation.run(params.require(:resource).permit!)
if outcome.success?
render json: outcome.result, status: :created
else
render json: {resource: outcome.result, errors: outcome.errors}, status: :unprocessable_entity
end
end
In my particular case I use 'permit!' since the mutations library ignores any parameters that aren't explicitly specified which means that strong parameters aren't necessary if you use this library as it filters parameters as well.

Related

API docs for a generic controller that handles all requests

I need to generate api endpoints for certain database views we have in our postgres database dynamically, as we may add / delete views we don't want to update the code every time we do this.
for this i have a generic controller which handles all requests and I create model class on the fly based on which view needs to be accessed.
when a request is sent to the generic_api#index based on the end_point passed i create certain model classes on the fly and query them.
routes.rb
namespace 'api' do
namespace 'v2' do
get '*end_point', to: 'generic_api#index'
end
end
const = ClassFactory.class_object(end_point.classify, result)
Octopus.using(result['shrad'].to_sym) do
result = const.ransack(params[:q]).result.page(params[:page]).per(10000)
render json: result.to_json
end
The api itself is working as expected, however i am not sure what's a good way to generate documentation for the API since i only have one controller. we were earlier using the apipie gem but the documentation seems to be tightly coupled with controllers for each end point.
Any help on how to generate api documentation when I have a single controller handling multiple end point requests would be great, Thanks.

Should this be a lone Model, a Model and Controller, or some other Ruby class entirely?

Currently in my app a QuoteRequest has_one Quote which belongs_to a QuoteRequest. QuoteRequest has full MVC, i.e. QuoteRequest#new view enables user to create and persist the object and its data to a QuoteRequest table in the db.
I'm now working out how to design the next stage, that should be;
when a QuoteRequest is created it should kick off the application crawling to an external site
crawling and scraping to get the quote results using the QuoteRequest data, persisting that result to the app’s db.
then rendering a view of that persisted quote data.
As a newbie I’m struggling to decide how best to design and write this. I believe I understand the general MVC standard, of a C serving the user a V, that V capturing something from the user and the C then taking that something and sending it to the M to persist it into the db. But I'm not unsure how anything other than something with this defined MVC relationship would fit into the Rails environment.
In my next stage after QuoteRequest is created there’s a part of the process that doesn’t need a view, ie once the QuoteRequest data is persisted to the db it needs to kick off something in the app, currently Quote though no functionality written in Quote yet, that goes out
crawls, scrapes and saves, then we’re back to needing a view for that saved scrape to be rendered.
So I’m wondering whether this ‘non view requiring’ stage of the process ought to be neither a model or a controller, rather some type of standard Ruby class. E.g. flow as below(pls excuse strange 'pseudo code'!):
On successful QuoteRequest.save >> OtherClass.start_crawl >> on OtherClass.crawl_success >> save as a Quote.create (no view) >> Quote.show(OtherClass.crawl_sucess_result) view >> Done.
Can anyone help me out with making decisions about how to design this, I guess pretty simple, pattern. Thanks
I'm trying to use your "pseudo code" and combine controller and PORO (plain Old Ruby Object), below some tips for your case
class QuoteRequestsController < ApplicationController
def new
#quote_request = QuoteRequest.new
end
def create
#quote_request = QuoteRequest.new(quote_request_params)
if #quote_request.save
flash[:success] = "QuoteRequest successful save"
# contact other class
#crawling_service = MyModuleService::Other.new(#quote_request)
#crawling_service.start_crawl
if #crawling_service.crawl_result # if success
# create your quote
#quote = #quote_request.create_quote(arg1, arg2, etc)
#quote.save
# after save redirect_to quote show
redirect_to quote_path(#quote)
end
else
flash[:error] = #barang.errors.full_messages[0]
end
end
end
create PORO as services in rails
create app/services folder, then you can create one file
in this folder with name for example my_module_service.rb
module MyModuleService
attr_reader :crawl_result
class Other
def initialize(quote_request)
#quote_request = quote_request
end
def start_crawl
# your start crawl process
if success
crawl_result = true
# this for send message back whether success or not the crawling
else
crawl_result = false
end
end
end
end
As I look at it, I feel like the crawling should be a background task so it doesn't hog up processes.
Add after_save in the QuoteRequest model (or you could also just invoke it in your controller)
Create an empty Quote object perhaps
Use a background-task library like resque or sidekiq.
Write a worker (activejob) that does the crawl and updates the Quote object above with the results.)
The view of the quote should show that it's processing at the time when it's not yet crawled.

Rails: Passing API JSON Response to View, without Model

Disclaimer: I'm doing something which may qualify for Code Smell of 2015 Award. Using rails 4.2, no javascript or anything like that.
I have a form into which users input their data. With this data I call a third-party API which will remain nameless. :)
I have no model, I'm not persisting anything. (Part of a larger app, not a one-pager.) Thus when faced with presenting the user with the response, I find myself stuck on how to render the data properly into a view. The response contains an array of hashes which I obviously intend to present the user.
I render the form into widgets/new, etc, create and process the request, etc, but then what?
I thought maybe I could make use of decorators to do my dirty work but not sure how to actually get the user off to the respective view. I don't care which view. Call it a widget_path.
WidgetsController < ApplicationController
def new
render :new
end
def create
# preparing request
...
# data = response, each_serializer, WidgetSerializer, root: false
# data = WidgetDecorator.new(render_serialized(response, WidgetSerializer))
# #data = WidgetDecorator.new(JSON.parse(response))
# redirect_to ??_path ... and take your #data with you
end
end
What do I do?
Your idea of Model is unfortunately corrupted by Rails itself (sorry).
A model is business logic not an ActiveRecord::Base (not necessarily). Controller methods shouldn't be big, ~5 lines long is probably the maximum with a ~100 lines max per controller file. Try to stick with this and it will automatically correct good chunck of code smells.
Anyway, you may handle this with a Model, as a PORO (plain old ruby object).
class MyApiResponse
attr_reader :myapikey
attr_reader :whatever
def initialize(myapikey, whatever)
#myapikey = myapikey
#whatever = whatever
end
def get
#_response ||= JSON.parse(run_api_stuff(myapikey))
end
end
So in controller you would do something like
def create
myapiresponse = MyApiResonse.new(myapikey, whatever)
#response = myapiresponse.get
end
Last but not least, you can't pass what you obtained through the API in the redirect. You are subject to HTTP limits so, you have a limit on GET params size, a limit on session and you can't redirect to a POST. You have 3 options
Best is store last api request for given user in the database and fetch it back through an ID (which will travel through the redirect)
Store it in session if request is really small (and you must ensure it is small!)
Perform the API request again after the redirect, horrible. Otherwise perform the API request only after redirect, not sure if this is an option though

Abstracting Database and Proper Controller Implementation

I'm using MongoDB as a backend for my project, but I don't specifically want a dependency on Mongo for the life of the project in case I later decide to change it or in case unit testing is easier without the database. As written, however, my controller is strictly dependent on MongoDB.
Mostly taken from the MongoDB tutorial, I have the following in a config/initializers/mongo.rb file.
MongoMapper.connection = Mongo::Connection.new('localhost')
MongoMapper.database = 'database'
if defined?(PhusionPassenger)
PhusionPassenger.on_event(:starting_worker_process) do |forked|
MongoMapper.connection.connect if forked
end
end
In a controller for querying the US states, I have the following code:
class StateController < ApplicationController
def index
states = MongoMapper.connection.db('database').collection('state').find()
render :json => states
end
end
There are a few issues I see right off the bat (there are probably more):
As previously mentioned, the controller has a hard dependency on MongoDB.
I'm not using the database property of the MongoMapper class (it's hardcoded in the controller).
I don't necessarily want to go through an HTTP endpoint every single time I want a reference to a collection of states --though I'd like to keep this option available. For example, if a signup page has a drop down list for the user to select their home state, it seems foolish to require a client-side jQuery.get() to populate the list of states. It seems to me it would make more sense to have the states pre-fetched server-side and build the drop down list on the server.
For #3, I could certainly query the states in any action method that renders a view that requires the list of states and save them in #states (or something similar), but this would be a lot of duplicate code.
What would be the best way to architect this to allow for less coupling and more code reuse?
First, you should have a Model for states:
class State
include MongoMapper::Document
end
Then, in your controller, you should access via that:
class StatesController < ApplicationController
def index
render :json => State.all
end
end
This way, your controller has no idea what underlying datastore it's using.
Finally, to reduce the need to make a HTTP call, but assuming you're building this in javascript, you code:
<div id="#states" data-states="<%= #states.to_json %>"></div>
Then load that from $("#states").data("states")

Excluding private data in RESTful response

What is the best practice for excluding certain fields/data in a RESTful response if the user requesting it shouldn't be able to see all of the data?
Example:
Person has a First Name, Last Name, and Date of Birth.
Both authenticated and non-authenticated users can make RESTful requests to /people.xml to get a full list of people. However, only authenticated users should be able to view all of the information. Non-authenticated users should only have the First and Last Name fields returned (excluding the Date Of Birth data).
Should the Person controller check for authentication before building the response? If user is authenticated they get everything, else they only get a subset? Does that break any rules of REST where /people.xml can send two separate results?
No, that's fine. It's the same resource, but with a different representations based on the authentication information. You could also serve different versions depending on what the Accept header contained (you should use that one instead of file extensions like .xml, by the way), or you could serve different language versions, or you could render the page different if the logged in user has specific personalization options defined. It's all legal. Consider a website that has a login box. If you're logged in, the page will be different. That's the same thing, except it doesn't specifically affect the enclosed information per se. Controlling caching and so forth in these cases is exactly what Cache-Control, Vary and friends are for. Also see http://www.subbu.org/blog/2007/12/vary-header-for-restful-applications
The same URL can yield different representations, depending on the request-headers. For example, Accept is commonly used to control the format of the response (f.ex. XML or JSON). Likewise, authentication-headers could be used to control how much is returned for an entity.
You can use the :only option of the to_xml method to restrict the fields returned by the controller, i.e.:
def show
#person = Person.find(params[:id])
payload = current_user.nil? ? #person.to_xml(:only =>
[:first_name, :last_name, :dob]) : #person
respond_to do |format|
format.xml { render :xml => payload }
end
end
I use serialize_with_options plugin, as most of the view data access configuration can be done at the model level.
class Person
serialize_with_options(:anonymous) do
only :first_name, :last_name, :dob
end
end
class PersonController
def show
#person = Person.find(:params[:id])
respond_to do |format|
format.xml { render :xml => current_user.nil? ? #person.to_xml(:anonymous):
#person }
end
end
end
Reference
1 serialize_with_options
Consider that different representations of the same data (depending on the user) could have surprising consequences if you have a cache somewhere in the middle. If the fully authenticated user makes the request first, then followed by the 'lesser' user, they might see surprising result: The same view as the fully authenticated user before. That's because there is no difference in the two requests as far as the cache is concerned (unless you make it aware of these issues). So, tread carefully.
I would also suggest separate namespaces, as proposed by Robert.
By the book, REST says that one resource should return the same result every request.
I would create a namespace with "unauthenticated" or something and make
/people.xml for authenticated
and
/unauthenticated/people.xml
They could be the same controller building different xml results for each request.

Resources