Rails 4 API with Strong Parameters? - ruby-on-rails

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!

Related

Rails 7 + Devise Not seeing flash messages from devise

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

Rails foreign key assignment failing

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.

Paperclip gem with Rails 4 doesn't save non-file fields

I'm building a Rails 4 app with the paperclip gem in which users can upload a video file from a form with several other fields (uploader_first_name, uploader_last_name, uploader_email).
The paperclip installation went smoothly (although I needed to add gem 'protected_attributes'
) and I'm able to save file to the correct and create the corresponding records in the videos table, however all the non-paperclip fields are null and I'm not sure why.
class Video < ActiveRecord::Base
belongs_to :project
attr_accessible :file
has_attached_file :file, :url=>"/tmp/video_uploads", :path=>":rails_root/tmp/video_uploads"
end
class VideosController < ApplicationController
def save
#video = Video.create(params[:video])
if #video.save
redirect_to root_path
else
redirect_to "somewhere_else"
end
end
#I tried with this too...
#private
#def video_params
#params.require(:video).permit( :uploader_first_name, :uploader_last_name, :uploader_email)
#end
end
In the view...
<h2>Upload your video</h2>
<% myclass = {:class=>'form-control'} %>
<%= form_for(:video, :url => {:action=>'save', :controller=>'videos'}, :html=>{:class=>'upload-form-js', :multipart=> true} ) do |f| %>
<div class="form-group">
<%= f.label(:uploader_first_name, "First Name") %>
<%= f.text_field(:uploader_first_name, myclass) %>
</div>
<div class="form-group">
<%= f.label(:uploader_last_name, "Last Name") %>
<%= f.text_field(:uploader_last_name, myclass) %>
</div>
<div class="form-group">
<%= f.label(:uploader_email, "Email") %>
<%= f.text_field(:uploader_email, myclass) %>
</div>
<div class="form-group">
<label>Video File</label>
<input type="file" name="video[file]" id="video_file"/></span>
</div>
<%= f.submit('Upload', {class: 'btn btn-primary btn-block'}) %>
<% end %>
Update 1
I changed to this...
class VideosController < ApplicationController
def save
#video = Video.create( video_params )
if #video.save
redirect_to root_path
else
redirect_to "somewhere_else"
end
end
private
def video_params
params.require(:video).permit(:file, :uploader_first_name, :uploader_last_name, :uploader_email)
end
end
...and now I get this error:
Errno::EISDIR in VideosController#save
Is a directory - /Applications/MAMP/htdocs/clippo2/tmp/video_uploads
Don't I want the url/path to point to a directory?
Update 2
I changed the video model to...
has_attached_file :file, :url=>"/tmp/video_uploads/:basename.:extension", :path=>":rails_root/tmp/video_uploads/:basename.:extension"
...and now no errors, files get saved to the right directory and the corresponding fields added to the new row, but all other fields are still NULL (original problem).
Update 3
I turned on the debugger and here's what I see after I try to upload a file. It looks like a strong parameters error but I'm not sure how to fix it:
Started POST "/videos/save" for 127.0.0.1 at 2013-08-27 09:21:34 -0400
Processing by VideosController#save as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"es4wPqFr9xPBUFsbHQR/gAzofDC+ZwYsiiJ7RAQZUHk=", "video"=>{"uploader_first_name"=>"adsfasdf", "uploader_last_name"=>"asdfasdf", "uploader_email"=>"asdfasdf", "file"=>#<ActionDispatch::Http::UploadedFile:0x007fc4782e31e8 #tempfile=#<Tempfile:/var/folders/f2/jhv7xx0j3hlckhcg_jbv6hr00000gn/T/RackMultipart20130827-89636-188f0hs>, #original_filename="sample_iPod.m4v", #content_type="video/mp4", #headers="Content-Disposition: form-data; name=\"video[file]\"; filename=\"sample_iPod.m4v\"\r\nContent-Type: video/mp4\r\n">, "project_hashed_id"=>"1377539908"}, "commit"=>"Upload"}
{"utf8"=>"✓", "authenticity_token"=>"es4wPqFr9xPBUFsbHQR/gAzofDC+ZwYsiiJ7RAQZUHk=", "video"=>{"uploader_first_name"=>"adsfasdf", "uploader_last_name"=>"asdfasdf", "uploader_email"=>"asdfasdf", "file"=>#<ActionDispatch::Http::UploadedFile:0x007fc4782e31e8 #tempfile=#<Tempfile:/var/folders/f2/jhv7xx0j3hlckhcg_jbv6hr00000gn/T/RackMultipart20130827-89636-188f0hs>, #original_filename="sample_iPod.m4v", #content_type="video/mp4", #headers="Content-Disposition: form-data; name=\"video[file]\"; filename=\"sample_iPod.m4v\"\r\nContent-Type: video/mp4\r\n">, "project_hashed_id"=>"1377539908"}, "commit"=>"Upload", "controller"=>"videos", "action"=>"save"}
Unpermitted parameters: project_hashed_id
WARNING: Can't mass-assign protected attributes for Video: uploader_first_name, uploader_last_name, uploader_email
app/controllers/videos_controller.rb:6:in `save'
[1m[35m (0.2ms)[0m BEGIN
[1m[36mSQL (0.3ms)[0m [1mINSERT INTO `videos` (`created_at`, `file_content_type`, `file_file_name`, `file_file_size`, `file_updated_at`, `updated_at`) VALUES ('2013-08-27 13:21:34', 'video/mp4', 'sample_iPod.m4v', 2236480, '2013-08-27 13:21:34', '2013-08-27 13:21:34')[0m
[1m[35m (9.0ms)[0m COMMIT
Redirected to http://localhost:3000/
Completed 302 Found in 21ms (ActiveRecord: 9.5ms)
Welcome to the rails 4. 'attr_accessible' was replaced by the strong parameters.
http://api.rubyonrails.org/classes/ActionController/StrongParameters.html
Upd. Can you try this?
def create
#video = Video.create(video_params)
end
private
def video_params
params.require(:video).permit(:file, :uploader_first_name, :uploader_last_name, :uploader_email)
end
I figured out what's going on. The strong parameters were getting in the way because I'm using attr_accessible in the model. Instead of fiddling with the require() and permit() I removed them entirely and added the missing fields to the attr_accessible and now it works:
class Video < ActiveRecord::Base
belongs_to :project
attr_accessible :file, :uploader_first_name, :uploader_last_name, :project_hashed_id, :uploader_email, :rating
has_attached_file :file, :url=>"/tmp/video_uploads/:basename.:extension", :path=>":rails_root/tmp/video_uploads/:basename.:extension"
end
class VideosController < ApplicationController
def save
logger.debug( params )
##video = Video.new( video_params )
#video = Video.new( params[:video])
if #video.save
redirect_to root_path
else
redirect_to "somewhere_else"
end
end
end
I realize that attr_accessible was replaced by strong parameters in Rails 4 but I couldn't get paperclip to work without it. If anyone can tell me how, I'd love to know.
Update
I removed attr_accessible entirely and just used the strong parameters...
class VideosController < ApplicationController
def save
logger.debug( params )
#video = Video.new( video_params )
if #video.save
redirect_to root_path
else
redirect_to "somewhere_else"
end
end
private
def video_params
#params.require(:video).permit(:file, :uploader_first_name, :uploader_last_name, :uploader_email, :project_hashed_id)
params.require(:video).permit!
end
end
...and it works BUT you have to remember to remove the protected_attributes gem and restart rails s for it to take effect (this n00b mistake cost me 45 minutes!)
Moral of this story: Don't mix and match attr_accessible with strong params. Do one or the other and paperclip will work with strong parameters.

Undefined Method Model Path, Form_for not saving, Posted Correctly, Rails Framework

sorry I am new to rails
Right now I am trying to build a small application just like https://pinboard.in, I am trying to get a summer internship with them.
Here is my Bookmark model
class Bookmark < ActiveRecord::Base
attr_accessible :url, :title, :description, :counter
belongs_to :user
#validates that url has https:// or http://
validates :url, :format => { :with => /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0- 9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix,
:message => "Invalid URL" }
end
Here is my bookmark controller
class BookmarksController < ApplicationController
def add_bookmark
#bookmark = Bookmark.new
respond_to do |format|
format.html
end
end
def draw_recent
#bookmarks = Bookmark.all
end
end
Here is my form
<%= form_for :bookmark do |f| %>
URL: <%= f.text_field :url %><br/>
Title: <%= f.text_field :title %><br/>
Description: <%= f.text_field :description %><br/>
<%= f.submit "Submit" %>
<% end %>
Everything get rendered correctly and when I put in the information and submit add
here is my output
Started POST "/add" for 127.0.0.1 at 2013-05-09 09:55:58 -0400
Processing by BookmarksController#add_bookmark as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZCxA226pOVyu5AkQAmvvfKz5uPQ4gFggPTwrswtqZYk=", "bookmark"=> {"url"=>"http://litmus.com", "title"=>"Email marketing ", "description"=>"email marketing "}, "commit"=>"Submit"}
Rendered bookmarks/_form.html.erb (1.9ms)
Rendered bookmarks/add_bookmark.html.erb within layouts/application (3.3ms)
Completed 200 OK in 96ms (Views: 95.4ms | ActiveRecord: 0.0ms)
I have two thoughts in my head, well my form is posting it correctly but somehow it is not saving anything to my db, do I need a save method in my controller?
when I try #bookmark instead of :bookmark the app throws me an error saying wrong bookmarks.path
undefined method `bookmarks_path
I understand the former you are actually working with the instance #bookmark in the controller and the latter is wrapping around the model....
Can someone enlighten me? i feel like this is very trivial for you guys... :)
Should be form_for #bookmark do |f|. You are getting the undefined method error because you have not defined your routes in config/routes.rb, add there:
resources :bookmarks
That will add the RESTful resources following the CRUD convention, so you should change the name of your controller methods for this to work out-of-the-box. You will need at least three methods for what you are doing right now:
In your app/controllers/bookmarks_controller.rb:
First one will render your form at /bookmarks/new
def new
#bookmark = Bookmark.new
end
Second one will process the form submission (no action is needed from you apart that following the naming convention)
def create
#bookmark = Bookmark.new(params[:bookmark])
#bookmark.save
end
Third one to show the bookmarks as in your current 'draw_recent'
def index
#bookmark = Bookmark.all
end
Afterwards you can go on validating the data, etc but the basic flow should be like that.
It is better to start with the conventions to go later on to change the method standard names when you have more confidence.

RoR, params not being passed to the controller for a :remote link_to

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.

Resources