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
Related
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.
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.
I am creating search form for model with some tabs, radiobuttons, dropbox, etc. I use index action for searching and sorting. Parameters for searching is persisted in params. It works while I stay in the same index view. But if I had to visit other page and then come back to search page again, params doesn't work and state is not persisted.
I know I can do it with session, but suppose I would have more search forms for another models and so all that params goes into session. Doesn't it make session messy?
So question is:
Is there other reasonable way to persist page state (but I don't want to put into database)
You might consider a class in your app to help manage data input/output, somewhat similar to ActiveRecord's database interface. You can store this data either in a session maybe memcache or redis server would be better. Here's an example.
class SearchParam
def initialize(id, model, query_string = nil)
#id = id
#model = model
#query_string = query_string
end
def save
# write #query_string to redis using `key`
end
def self.find(id, model)
instance = new(id, model)
instance.query_string = Redis.get(instance.key)
return instance
end
def query_string
#query_string
end
def query_string=(qstring)
#query_string=qstring
end
def key
"#{#id}-#{model}"
end
end
I forget the exact redis commands and syntax, but you'll get the idea if you've worked with it before - basic writing and reading. Memcache or session would also work. Then you can easily store the hash representation of the query string for a user/model combo and set your search params for use in the search form.
Easy to lookup based on a session id in the controller when they first get to the index page (if there are no params submitted)
#search_params = SearchParam.find(session.id, model).query_string
Or to save the new query_string when you return results based on the form submit in the index action
SearchParam.new(session.id, model, params).save
There is also localStorage (https://developer.mozilla.org/en/docs/Web/API/Window/localStorage) available in browser, but you have to use JS to access it.
Otherwise, I think it is okay to store it in session, unless you have like tens of different search forms.
The scenario: I need to give models access to API tokens stored in the session.
Background: I have an API-driven rails 3 application utilizing DataMapper(DM) and a DM adapter to interface with the API. Each DM model has a corresponding REST-ish API endpoint much like you get with rails scaffolding. The API requires various headers for requests, including API tokens, keys, ids etc. The headers have nothing to do with the requested data, they exist for authorization and tracking purposes only. A number of these tokens are stored in the session. I want a clean way to make these API headers available to any model during a request.
Possible solutions:
1. Passing session variables from the controller to the models
The obvious answer is passing the tokens in a hash or other object from the controller to the models. A controller action might have the following: #user = User.find(params[:id], api_headers).
The problem is needing to override any model method to accept the additional api_headers object. Not counting methods defined by Rails and DataMapper, there are hundreds of methods already defined in the application models that would need to be rewritten. So I'm ruling out a rewrite, and this also doesn't seem like a good solution since it would require overriding a ridiculous number of DM methods like the User#find example above.
2. Some metaprogramming hack
I could catch any ArgumentError's on DM's base class and check if the last argument is the api_headers object, then set the values as instance variables and invoke the requested method. This thought exercise already has me cringing at dealing with optional arguments etc. If given long enough I could probably create a functional Frankenstein that should get me fired but probably wouldn't.
3. Use a singleton (current preferred solution)
In the application controller set a before_filter to dump the session-stored API headers into a singleton ApiHeaders object. Then any model making an API request can get that singleton with the required API headers.
An additional after_filter* on the application controller would set all attributes to nil on the ApiHeaders singleton at the end of the request to prevent leaking headers between requests.
This is currently my preferred solution but I don't like that the API header values could potentially carry over into other requests if the after_filter doesn't get invoked. I don't know in which scenarios this might happen (in an application error perhaps?) which raises concerns. All I know is the values don't necessarily die with the request.
4. Custom code
Drop support of DataMapper and the custom API adapter and manually make all API calls, passing through all required API headers. Besides the fact I don't have time for this level of rewrite, why use a framework at all if you have to throw a huge chunk out to support a custom auth scheme?
Summary
What's the cleanest way to get these pesky API tokens from the session into the bowels of the application where they can be sent with each API request? I'm hoping for a better solution than those listed above.
* An alias for after_action
I set the current user and the request information on my User model using the request_store gem which is just a tiny shim over thread local storage with a bit of clean-up.
This makes the information available from any of my models via the User class. I have User.current, User.request and User.location available wherever I need it.
Your controller just has to set User.current and User.request once it has authenticated the user.
Example User model:
# models/user.rb
require 'request_store'
class User
def self.current
RequestStore.store[:current_user]
end
def self.current=(user)
RequestStore.store[:current_user] = user
end
def self.request
RequestStore.store[:current_request]
end
def self.request=(request)
# stash the request so things like IP address and GEO-IP based location is available to other models
RequestStore.store[:current_request] = request
end
def self.location
# resolve the location just once per request
RequestStore.store[:current_location] ||= self.request.try(:location)
end
end
Use Thread.current, which is passed in from request to model (note, this breaks if, inside your request, you use sub-threads). You can store the attribute you want to share in a cattr_accessor or in rails cache:
in a cattr_accessor
class YourClass
cattr_accessor :my_var_hash
...
# and in your controller
# set the var
YourClass.my_var_hash = {} if YourClass.my_var_hash.nil?
YourClass.my_var_hash[Thread.current.object_id] = {}
YourClass.my_var_hash[Thread.current.object_id][your_var] = 100
... and in your model
lvalue = YourClass.my_var_hash[Thread.current.object_id][your_var]
Note, if you use this method, you will also want to make one of the hash values a timestamp, and do some housekeeping on getting, by deleting old keys, b/c you'll eventually use up all your system memory if you don't do the housekeeping
with cache:
# in your controller
#var = Rails.cache.fetch("#{Thread.current.object_id}_var_name") do
return 100 # do your work here to create the var value and return it
end
# in your model
lvalue = Rails.cache.fetch(("#{Thread.current.object_id}_var_name")
You can then set the cache expiration to 5 minutes, or you can wildcard clear your cache at the end of your request.
I need to pass a parameter from one method in a controller to another.
From my understanding I have to pass the parameters as a GET exposing it in the url string.
What is the best way to encrypt the data so no one can see what is actually getting passed in the string? Also, is there a way to pass it via POST or is my original understanding correct?
I haven't used RoR, but in the web world, this problem is solved with sessions. Using sessions you can store the parameters on the server and avoid sending sensitive data with GET or POST (both are insecure).
The Ruby on Rails Security Guide looks like a great read related to this.
I suggest you abstract your code into lib/ so that you don't have to call additional methods. Instead of making a new HTTP request, just put the code in a central place and call it from there.
class MyController < ApplicationController
def index
MyLibrary::Thing.do_stuff
end
def show
MyLibrary::Thing.do_stuff
end
end
# lib/my_library/thing.rb
module MyLibrary
module Thing
def self.do_stuff
# do stuff!
end
end
end
That way you can access the same code in multiple actions, without doing extra HTTP requests.