Update Feb 23 - See github issue https://github.com/heartcombo/devise/issues/5446, this should now be fixed. My apologies to all here that since I discovered this and created the issue a year ago I've had zero time to code and respond (I code for fun alone and this year was too busy for me, that time will come again and I appreciate all of your help).
I have a new rails 7 app [rails new devisetest], a simple controller with static page and added devise [gem 'devise' + rails g devise user]. All defaults. Flash messages added to the application.html.erb as per devise instructions
Devise error messages are not being shown, but I see them being generated in the console (or at least, I see the rendered message but they don't show, and I see rendered for the _links partial and that does show)
In another test using a quick scaffold (and having copied the devise views into the app but not modified them), flash messages show for the scaffold but still not for devise.
If I visit http://localhost:3000/users/sign_up and create a user with a too short password I see this in the log
Started POST "/users" for ::1 at 2021-12-24 08:02:10 +0000
Processing by Devise::RegistrationsController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"email"=>"asdf#asdf.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up"}
TRANSACTION (0.1ms) begin transaction
User Exists? (2.7ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "asdf#asdf.com"], ["LIMIT", 1]]
TRANSACTION (0.2ms) rollback transaction
Rendering layout layouts/application.html.erb
Rendering /home/sroot/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/devise-4.8.1/app/views/devise/registrations/new.html.erb within layouts/application
Rendered /home/sroot/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/devise-4.8.1/app/views/devise/shared/_error_messages.html.erb (Duration: 0.7ms | Allocations: 582)
Rendered /home/sroot/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/devise-4.8.1/app/views/devise/shared/_links.html.erb (Duration: 0.2ms | Allocations: 92)
Rendered /home/sroot/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/devise-4.8.1/app/views/devise/registrations/new.html.erb within layouts/application (Duration: 3.9ms | Allocations: 2199)
Rendered layout layouts/application.html.erb (Duration: 77.5ms | Allocations: 4429)
Completed 200 OK in 473ms (Views: 78.7ms | ActiveRecord: 3.1ms | Allocations: 15288)
Can anyone point out my mistake please?
Or suggest other steps I can follow to get closer to my error?
Thanks
I am following the tutorials video https://gorails.com/episodes/devise-hotwire-turbo as well as tried it with this tutorial here:
https://medium.com/#nejdetkadir/how-to-use-devise-gem-with-ruby-on-rails-7-33b89f9a9c13
But the flash messages are not showing up. I added to my devise initializer:
# config/initializers/devise.rb
class TurboFailureApp < Devise::FailureApp
def respond
if request_format == :turbo_stream
redirect
else
super
end
end
def skip_format?
%w(html turbo_stream */*).include? request_format.to_s
end
end
config.navigational_formats = ['*/*', :html, :turbo_stream]
config.warden do |manager|
manager.failure_app = TurboFailureApp
# manager.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy
end
As well as created a devise controller:
# app/controllers/users/devise_controller.rb
class Users::DeviseController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end
I have the same issue. It is someway linked with the compatibility of Rails 7.0 and Devise 4.8.1. I am unable to sign up a user. The sqlite3 database also remains empty, so I don't think its just the issue of flash messages and the users aren't being entered.
Some people have addressed various issue with Rails 7 / Devise by adding the following to config/initializers/devise.rb:
config.navigational_formats = ['*/*', :html, :turbo_stream]
If you just want it, "to work", you can add data: { turbo: false } to your devise form. This will prevent turbo from hijacking your form submissions with all it's automagic.
Credit to john-999 here https://github.com/heartcombo/devise/issues/5446#issuecomment-1083485163
In my case my views/devise/sessions/new.html.erb looked like:
<%= form_for(resource, as: resource_name, url: session_path(resource_name), data: { turbo: false }) do |f| %>
<% resource.errors.full_messages.each do |msg| %>
<div><%= msg %></div>
<% end %>
<div class="mb-3">
<%= f.label :email, class: "form-label" %>
<%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control' %>
</div>
<div class="mb-3">
<%= f.label :password, class: "form-label" %>
<%= f.password_field :password, autocomplete: "current-password", class: 'form-control' %>
</div>
<div class="mb-4 mt-4">
<%= f.submit t('.sign_in'), class: "btn btn-lg btn-primary" %>
</div>
<% end %>
I added in config/initializers/devise.rb, at the top this:
class TurboFailureApp < Devise::FailureApp
def respond
if request_format == :turbo_stream
redirect
else
super
end
end
def skip_format?
%w(html turbo_stream */*).include? request_format.to_s
end
end
inside devise.setup do |config|, this:
# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
# :html, should redirect to the sign in page when the user does not have
# access, but formats like :xml or :json, should return 401.
#
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists.
#
# The "*/*" below is required to match Internet Explorer requests.
config.navigational_formats = ['*/*', :html, :turbo_stream]
# ==> Controller configuration
# Configure the parent class to the devise controllers.
config.parent_controller = 'TurboController'
# ==> Warden configuration
config.warden do |manager|
manager.failure_app = TurboFailureApp
end
then I created a controller in app/controllers/turbo_controller.rb whit this:
class TurboController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end
I think the best answers are here by Alex Flash is not displayed in the same view in Rails
and again by Alex Rails 7 signup form doesn't show error messages
This is also true without devise, the array of error messages belong to Active Record and associated to the Active Record #user object failing validation in the model which inherits from ApplicationRecord and ActiveRecord, not devise. Even if you created a user model, controller, views and all the actions from scratch, the error messages will still be there when validations fail in the model, yet not showing up in the view. Even the flash messages don't show. Instead in your controller if user fails to save, then, in the else statement to redirect to request referrer with a flash error message, maybe like redirect_to request.referrer, flash: {error: #user.errors.full_messages}, in the create user method or update, but error messages don't seem to show if you are rendering back to new action unless server responds with 422, so you can do same as scaffolds render 'new', status: :unprocessable_entity, flash: {error: #user.errors.full_messages} which leads to a server response of 422, this will work . This also applies to success message if #user is saved in the DB. or updated you can just show redirect_to user_url(#user), notice: " user was successfully created / updated."
You will notice in the browser console, if you register with invalid fields, you won't get the error full_messages and you'll notice your browser console showing: Error: Form responses must redirect to another location, that's if you're rendering back to new action, if the object fails to save in your create action.
It seems like if you go to application.html.erb and remove <%= javascript_importmap_tags %>, save the file and try to register with invalid fields, you will get all your #user.errors.full_messsages keys and values. Not a solution.
or better leave javascript_importmap_tags alone, and you can add data: { turbo: false } in your form_with. But still not a sustainable solution.
or like others suggested if you're using devise for registering users in initializers.rb you can add config.navigational_formats = ['*/*', :html, :turbo_stream]
Using Devise Gem alerts and flash messages are not shown in rails 7 because of Turbo Stream
I added in config/initializers/devise.rb, at the top this:
# config/initializers/devise.rb
class TurboFailureApp < Devise::FailureApp
def respond
if request_format == :turbo_stream
redirect
else
super
end
end
def skip_format?
%w(html turbo_stream */*).include? request_format.to_s
end
end
# I added in config/initializers/devise.rb, at the top this:
class TurboFailureApp < Devise::FailureApp
def respond
if request_format == :turbo_stream
redirect
else
super
end
end
def skip_format?
%w(html turbo_stream */*).include? request_format.to_s
end
end
# Add these lines inside devise.setup do |config|, this:
config.parent_controller = 'TurboController'
config.navigational_formats = ['*/*', :html, :turbo_stream]
config.warden do |manager|
manager.failure_app = TurboFailureApp
# manager.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy
end
Also create new file with name (turbo_controller.rb) in the controller folder. On some places I saw that with out createing this controller file they added it into the devise.rb file which don't work in my case so I create that file.
# app/controllers/turbo_controller.rb
class TurboController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end
The proper way of doing this, at the moment, is #Sabrina's answer. The following is just something else that could be done:
Put it in a frame
Do this first:
# config/initializers/devise.rb
config.navigational_formats = ['*/*', :html, :turbo_stream]
Now the form is rendering 200 on validation errors and it should be 422. This would not be an issue if we were working inside a turbo frame.
Copy your application.html.erb layout and rename it to devise.html.erb:
cp app/views/layouts/application.html.erb app/views/layouts/devise.html.erb
Wrap it in a frame:
<!-- app/views/layouts/devise.html.erb -->
<!DOCTYPE html>
<html>
<head> <%= render "layouts/head" %> </head>
<body>
<main class="container px-5 mx-auto mt-28">
<!-- turbo action `advance` to update the url vvvvvvvv -->
<%= turbo_frame_tag :login, data: { turbo_action: :advance } do %>
<% if alert.present? %>
<p class="p-3 text-red-500 bg-red-50"> <%= alert %> </p>
<% end %>
<%= yield %>
<% end %>
</main>
</body>
</html>
This doesn't work by itself, because responses now render without a layout and we're losing the :login frame. Update your controller to fix it:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
layout -> { devise_controller? ? "devise" : (false if turbo_frame_request?) }
end
Update routes, to have a GET route for every POST route:
devise_scope :user do
get "/users", to: redirect("/users/sign_up")
get "/users/password", to: redirect("/users/password/new")
end
Alternatively, don't use turbo_action: :advance and skip extra routes. You can update links frame target to navigate outside of the frame:
# app/views/devise/shared/_links.html.erb
<% if controller_name != 'sessions' %>
<%= link_to "Log in", new_session_path(resource_name), data: { turbo_frame: :_top } %><br />
<% end %>
<% if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name), data: { turbo_frame: :_top } %><br />
<% end %>
# ...
or just edit the templates and put those links outside of the frame.
DIY render
Second idea is a bit more questionable, I just wanted to see if it would work.
# still need this bit
config.navigational_formats = ['*/*', :html, :turbo_stream]
The issue is that turbo refuses to render the 200 form response, which we actually receive.
I mean we have the response, so just render it anyway:
// app/(assets/)?javascript(s)?/devise.js
document.addEventListener("turbo:before-fetch-response", async (event) => {
event.preventDefault();
const response = new DOMParser().parseFromString(await event.detail.fetchResponse.responseHTML, "text/html")
document.body.replaceWith(response.body)
})
// add to devise layout, your favorite javascript (include|pack|import_module) tag
<%= javascript_include_tag "devise" %>
// turbo frame and controller layout override are not needed
Login works. Registration works. Links navigate and url is updated. Form submissions don't update the url, which is perfect (supporting refresh after form submissions is not needed here).
adding data-turbo="false" or data: {turbo: false} with form helpers might solve this issue
Related
I am creating a simple workflow where, after signing up, a publisher can create a newsletter. This newsletter needs three pieces of information: title, description, and publisher_id (i.e. the creator). My question is two-fold:
What is the 'correct' way to set the publisher_id, given that newsletters will have posts nested inside them and Rails recommends not nesting resources more than one level deep (i.e. I shouldn't nest newsletter inside publisher)?
If I am generally approaching it the correct way (see below), how am I supposed to pass the publisher_id and what am I doing wrong?
The workflow is as follows:
Create publisher and set session[:id] to #publisher.id.
Redirect to the newsletter new view.
Upon creating a newsletter, set the newsletter's publisher_id to the session[:id].
Upon navigating to to '/newsletters/new', I'm seeing the following error:
Started GET "/newsletters/new" for ::1 at 2020-05-04 15:53:22 -0700
Processing by NewslettersController#new as HTML
"<ActionController::Parameters {\"controller\"=>\"newsletters\", \"action\"=>\"new\"} permitted: false>"
Rendering newsletters/new.html.erb within layouts/application
Rendered newsletters/new.html.erb within layouts/application (Duration: 2.3ms | Allocations: 738)
And upon submitting 'Create Newsletter', I'm seeing the following error:
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
app/controllers/newsletters_controller.rb:21:in `create'
Started POST "/newsletters" for ::1 at 2020-05-04 15:58:34 -0700
(0.0ms) SELECT sqlite_version(*)
Processing by NewslettersController#create as JS
Parameters: {"authenticity_token"=>"XXX", "newsletter"=>{"title"=>"Newsletter 1", "description"=>"Description content"}, "commit"=>"Create Newsletter"}
Completed 500 Internal Server Error in 11ms (ActiveRecord: 1.0ms | Allocations: 7085)
publishers_controller.rb
class PublishersController < ApplicationController
def create
#publisher = Publisher.new(publisher_params)
if #publisher.save!
session[:id] = #publisher.id
redirect_to new_newsletter_path
else
render 'new'
end
end
private
def publisher_params
params.require(:publisher).permit(:email, :password)
end
end
newsletters_controller.rb
class NewslettersController < ApplicationController
def new
#newsletter = Newsletter.new
end
def create
#newsletter = Newsletter.new(newsletter_params)
if #newsletter.save!
redirect_to #newsletter
else
render 'new'
end
end
private
def newsletter_params
params.require(:newsletter).permit(:title, :description).merge(publisher_id: session[:id])
end
end
/newsletters/new.html.erb
<%= form_with model: #newsletter, url: newsletters_path do |form| %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :description %><br>
<%= form.text_area :description %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
You have misunderstood what the rails guides meant by "nesting resources more than one level deep" - whats really meant is that this is OK:
/publishers/1/newsletters/new
Which is one level of nesting and the nesting provides very useful contextual information. While these are kind of fishy:
/publishers/1/newsletters/2
/publishers/1/newsletters/3/replies/new
In both cases we have two levels of nesting should be able to reach the nested resource without going though publishers.
/newsletters/2
/newsletters/3/replies/new
Also if you want to add values from the session or somewhere else then the params hash when creating a record use a block or create the record off the association instead:
class NewslettersController < ApplicationController
def create
#newsletter = Newsletter.new(newsletter_params) do |newletter|
newsletter.publisher = current_publisher
end
# or
# #newsletter = current_publisher.newsletters(newsletter_params)
# save! will raise an exception if the record is not valid
# that is NOT what you want here
if #newsletter.save
redirect_to #newsletter
else
render 'new'
end
end
end
This makes it much more apparent what is coming from where.
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.
I'm building a simple API with Rails 4, but with my "create" method, it all goes horribly wrong.
Here is the relevant part of my routes file:
namespace :api, defaults: { format: 'json' } do
# /api/... Api::
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
resources :users
end
end
Here is the api/v1/users_controller.rb:
class Api::V1::UsersController < ApplicationController
protect_from_forgery except: :create
respond_to :json
def index
respond_to do |format|
format.html {render text: "Your data was sucessfully loaded. Thanks"}
format.json { render text: User.last.to_json }
end
end
def show
respond_with User.find(params[:id])
end
def create
respond_with User.create(user_params)
end
def update
respond_with User.update(params[:id], params[:users])
end
def destroy
respond_with User.destroy(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :age, :location, :genre_ids => [], :instrument_ids => [])
end
end
Whenever I try to add an API with JSON, I get "{"errors":{"name":["can't be blank"]}}"
It works to create a user with my regular controller, but I have a feeling my API controller is getting messed up because of the Strong Parameters.
Any suggestions for how to do this correctly in Rails 4?
Also, I have a few Has-Many-Through relationships through my user model. The API's user controller should be able to see that off the bat, right?
Thanks
EDIT:
I'm now getting this error:
EDIT:
{
"name": "Sally",
"age": "23",
"location": "Blue York",
"genre_ids": [1, 2, 3]
}
EDIT AGAIN
Even with adding the User parameter in my JSON call, it still gives me the same error of the :user param missing. Am I using strong parameters incorrectly? In my "regular" users_controller, I can create a user easily with a form that I have set up, but with this API controller, I can't seem to create one with JSON. Any other suggestions?
EDIT YET AGAIN
Here Is The Log From Start to Error
rails s
=> Booting WEBrick
=> Rails 4.0.1 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2013-12-19 14:03:01] INFO WEBrick 1.3.1
[2013-12-19 14:03:01] INFO ruby 1.9.3 (2013-02-22) [x86_64-darwin10.8.0]
[2013-12-19 14:03:01] INFO WEBrick::HTTPServer#start: pid=53778 port=3000
Started GET "/api/users" for 127.0.0.1 at 2013-12-19 14:03:02 -0500
ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by Api::V1::UsersController#index as JSON
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
Rendered text template (0.0ms)
Completed 200 OK in 142ms (Views: 27.8ms | ActiveRecord: 0.6ms)
[2013-12-19 14:03:03] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
[2013-12-19 14:03:03] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
Started POST "/api/users" for 127.0.0.1 at 2013-12-19 14:03:37 -0500
Processing by Api::V1::UsersController#create as JSON
Completed 400 Bad Request in 1ms
ActionController::ParameterMissing (param not found: user):
app/controllers/api/v1/users_controller.rb:40:in `user_params'
app/controllers/api/v1/users_controller.rb:20:in `create'
Rendered /usr/local/rvm/gems/ruby-1.9.3-p392/gems/actionpack- 4.0.1/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.7ms)
Rendered /usr/local/rvm/gems/ruby-1.9.3-p392/gems/actionpack-4.0.1/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.0ms)
Rendered /usr/local/rvm/gems/ruby-1.9.3-p392/gems/actionpack-4.0.1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (0.8ms)
Rendered /usr/local/rvm/gems/ruby-1.9.3-p392/gems/actionpack- 4.0.1/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (31.6ms)
EDIT #6
Here is my "real" users_controller that lives in my app and not my API. The form creates a user from this controller and NOT the API controller.
class UsersController < ApplicationController
def index
#users = User.all
#genres = Genre.all
#instruments = Instrument.all
render json: #users
end
def new
#user = User.new
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
render json: #user, status: :created, location: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :age, :location, :genre_ids => [], :instrument_ids => [])
end
end
ALSO - The User Form
<div class="row">
<div class="span6 offset3">
<%= form_for(#user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :age %>
<%= f.text_field :age %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :location %>
<%= f.text_field :location %>
<br>
<% Genre.all.each do |genre| %>
<%= check_box_tag "user[genre_ids][]", genre.id %>
<%= genre.name %><br>
<% end %>
<br>
<% Instrument.all.each do |instrument| %>
<%= check_box_tag "user[instrument_ids][]", instrument.id %>
<%= instrument.name %><br>
<% end %>
<%= f.submit "Create My Account!" %>
<% end %>
</div>
</div>
<%= users_path %>
Here is my user.rb File
class User < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
has_many :generalizations
has_many :genres, through: :generalizations
has_many :instrumentations
has_many :instruments, through: :instrumentations
end
Here is what I have in my routes file:
namespace :api do
namespace :v1 do
resources :users
end
end
My POST Request
POST /api/v1/users HTTP/1.1
Host: localhost:3000
Cache-Control: no-cache
{ "user": { "name": "Sally", "age": "23", "location": "Blue York", "genre_ids": [1, 2, 3] } }
UPDATE
I changed my strong-params to be this:
def user_params
params.require(:user).permit(:name, :age, :location, :genre_ids => [], :instrument_ids => []) if params[:user]
end
So the "if" statement at the end makes the error go away, but whenever I post to my API, it gives me back "null". So this could be the same problem as before, but shown in a different way. But, at the same time, it could be progress!
Here Is The Log For The Previous Update
Started POST "/api/v1/users" for 127.0.0.1 at 2013-12-21 11:38:03 -0500
Processing by API::V1::UsersController#create as */*
(0.1ms) begin transaction
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
(0.1ms) rollback transaction
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
Rendered text template (0.0ms)
Completed 200 OK in 20ms (Views: 0.3ms | ActiveRecord: 0.6ms)
FINAL UPDATE
I was missing a few things, but the main thing that did it was that I was missing "Content-Type - application/json" as my Header.
I feel so accomplished! Thanks for all your help, everyone!
According to your code parameters in the JSON you are posting should be inside params[:user]. So the JSON should look like:
{
"user": {
"name": "Sally",
"age": "23",
"location": "Blue York",
"genre_ids": [1, 2, 3]
}
}
Rails 4 is a great choice for building APIs. I would go with the rails-api gem. It will perform way better than a full blown Rails stack.
I have built plenty of API's in Rails using the Rails API gem. Usually in combination with RABL (which you can use to create nice templates to render your JSON). I am not a big fan of integrating an API directly into your production Rails app (serving websites and JSON) as you will create a big mess over time when starting to add more versions to your API. There are some very good Railscasts (www.railscasts.com): Search for API.
When accessing your API you would use a global filter in your application_controller.rb file. You could do something like this:
before_filter :authenticate_user, :except => 'users#index'
private
def authenticate_user
#current_user = User.find_by_api_key(params[:token])
unless #current_user
render :status=>403, :json=>{:message=>"Invalid token"}
end
end
def current_user
#current_user
end
end
In this case you would send the token in your request (that's quick and dirty, rather use the header instead) as a request parameter. You need to add the API key or whatever you want to use to your user model. Just create a migration adding api_key or however you want to call it to the user model or create a new table with keys, secrets etc. and a user_id field for your belongs_to (and a User has_many api_keys, or has_one). This way you can allow your users at any time to change their keys etc. (re-generate) without messing with usernames/password or even allow them to have multiple API keys with some tags (testing, production, etc). For your user signup you could add to your model:
before_create :generate_api_key
and then create a simple method like:
def generate_api_key
begin
self.api_key = SecureRandom.hex
end while self.class.exists?(api_key: api_key)
end
Hope it helps!
I'm trying to create a simple form to collect an email address and then send an email to me.
I'm randomly getting uninitialized constant Marketing::InviteController::MarketingMailer, sometimes on the first time the form is submitted, but ALWAYS the second time is submitted, so if you go to localhost:3000/request-invite, enter an email and submit the form, then enter an email and submit the form again, you will get this error.
Any ideas why i'm getting this error even?
/controllers/marketing/invite_controller.rb
class Marketing::InviteController < ApplicationController
layout 'marketing/layouts/layout'
# GET /request-invite
def new
#invite = Invite.new
respond_to do |format|
format.html # new.html.erb
end
end
# POST /request-invite
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
MarketingMailer.invite(#invite.email).deliver
format.html { redirect_to request_invite_path, notice: 'Success' }
else
format.html { render action: "new", error: 'An error has occurred' }
end
end
end
end
/views/marketing/invite/new.html.erb
<%= render :partial => 'shared/messages', :locals => {:object => #invite} %>
<%= render :partial => 'shared/object_errors', :locals => {:object => #invite} %>
<%= form_for #invite, :url => request_invite_path, :method => :post do |f| %>
<%= f.text_field :email, :placeholder => 'Email' %>
<%= f.submit 'Send Request', :class => 'btn' %>
<% end %>
/mailers/marketing/marketing_mailer.rb
class Marketing::MarketingMailer < ActionMailer::Base
require 'mail'
address = Mail::Address.new "test#test.com" # ex: "john#example.com"
address.display_name = "Test" # ex: "John Doe"
# Set the From or Reply-To header to the following:
address.format # returns "John Doe <john#example.com>"
default from: address
# Sends an email when they request an invite
def invite(to)
#to = to
mail(:subject => "Jobfly Invite Request", :to => 'test#test.com', :reply_to => #to)
end
end
/views/marketing/marketing_mailer/invite.html.erb
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<p>The following email has requested an invite for jobfly</p>
<p>Email: <%= #to %></p>
</body>
</html>
Log sample:
Started GET "/request-invite" for 127.0.0.1 at 2013-12-10 17:41:44 -0600
Processing by Marketing::InviteController#new as HTML
Rendered shared/_messages.html.erb (0.1ms)
Rendered shared/_object_errors.html.erb (0.1ms)
Rendered marketing/invite/new.html.erb within marketing/layouts/layout (3.0ms)
Completed 200 OK in 72ms (Views: 71.3ms | ActiveRecord: 0.0ms)
Started POST "/request-invite" for 127.0.0.1 at 2013-12-10 17:41:49 -0600
Processing by Marketing::InviteController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"RTVaxb3jK9VwS0/SZ1e6JjMeA6XVqBof44m04Wsvbd8=", "invite"=>{"email"=>"jeff#gmail.com"}, "commit"=>"Send Request"}
(0.2ms) BEGIN
Invite Exists (0.3ms) SELECT 1 AS one FROM `invites` WHERE `invites`.`key` = 'SZNu9vbl' LIMIT 1
SQL (33.8ms) INSERT INTO `invites` (`created_at`, `email`, `key`, `updated_at`) VALUES ('2013-12-10 23:41:49', 'jeff#gmail.com', 'SZNu9vbl', '2013-12-10 23:41:49')
(0.4ms) COMMIT
Completed 500 Internal Server Error in 43ms
NameError - uninitialized constant Marketing::InviteController::MarketingMailer:
...
It sounds like autoloading issues. Have you tried referencing the MarketingMailer constant using the full name? ::Marketing::MarketingMailer ?
Rails doesn't load all classes in development. Instead, when a class is missing, Rails tries to guess where in the project to find the class, based on a set of autoloading paths.
For instance, all of the folders in your app folder are added to the autoloading paths. So if you reference Foobar.new, Rails will look in your app/models or app/controllers for a foobar.rb file, that it expects to contain a class Foobar definition. However, when you use modules, the waters become more muddy.
class Foo::Bar
# some code
Baz.new
end
Rails will now think that the Baz class is defined in a baz.rb class that is located in subfolders, so it will look in the autoload folders and search for subfolders named according to the enclosing modules, checking app/models/foo/bar/baz.rb and app/controllers/foo/bar/baz.rb
When it doesn't find anything, Rails has different ways of trying to resolve it which I don't fully understand. However, if you prefix your class reference with two colons, that means you are referencing from the root namespace and Rails should not try to search relative to your current module, so if we take the above example again:
class Foo::Bar
...
Baz.new # Searches for foo/bar/baz.rb
::Baz.new # Searches for baz.rb
::My::Baz.new # Searches for my/baz.rb
My::Baz.new # Searches for foo/bar/my/baz.rb
...
end
In production mode, most of these problems disappear, because Rails preloads all source files in your project, so if Baz has been defined anywhere in any folder, Rails doesn't have to make guesses about where to find it. It already exists, because it has been loaded.
i think you should refer your mailer just as you defined it in your code Marketing::MarketingMailer, not MarketingMailer
Ok ill be honest, i haven't spent much time looking for a solution yet seeing as how my son is keeping my attention running around. Either way I would like to ask a question for something that seems pretty simple but has stumped me thus far.
So to keep it simple lets say I have Users(w/model) and Home controllers, Home is the root route.
In the root directory I want to be able to see all posts made by the User using ajax to update a partial on the home page with the list of posts.
In the users controller I have a def called userposts with this in it
def userposts
#user = User.find_by_id(params[:id])
#userposts = #user.posts.all(:order => "created_at DESC")
respond_to do |format|
format.js { #userposts}
end
end
And in my view I have
<p id="aboutuser">
<% if #user.about? %>
<%= " " + #user.id.to_s %>
<% else %>
User has not yet filled this out.
<% end %>
</p>
<h3 id="authpostlink">
<%= link_to "List of all posts", user_userposts_path(#user.id), :id => #user.id, :remote => true %>
</h3>
my errors are as follows
Started GET "/users/2/userposts" for 127.0.0.1 at Sun Jan 15 13:36:23
-0600 2012 Processing by UsersController#userposts as JS Parameters: {"user_id"=>"2"} User Load (0.1ms) SELECT "users".*
FROM "users" WHERE "users"."id" IS NULL LIMIT 1 Completed 500 Internal
Server Error in 1ms
NoMethodError (undefined method posts' for nil:NilClass):
app/controllers/users_controller.rb:27:inuserposts'
Rendered
/home/n0de/.rvm/gems/ree-1.8.7-2011.03/gems/actionpack-3.1.0/lib/action_dispatch/middleware/templates/rescues/_trace.erb
(0.8ms) Rendered
/home/n0de/.rvm/gems/ree-1.8.7-2011.03/gems/actionpack-3.1.0/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
(0.8ms) Rendered
/home/n0de/.rvm/gems/ree-1.8.7-2011.03/gems/actionpack-3.1.0/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (3.2ms)
I do realize i did not post the _show.js.erb file that calls the action to update the div but according to the error messages it doesn't seem the process has gotten that far.
Assuming you have the following:
# /app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
end
# /app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
end
I would add a nested resource in your routes file:
#/config/routes.rb
resources :users do
resources: posts
end
You get a bunch of great "_path" methods for free (run $ rake routes from your console to see them all), and it gives you access to URLs such as /users/123/posts. This request will go to the index method of your PostsController and will automatically include :user_id => 123 in the params hash. You can then do the following:
# In your view:
<%= link_to "List of all posts", user_posts_path(#user), :remote => true %>
<div id="posts"></div>
# /app/controllers/posts_controller.rb
class PostsController < ApplicationController
respond_to :js # allows for AJAX requests
def index
if params[:user_id].present? # Do this if using the nested resource
#user = User.find(params[:user_id])
#posts = #user.posts.order('posts.created_at DESC')
else # Otherwise, treat it like a normal request
#posts = Post.all
end
respond_with #posts
end
end
Because the your request is sent remotely, you need a corresponding "js" version of your index view (note the file name below and see this Railscast for more explanation):
# /app/views/posts/index.js.erb
$('#posts').html("<%= escape_javascript(render(#posts)) %>");
This will render out the posts into that <div id="posts"> tag. (You'll probably need a "_post.html.erb" partial in /app/views/posts/" as well.)
However, having said all this, are you sure you need to do this via AJAX? You could simply preload all the posts in the UsersController#show method, initially hide the list using CSS, and then add a jQuery toggle() method on that link. Anyway, hope this makes sense and is helpful.