First argument in form cannot contain nil or be empty in rails 4.0 - forms

There are tons of these, especially for Rails 4.0 and Rails 3.x. I am new to nested routing and I find it very difficult, especially with forms.
So I have the following:
Routes
resources :users do
resources :api_keys, path: '/developmentcenter'
end
The relationship here is: user has many api keys where api key belongs to user.
Controller
class ApiKeysController < ApplicationController
before_action :authenticate_user!
def new
#user = User.find(params[:user_id])
#api_key = ApiKey.new(:user => #user)
end
def index
#user = User.find(params[:user_id])
#api_key = #user.api_keys
end
def create
#user = User.find(params[:user_id])
#api_key = ApiKey.new(create_new_api_key)
create_api_key(#api_key, #user)
end
def destroy
#user = User.find(params[:user_id])
destroy_api_key(#user)
end
private
def create_new_api_key
params.permit(:api_key, user_attributes: [:id])
end
end
The above is pretty basic. create_api_key is a method that does something on save, or does something else on failure to save. While destroy_api_key Just find the api key based on the user id and deletes it, does something on success and something on failure.
So now lets create a form - which has a single button for creating the API key.
<h2>Create a new Key</h2>
<%= form_for ([#user, #api_keys]) do |f| %>
<p class="button"><%= f.submit "Generate API Key" %></p>
<% end %>
All we need is a single submit button that upon click, creates a new api key for the user whom is logged in.
But wait we have a an error:
First argument in form cannot contain nil or be empty
This error takes place at:
<%= form_for ([#user, #api_keys]) do |f| %>
So - I have looked at every single one of the stack questions (well most) that deal with this error, I have changed my controller based on a few, to what you see above. I have even look at the form helpers docs in the rails manual.
I cannot figure this out.

It is telling you that #user is empty or nil in the context it is using. Either this is a user who has not been created in the DB yet, or your User.find call is not working to return an actual user. The form needs to know who #user is before it can create a nested resource (#api_key) for it. Your `create_api_key' is completely wrong. You need to whitelist your params first, then find the user in the DB (or create them), then you can use the #user instance variable to create a form for that user to create a key. I think if you do it right, you shouldn't need to call #api_keys in the beginning of the form if you defined the relationships in your models (has_many or has_one, belongs_to etc.). Can you post the web server console output when you visit that page? First off you are calling
#user = User.find(params[:user_id])
every time in your controller. You should DRY that up with a before_action.
private
def set_user
#user = User.find(api_key_params[:user_id])
end
Then at the top of the controller you would have:
class ApiKeysController < ApplicationController
before_action :authenticate_user!
before_action: :set_user
Also you should make your .permit a method that returns a variable called api_key_params:
def api_key_params
params.require(:user).permit(:api_key)
end
That way you have the things you want returned in a whitelist. To access these params you just call the method. You can specify a param you want returned from the method like my set_user example. But it also gives you the ability to do things like:
def create
#api_key = Api_key.new(api_key_params)
end
That way the Api_key.new gets the user and creates the key. You don't show how or where you generate the key itself. If it gets created by some method, you should put that in the model for Api_key. There are some other things about your code that are confusing without seeing the rest of your files.

Related

Searchkick: how does search_index.refresh work with async callbacks?

In a typical Rails controller, we create a Post model and then redirect to posts_path. The index action uses Searchkick to list Posts.
# In the model
class Post < ApplicationRecord
searchkick callbacks: :async
end
# In the controller
def index
#posts = Post.search("*")
end
def create
Post.create!(post_params)
redirect_to posts_path
end
However, since Elasticsearch is eventually consistent, sometimes the redirect occur before Searchkick/Elasticsearch indexed that record.
Searchkick's docs state that one could use Post.search_index.refresh to wait for the index to catch up with the new record. So one could write:
def create
Post.create!(post_params)
Post.search_index.refresh
redirect_to posts_path
end
However, sometimes we still redirect to /posts without seeing the newly created record; I think that it's because we're using callbacks: :async, so Searchkick (and Elasticsearch::API::Indices::IndicesClient which is used behind the scenes) has no way to know about the queued Searchkick::ReindexV2Job.
If that's the case, how does one solve this use-case, which is: after the #create or #destroy action have created/destroyed a Searchkick-indexed record, redirect to the #index action making sure that the record is already on the index, when using async callbacks?

2 separate controllers for the same end point in html and json or a single one?

I have the end points "/customers" and "/api/v1/customers", in html and json respectively for a list of customers. Do I have to create 2 different controllers and thus actions for them? Or can I return html or json from a single controller and action depending a requested format: html or json? Note that for "/api/v1/customers" I need authentication via an Api Key.
You can have one controller and action for both endpoints, but I would advise against it.
You mentioned that those controllers need to do different stuff, so instead of adding stuff like "if json then check api key" make two separate controllers and extract common code of getting all the customers.
There is a great talk about untangling business logic from http interface: http://www.elixirconf.eu/elixirconf2016/lance-halvorsen Getting a list of customers might be out of your controllers, so at the end you will have two controllers like this:
defmodule MyApp.Api.CustomersController do
plug MaApp.ApiAuth #plug for checking api key
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
def MyApp.CustomersController do
plug MyApp.UserAuth #for example checks if user is logged in
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
At the end your controller does not perform any logic, it calls something else to do the job and action is responsible only for web stuff like parsing params, authentication via api key, session cookies and translating end result to json/html.

Multiple instances of Sinatra::Base applications with different configurations

I developed a Rack application based on Sinatra::Base. Now I would like to use many instances of it, each with a slightly different configuration, in a single Rack application.
My rackup should look like
use Rack::Lint
map '/mel' do
run Site.new('/home/mel/site').app
end
map '/pub' do
run Site.new('/pub').app
end
The Site class collects various parameters (in this example only the root dir) and does some preparatory work. The #app method should return a Server object that holds a reference to the served Site instance.
This is an example of the Site and Server code:
class Site
def initialize(root_dir)
#root_dir = root_dir
# ... set up things ...
end
def app
# This is where a new Server Rack application should be created
return Server.new { |server| server.set :site, self }
end
end
class Server < Sinatra::Base
before do
#content = settings.site.all_files
end
get /(.*)/ do |url_path|
# do things...
end
end
The problem with this code is that the #app method does not return a valid Rack application.
What should I do in #app to return a new, configured Server Rack application?
This is a way to make it work suggested by "carloslopes" on #sinatra.
The Site#app method becomes
class Site
def app
# This is where a new Server object should be created
return Server.new(self)
end
end
and the Server objects get their parameters via instance variables:
class Server < Sinatra::Base
def initialize(site)
super()
#site = site
end
before do
#content = #site.all_files
end
get /(.*)/ do |url_path|
# do things...
end
end
Edit: made community wiki so that other can make the solution even better and share the credit.

How can I redirect to login_path if email uniqueness validation fails?

I am creating my very first Rails and MVC app. It is a website for my wedding guests to create their RSVPs.
I have a single form that is deeply nested. An RSVP has_one User and has_many Guests.
This form creates a User, RSVP, and Guests all in one go. There is also a link to edit an existing RSVP through a login_path.
I have validates_uniqueness_of :email on the User model. I would like to redirect to the login_path if a user attempts to create a new RSVP when they've already created one, i.e. the :email :uniqueness validation fails.
How can I redirect to the login_path if the :email :uniqueness validation fails?
You would need to test in your controller action if the unique validation failed for the that email and redirect if that's the case. That said, I'm not sure if there is better way to know if a certain validation failed for an specific field than just comparing the error message, as follows:
if #user.save
# do success actions
else
if #user.errors[:email] == 'has already been taken' # ugh
respond_to |format|
format.html { redirect_to login_path(email: #user.email) }
end
else
# handle other errors
end
end

TempData["message"] isn't reliable-- what am I doing wrong?

I'm using TempDate["Message"] to show little update banners as the user does things on my site like this:
[AcceptVerbs(HttpVerbs.Post), Authorize(Roles = "Admins")]
public ActionResult Delete(int id)
{
_Repo.DeletePage(id); // soft-delete
TempData["Message"] = "Page deleted!";
return RedirectToAction("Revisions", "Page", new { id = id });
}
Then in my master page I have this:
<%-- message box (show it only if it contains a message) --%>
<% string Message = (TempData["Message"] ?? ViewData["Message"]) as string;
if(!string.IsNullOrEmpty(Message)){
%>
<div id="message"><%:Message %></div>
<% }
TempData["Message"] = null; ViewData["Message"] = null; %>
I hit both TempData and ViewData because I read somewhere that TempData should be used for redirects and ViewData should be used otherwise.
The issue is: often the message won't show up right away. Sometimes it takes a click or two to different parts of the site for the message to show up. It's very strange.
Any ideas?
You should verify all places where you use TempData["Message"] in your code. Corresponds to ASP.NET MVC does browser refresh make TempData useless? you can read TempData["Message"] only once (see also http://forums.asp.net/p/1528070/3694325.aspx). During the first uage of TempData["Message"], the TempData["Message"] will be deleted from the internal TempDataDictionary.
Probably it would be better to use TempData["Message"] only inside of Revisions action of the Page controller and not inside of master page or inside a View.
TempData is not intended to pass data to views, hence the name ViewData for that purpose. In fact, I can't think of a reason to use TempData from within a view definition at all...
One very common usage of TempData is the passing of information between controller actions when you do a redirect (the Revisions action in your example above, for instance, would be able to make use of your TempData["Message"] variable).
This is common practice in the PRG means of coding MVC interactions (Post-Redirect-Get) since you often need to pass information from the initial target action when doing the Redirect to the Get. An example of how this might be useful in a Get is below where I often just default to a new viewmodel UNLESS there is one already passed from a redirect in TempData:
public ActionResult System() {
SystemAdminVM model = (SystemAdminVM)TempData["screenData"] ?? new SystemAdminVM();
One more thing; I see you explicitly clearing your TempData and ViewData dictionary entries in your view. You don't need to do that as by that point they are at the end of their life spans anyway...
Happy coding!
Your app's behavior is the one you'd expect if you're using TempData where you should be using ViewData.
You want to double-check that you're storing your status feedbacks in TempData only when the controller does a re-direct. Otherwise, you should use ViewData.
This smells like you need a couple of unit tests to confirm the behavior you're seeing. Try writing up a couple using this example as a starting point:
http://weblogs.asp.net/leftslipper/archive/2008/04/13/mvc-unit-testing-controller-actions-that-use-tempdata.aspx
If you have configured multiple worker process for your application, but session state mode is "InProc", then you can't use default TempData implementation, as session state becomes unusable. (see ASP.NET session state and multiple worker processes)
You could try to use MvcFutures CookieTempDataProvider instead.