I am learning rails and I am trying to understand how the MVC model works with rails 4. I am practicing this by creating a form that will allow the user to upload an image with a name to the database. I am using CarrierWave to handle image storage in the database. This is what I have so far. As I am new to Rails 4, I'm not sure how all these parts connect together.
Here are my models for User and IncomePicture:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:rememberable, :validatable
has_many :expense_pictures
has_many :income_pictures
end
class IncomePicture < ActiveRecord::Base
belongs_to :user
mount_uploader :image, ImageUploader
has_one :income_text
end
Controllers:
class UserController < ApplicationController
def create
User.create(user_params)
end
private
def user_params
# required input for params
# permit - returns a version of the params hash with ony the permitted attributes
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
class IncomePicturesController < ApplicationController
def create
# create IncomePicture object with params
#income_picture = IncomePicture.new(IncomePicture_params)
#
if #income_picture.save
flash[:notice] = "Income picture successfully uploaded"
redirect_to
end
private
def IncomePicture_params
params.require(:income_picture).permit(:image, :name)
end
end
view for form:
<%= form_for #income_picture, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<p>
<%= f.file_field :image %>
</p>
<p><%= f.submit %></p>
<% end %>
I'm not sure how to create a form that will store the upload to the logged in user. Currently only the user login portion works.
I am getting this error when I try to run rails s
First argument in form cannot contain nil or be empty
on the line
--> <%= form_for #income_picture, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
As the error says, the first argument of form_for cannot be nil or empty which means that #income_picture, the first argument, is most probably nil. So you have to ask why this variable is nil and where should I define it.
I'm assuming that the form is under app/views/income_pictures/new.html.erb which means that the most probable action corresponding to that view is the new action under IncomePicturesController.
Add a new action in the IncomePicturesController and define #income_picture
class IncomePicturesController < ApplicationController
def new
#income_picture = IncomePicture.new
end
...
end
Related
As I searched, this is a common issue, but none of the answers I found work for my case.
I have set up a User model with devise and it has two related models, it has one Contact Detail and many Addresses. The nested form works well with addresses, but my contact detail fields are not shown.
My User model is the following:
validates_presence_of :contact_detail, :addresses
# Include default devise modules. Others available are:
# :lockable, :timeoutable, :trackable and :omniauthable
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
has_one :contact_detail, dependent: :destroy
has_many :addresses, dependent: :destroy
accepts_nested_attributes_for :addresses,
allow_destroy: true
accepts_nested_attributes_for :contact_detail,
allow_destroy: true
The contact details model only has belongs_to :user
I made the changes mentioned at devise gem at my application controller:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [addresses_attributes: [:street_name, :street_number, :city, :country, :postal_code, :name],
contact_detail_attributes: [:first_name, :last_name, :description, :telephone, :mobile ]])
end
end
and my app/views/devise/registrations/new.html.erb file looks like this:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
...
<div>
<% f.fields_for :contact_detail do |contact_detail|%>
<div class="field">
<%= contact_detail.label :first_name %>
<%= contact_detail.text_field :first_name %>
</div>
<div class="field">
<%= contact_detail.label :last_name %>
<%= contact_detail.text_field :last_name %>
</div>
<div class="field">
<%= contact_detail.label :description %>
<%= contact_detail.text_area :description %>
</div>
<div class="field">
<%= contact_detail.label :telephone %>
<%= contact_detail.number_field :telephone %>
</div>
<div class="field">
<%= contact_detail.label :mobile %>
<%= contact_detail.number_field :mobile %>
</div>
<% end %>
</div>
...
But my contact detail fields are not shown. Any ideas?
You have to "seed" the relation in order for the inputs for an association to appear. fields_for works like a loop. If the association is empty or nil the block runs 0 times.
Normally you would do this in the new action of your controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_contact_detail
end
end
In Devise the new action is Devise::RegistrationsController#new which you can customize by subclassing the controller:
class MyRegistrationsController < Devise::RegistrationsController
def new
super do |user|
user.build_contact_detail
end
end
end
super do |user| ... end uses the fact that all the Devise controller actions take a block and yield the resource. This makes it really easy to customize them without copy-pasting the entire method.
You then have to alter the routes so that your custom controller is used:
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'my_registrations'
}
end
I'm trying to setup a simple rails app with job board functionality. I was able to add jobs to the database, until I added an association between my Job model and devise User model. Now it won't update the database when I fill out the form.
jobs_controller
class JobsController < ApplicationController
def index
#jobs = Job.all
end
def new
#job = Job.new
end
def listing
end
def listings
end
def create
#job = Job.new(params.require(:job).permit(:title, :description, :url, :user_id))
if #job.save
redirect_to root_path
else
render "new"
end
end
end
new.html.erb
<%= simple_form_for #job do |form| %>
<%= form.input :title, label: "Job title" %>
<%= form.input :description, label: "Description" %>
<%= form.input :url, label: "URL" %>
<%= form.button :submit %>
<% end %>
index.html.erb
<% #jobs.each do |job| %>
<div class="job">
<h2><%= link_to job.title, job.url %></h2>
<p><%= job.description %></p>
</div>
<% end %>
<p><%= link_to "Add a job", new_job_path %></p>
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :jobs
end
job.rb
class Job < ApplicationRecord
belongs_to :user
end
There isn't an error in the console, but the database doesn't seem to be updated or it's not updating the view.
I also ran a migration:
class AddUserToJob < ActiveRecord::Migration[5.2]
def change
add_reference :jobs, :user, foreign_key: true
end
end
You can get the user with current_user in Devise.
class JobsController < ApplicationController
# This restricts the actions to authenticated users and prevents a nil error
before_action :authenticate_user, except: [:show, :index]
# ...
def create
# this sets the user_id column
#job = current_user.jobs.new(job_params)
if #job.save
# you really should set a flash message or something to notify the user
# and possibly redirect to the show or index action instead
redirect_to root_path
else
render "new"
end
end
private
def job_params
params.require(:job)
.permit(:title, :description, :url, :user_id)
end
end
If you don't want to associate the job immediately to a user, you need to change the association to be optional, like:
class Job < ApplicationRecord
belongs_to :user, optional: true
end
Else you need to supply user_id in your form or set it in the controller action.
You should also delegate this part to a separate method
def job_params
params.require(:job).permit(:title, :description, :url, :user_id)
end
Job.new(job_params)
I have Account model which have a has_many relationship with User model:
class Account < ActiveRecord::Base
has_many :users, -> { uniq }
accepts_nested_attributes_for :users
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :confirmable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :account
I added avatar attribute to User model using paperclip.
I want each user to have access to the common account settings, and inside it having the possibility to upload his/her own avatar.
I use simple_form so I tried this:
<%= simple_form_for current_account, :html => { :multipart => true } do |f| %>
<%# here come account settings %>
<%= f.input :time_zone, :label => t(".timezone"),
:
:
<%# here I need to access current user attributes %>
<%= f.simple_fields_for :user, current_account.users.first do |user_form| %>
<%= user_form.file_field :avatar, :error => false %>
<% end %>
<% end %>
First problem:
I need some logic to access current_user instead of current_account.users.first. Since there is a superadmin which can access all accounts, use current_user is not enough.
Second (and bigger) problem:
I added in my controller the avatar parameter to the whitelist:
def allowed_params
params.require(:account).permit(:time_zone, :logo, :description, user: [:avatar])
end
When I try to update my model:
if current_account.update(allowed_params)
I get this error:
unknown attribute: user
I also tried:
params.require(:account).permit(:language, :time_zone, :logo, :description, :user_attributes => [:avatar])
and:
params.require(:account).permit(:language, :time_zone, :logo, :description, :users_attributes => [:avatar])
(in plural)
but since I use ActionController::Parameters.action_on_unpermitted_parameters = :raise I get:
found unpermitted parameters: user
It must be something very easy, some help please?
Ok, got it!!
The problem is the one-to-many relationship and the way I tried to access a single instance of user. The correct way to do it is:
<% current_account.users.each_with_index do |user, index|%>
<%= f.simple_fields_for :users, user do |user_form| %>
<%= user_form.file_field :avatar, :error => false %>
<% end %>
<% end %>
As you can see, the iteration should be done over the relation, and only when having a
single instance "in hand" we can user the simple_fields_for.
Also, notice that the first parameter passed to simple_fields_for is :users and not :user, since this is a one-to-many relationship.
I am working on a web-app using Devise and Rails 4. I have a User model which I have extended with 2 extra form fields such that when a user signs up he can also submit his first/last names. (based on http://blog.12spokes.com/web-design-development/adding-custom-fields-to-your-devise-user-model-in-rails-4/). I now want to add a Institution model. This model has_many :users, and a user belongs_to :institution. I want to be able to register the institution's name on the same form I register the user. I know I need a nested_attribute in my Institution model, since this is the parent, which I will show in a bit. When I try to sign up the user I get in the console: Unpermited parameters: Institutions.
My hint is that I cannot update my parent class(Institution) based upon my child class (User). Might there be a solution to this? Or has anyone experienced something similar?
class Institutions < ActiveRecord::Base
has_many :users,
accepts_nested_attributes_for :users
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :institution
end
registrations/new.html.erb Here I have the nested form
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
.
.
<%= f.fields_for :institutions do |i| %>
<p><%= i.label :name %><br />
<%= i.text_field :institutions_attr %></p>
<% end %>
Based on the tutorial I have linked earlier, I have created a new User::ParameterSanitizer which inherits from the Devise::ParameterSanitizer and overridden the sign_up method as follows:
lib/user_sanitizer.rb
private
def sign_up
default_params.permit(:first_name, :last_name ,:email, :password, :password_confirmation, :current_password, institutions_attributes: [:id, :name])
end
Finally, my application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
protected
def devise_parameter_sanitizer
if resource_class == User
User::ParameterSanitizer.new(User, :user, params)
else
super
end
end
end
Thank you for reading!
Console params output:
{"utf8"=>"✓",
"authenticity_token"=>"JKuN6K5l0iwFsj/25B7GKDj7WEHR4DO3oaVyGxGJKvU=",
"user"=>{"email"=>"abc#foo.com",
"first_name"=>"abc",
"last_name"=>"xyz",
"institutions"=>{"name"=>"Government"},
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"commit"=>"Sign up"}
EDIT
As suggested, I have added
params.require(resource_name).permit( :email, :first_name, :last_name, institution: [:name], :password, :password_confirmation ) and I get an *error syntax error, unexpected ',', expecting => ...nstitution: [:name], :password, :password_confirmation )*
BUT, if I re-edit to
params.require(resource_name).permit( :email, :first_name, :last_name, :password, :password_confirmation, institution: [:name] )
I get NO syntax error but I get Unpermited parameters: Institutions in the Request.
My belief is that this happens because User is a child of Institution. I have, however, been unable to find a work-around this.
config/routes.rb
Create your own registration controller like so ... (see Devise documentation for the details of overriding controllers here ...) ... which is more elegant way as opposed to doing it via the ApplicationController
devise_for :users, controllers: {registrations: 'users/registrations'}
app/controllers/users/registrations_controller.rb
Override the new method to create a Profile associated with the User model as below ... run the configure_permitted_parameters method before to sanitize the parameters (note how to add nested parameters)
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
# GET /users/sign_up
def new
# Override Devise default behaviour and create a profile as well
build_resource({})
resource.build_profile
respond_with self.resource
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u|
u.permit(:email, :password, :password_confirmation, :profile_attributes => :fullname)
}
end
end
db/migrate/xxxxxxxxxxxxxx_create_profiles.rb
This is the migration that generates the Profile model (note the reference to User) ... this example profile only keeps fullname as an extension of the User but feel free to add as you wish!
class CreateProfiles < ActiveRecord::Migration
def change
create_table :profiles do |t|
t.references :user
t.string :fullname
t.timestamps
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
# Associations
has_one :profile, dependent: :destroy, autosave: true
# Allow saving of attributes on associated records through the parent,
# :autosave option is automatically enabled on every association
accepts_nested_attributes_for :profile
# Devise
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
app/models/profile.rb
class Profile < ActiveRecord::Base
# Associations
belongs_to :user
# Validations
validates :fullname, presence: true
end
app/views/devise/registrations/new.html
<% resource.build_profile if resource.profile.nil? %>
<%= form_for(resource, :as => resource_name,
:url => registration_path(resource_name)) do |f| %>
<ul>
<%= devise_error_messages! %>
<li class="fullname">
<%= f.fields_for :profile do |profile_fields| %>
<%= profile_fields.label :fullname %>
<%= profile_fields.text_field :fullname %>
<% end %>
</li>
<li class="email">
<%= f.label :email %>
<%= f.email_field :email, :autofocus => true %>
</li>
<li class="password">
<%= f.label :password %>
<%= f.password_field :password %>
</li>
<li class="password">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</li>
<li>
<%= f.submit %>
</li>
<li>
<p><%= render "devise/shared/links" %></p>
</li>
</ul>
<% end %>
You must create your own registration controller to do so, here is how:
routes.rb
devise_for :users, controllers: {registrations: 'registrations'}
Controller
You must replace :your_fields by the fields you want to allow (sorry if I leave that to you, but that makes my answer more general, therefore usable for anyone that would pass by)
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
allow = [:email, :your_fields, :password, :password_confirmation]
params.require(resource_name).permit(allow)
end
end
Additional info (nested attributes + some testing)
Also note that if you are using association and accepts_nested_attributes_for you will have params structured like this
model: {field, field, field, associated_model: {field, field}}
And off course you must use the same structure in your sign_up_params method. If you need to understand this, you can change the content of sign_up_params method like this:
def sign_up_params
params.require(resource_name).permit!
end
That will allow any param, then post your form (it should pass this time) and look into your rails console to see the structure of params, finally you can set-up sign_up_params method correctly
Check this for more info http://www.railsexperiments.com/using-strong-parameters-with-nested-forms/
In your case you should use:
params.require(resource_name).permit( :email, :first_name, :last_name, institutions: [:name], :password, :password_confirmation )
Using rails 5.1 and devise 4.4.1 following is the shortest and works pretty good:
app/models/user.rb
after_initialize do
build_profile if new_record? && profile.blank?
end
app/controllers/application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [{ profile_attributes: :name }])
end
The key here is that you can do following without making separate controller:
permit nested attributes
build relation for form builder
in my rails app, I am running into an issue. As a heads up I am using devise.
tracks_controller.rb
def new
#track = Track.new
end
def create
#track = current_user.tracks.build(params[:content])
if #track.save
flash[:success] = "Track created!"
redirect_to #user
else
render 'static_pages/home'
end
users_controller.rb
def show
#user = User.find(params[:id])
#tracks = #user.tracks
if signed_in?
#track = current_user.tracks.build
end
end
I am logged in as a current user, and when I try to add a new track (through the current user) it is not saving.. (and instead redirects to root_url)
track.rb
class Track < ActiveRecord::Base
attr_accessible :content
belongs_to :user
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :username, :email, :password, :password_confirmation, :remember_me
# attr_accessible :title, :body
validates :username, uniqueness: { case_sensitive: false }
has_many :tracks, dependent: :destroy
end
shared/_track_form.html.erb
<%= form_for(#track) do |f| %>
<div class="track_field">
<%= f.text_area :content, placeholder: "Upload a youtube song URL...", :id => "message_area" %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
relavent section for /users/show.html.erb
<div class="span8">
<% if signed_in? %>
<section>
<%= render 'shared/track_form' %>
</section>
<% end %>
I believe the issue is in my TracksController #create method, however I just can't figure it out. any help is greatly appreciated, thanks!
In your controller create action change
#track = current_user.tracks.build(params[:content])
to this
#track = current_user.tracks.build(params[:track])
Since you used form_for(#track) the params hash will contain the :content field filled into the form.
The way you have it now the create action cant find the form :content because there isn't a form named content. content is an attribute of the Track model.