I have a User who has a Profile (2 models). Here is the relevant part of my schema:
create_table "profiles", force: :cascade do |t|
t.text "about"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "provider"
t.string "uid"
t.string "first_name"
t.string "last_name"
t.string "street"
t.integer "house_number"
t.string "city"
t.integer "zip_code"
t.string "image"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
The reason I have a Profile as a separate model, as because I thought it was easier to assign roles later, for certain manipulations. So, now I am wondering, if it is possible to ask for
user.first_name , user.last_name, user.email and user.password
in the registration form and for
user.street, user.house_number, user.city and user.zip_code
in the Profile#new _form. Like this:
<%= form_for([#user, #profile], url: user_profiles_path, method: :post) do |form| %>
<div class="field">
<%= form.label :about %>
<%= form.text_area :about %>
</div>
<div class="field">
<%= form.file_field :avatar %>
<% form.label "Profile photo" %>
</div>
<div class="field">
<%= form.label :street %><br />
<%= form.text_field :street, class: 'form-control' %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
So here you can see, that avatar and about refer to a Profile, while street if from User table. But somehow this form, doesn't undertand this. I allow nested_attributes for :profile, but I guess, this doesn't matter for this form. I know, that maybe the easier way would be, to rearrange my table, so that all the adress attributes are stored in Profile. But as I am new to Rails and I really wish to learn more, I would love to know, if there is a way of saving to both #user and #profile in one form? Thank you!
You're touching on two somewhat different concepts here that most beginners get stumped on.
The first is nested resources. A nested resource has its path nested under another resource.
# config/routes.rb
resources :magazines do
resources :ads
end
So now instead of /ads we have /magazines/:magazine_id/ads. So the routes themselves describe the relation between the two resources in a RESTful way - awesome.
class AdsController < ApplicationController
before_action :set_magazine
# GET /magazines/:magazine_id/ads/new
def new
#ad = #magazine.ads.new
end
# POST /magazines/:magazine_id/ads/new
def create
#ad = #magazine.ads.new(ad_params)
if #ad.save
redirect_to #ad
else
render :new
end
end
def set_magazine
#magazine = Magazine.find(params[:magazine_id])
end
# ...
end
<%= form_for([#ad, #magazine]) do |f| >
# ...
<% end %>
This will let you create ads that belong to a magazine. It will not magically let you create a magazine at the same time as an add in the same form.
That's where nested attributes comes in. It creates a super-powered setter in the model which lets it accept attributes for an associated model and creates / updates the associated records in the same request as the parent.
This for example would let us create a user and a profile in the same form:
class User < ApplicationRecord
has_one :profile
accepts_nested_attributes_for :profile
end
class Profile < ApplicationRecord
belongs_to :user
end
<%= form_for(#user) do |f|>
<div class="field">
<%= f.label :email %>
<%= f.email_field :street, class: 'form-control' %>
</div>
# ...
<%= f.fields_for(:profile) do |profile_fields| %>
<div class="field">
<%= profile_fields.label :about %>
<%= profile_fields.text_area :about %>
</div>
<% end %>
# ...
<% end %>
class UsersController < ApplicationRecord
POST /users
def create
#user = User.new(user_params)
if #user.save
redirect_to :user
else
render :new
end
end
# ...
private
def user_params
params.require(:user)
.permit(:email, ..., profile_attributes: [:about])
end
end
accepts_nested_attributes_for is one of the most misused, misunderstood and hardest concepts to grasp in rails though. If you're just starting out you should consider bypassing this and circling back around once you have a better understanding of rails.
Related
I'm a rails noob that's tryin to do something in his app.
My problem is:
I have a Corso model, a Libro model and an Annuncio model. Corso has_many Libro, Libro has_many Annuncio, Annuncio belongs_to Libro and Libro belongs_to Corso. I want a form where the user can create a Libro (that MUST be associated to a Corso) and meanwhile an Annuncio of that Libro must be created. I'm literally freaking out, I saw hundreds of discussions but nothing solved my problem. Every time I change something I get some different errors.
My focus is:
- how to pass the corso_id (I mean, the objects Corso are already defined in the db) to the new Libro when I submit the form;
-I don't see why a Libro object is created (all fields nil) but an Annuncio object doesn't. It seems like the code #libro.annuncios.build in LibroController/new is useless.
I hope you will help me to get this form working.
LibroController:
class LibroController < ApplicationController
def new
#corso = Corso.find(params[:corso_id])
#libro = #corso.libros.build
#libro.annuncios.build
end
def create
#corso = Corso.find(params[:corso_id])
#libro = #corso.libros.build(libro_params)
if #libro.save
redirect_to #libro
else
redirect_to root_path
end
end
def show
#libro = Libro.find(params[:id])
end
def index
end
private
def libro_params
params.require(:libro).permit(:titolo, :autori, :casa_editrice, :edizione, :anno_pubblicazione, :ISBN, :immagine, :corso_id, annuncios_attributes[:prezzo, :note])
end
end
AnnuncioController:
class AnnuncioController < ApplicationController
def new
#annuncio = Annuncio.new
end
def create
#libro = Libro.find(params[:libro_id])
#annuncio = #libro.annuncio.build(annuncio_params)
if #annuncio.save
redirect_to #libro
else
redirect_to root_path
end
end
private
def annuncio_params
params.require(:annuncio).permit(:prezzo, :note)
end
end
Libro Model:
class Libro < ApplicationRecord
belongs_to :corso, inverse_of: :libros
has_many :annuncios, inverse_of: :libro
accepts_nested_attributes_for :annuncios
end
Annuncio Model:
class Annuncio < ApplicationRecord
belongs_to :utente, inverse_of: :annuncios
belongs_to :libro, optional: true, inverse_of: :annuncios
end
Corso Model:
class Corso < ApplicationRecord
belongs_to :facolta
has_many :libros
validates :nome, uniqueness: true
end
routes.rb (a little messed up)
Rails.application.routes.draw do
get 'facolta/new'
get 'sessions/create'
get 'sessions/destroy'
get 'users/new'
get 'corso/index'
get 'auth/:provider/callback', to: 'sessions#create'
get 'auth/failure', to: redirect('/')
get 'signout', to: 'sessions#destroy', as: 'signout'
resources :sessions, only: [:create, :destroy]
resource :home, only: [:show]
root 'facolta#index'
get 'corso_new' => 'corso#new'
get 'libro_new' => 'libro#new'
get 'about_us' => 'static_pages#about_us'
get 'faq' => 'static_pages#faq'
resources :facolta do
resources :corso
end
resources :corsos, shallow: true do
resources :libro
end
resources :libros do
resources :annuncio
end
resources :user do
resources :annuncio
end
end
views/libro/new
<h1>FORM DI REGISTRAZIONE HERE</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for([#corso, #libro]) do |libro| %>
<p>
<%= libro.label :titolo %>
<%= libro.text_field :titolo %>
</p>
<p>
<%= libro.label :autori %>
<%= libro.text_field :autori %>
</p>
<p>
<%= libro.label :casa_editrice %>
<%= libro.text_field :casa_editrice %>
</p>
<p>
<%= libro.label :edizione %>
<%= libro.text_field :edizione %>
</p>
<p>
<%= libro.label :anno_pubblicazione %>
<%= libro.text_field :anno_pubblicazione %>
</p>
<p>
<%= libro.label :ISBN %>
<%= libro.text_field :ISBN %>
</p>
<p>
<%= libro.label :immagine %>
<%= libro.text_field :immagine %>
</p>
<%= libro.fields_for :annuncios do |annuncio| %>
<p>
<%= annuncio.label :prezzo %>
<%= annuncio.text_field :prezzo %>
</p>
<p>
<%= annuncio.label :note %>
<%= annuncio.text_field :note %>
</p>
<% end %>
<%= libro.hidden_field :corso_id, value: params[:post_id]%>
<%= libro.submit "Inserisci il libro", class: "btn btn-primary" %>
<% end %>
</div>
</div>
schema.rb
create_table "annuncios", force: :cascade do |t|
t.integer "user_id"
t.integer "libro_id"
t.string "prezzo"
t.text "note"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["libro_id"], name: "index_annuncios_on_libro_id"
t.index ["user_id"], name: "index_annuncios_on_user_id"
end
create_table "corsos", force: :cascade do |t|
t.string "nome"
t.integer "facolta_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["facolta_id"], name: "index_corsos_on_facolta_id"
end
create_table "facolta", force: :cascade do |t|
t.string "nome"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "libros", force: :cascade do |t|
t.string "titolo"
t.string "autori"
t.string "casa_editrice"
t.string "edizione"
t.string "anno_pubblicazione"
t.string "ISBN"
t.string "immagine"
t.integer "corso_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["corso_id"], name: "index_libros_on_corso_id"
end
create_table "users", force: :cascade do |t|
t.string "provider"
t.string "uid"
t.string "name"
t.string "oauth_token"
t.datetime "oauth_expires_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email"
end
EDIT: edited with #arieljuod advices
You don't need to merge the corso_id here: #libro = #corso.libros.build(libro_params.merge({corso_id: #corso.id})). ActiveRecord already sets the corso_id since you are doing #corso.libros.build(...).
You also don't need that hidden_field with the Corso id since you already have that from the URL.
You may want to do #libro = #corso.libros.build on your new action too.
And finally, I think this is the main problem: this is wrong belongs_to :corso, inverse_of: :corsos, it should be inverse_of: :libros
Fix the other things I point out too to clean up your code.
EDIT:
to add a select do something like this:
= libro.select :corso_id, options_from_collection_for_select(Corso.all, :id, :name)
I want to add the date in rails form. For example
def create
#land=current_user.lands.build(land_params)
if #land.save
session[:land_id]=#land.id
flash[:success]="Success"
redirect_to lands_path
else
flash[:error]="Fail!!"
render 'new'
end
end
schema.rb
create_table "lands", force: :cascade do |t|
t.date "DateStart"
t.date "DateEnd"
end
new.html.erb
<%= form_for #land do |f| %>
<p>Information</p>
<%= f.label :Start %><br />
<%= f.date_select :DateStart, :default => #DateStart, :order => [:month, :day, :year] %>
<%= f.label :End %><br />
<%= f.date_select :DateEnd,:default => #DateEnd,:order => [:month, :day, :year]%>
<% end %>
show.html.erb
<p>Start : <%= #land.DateEnd %></p>
<p>End : <%= #land.DateEnd %></p>
Land.controller
def show
#land= Land.find(params[:id])
end
But nothing is printed out in my show.html.erb. When I check database my DateStart and DateEnd is nil. I don't know what wrong. Can you give me some advice? Thanks
You can follow like below
def create
#land= Land.new(land_params)
#land.user = current_user
if #land.save
flash[:success] = 'Land was successfully created.'
redirect_to lands_path #=> or anything
else
flash[:error] = 'Land was not created successfully .'
redirect_to lands_path #=> or anything
end
end
I think that's work
You make sure add this before_action :authenticate_user! on your controller header, that's cannot access user without authentication
Hope to help
You can follow my answers -
Method - 1. No association with User and Land model.
lands_controller.rb file look like -
class LandsController < ApplicationController
def new
#DateStart = Date.today
#DateEnd = Date.today + 2.days
#land = Land.new
end
def create
#land = Land.new(land_params)
if #land.save
session[:land_id] = #land.id
flash[:success]= "Success"
redirect_to lands_path
else
flash[:error] = "Fail!!"
render 'new'
end
end
def show
#land= Land.find(params[:id])
end
def land_params
params.require(:land).permit(:DateStart, :DateEnd)
end
end
Database Schema file look like - (schema.rb)
create_table "lands", force: :cascade do |t|
t.date "DateStart"
t.date "DateEnd"
end
Lands controller new action view file - (app/views/lands/new.html.erb)
<%= form_for #land do |f| %>
<p>Information</p>
<%= f.label :Start %><br />
<%= f.date_select :DateStart, :default => #DateStart, :order => [:month, :day, :year] %>
<%= f.label :End %><br />
<%= f.date_select :DateEnd,:default => #DateEnd,:order => [:month, :day, :year]%>
<%= f.submit "Submit" %>
<% end %>
Lands view page (app/views/lands/show.html.erb)
<p>Start : <%= #land.DateEnd %></p>
<p>End : <%= #land.DateEnd %></p>
Method - 2. Association with User and Land model.
lands_controller.rb file look like - (app/controllers/lands_controller.rb)
class LandsController < ApplicationController
before_action :authenticate_user!
def new
#DateStart = Date.today
#DateEnd = Date.today + 2.days
#land = current_user.lands.build
end
def create
#land = current_user.lands.build(land_params)
if #land.save
session[:land_id] = #land.id
flash[:success]= "Success"
redirect_to lands_path
else
flash[:error] = "Fail!!"
render 'new'
end
end
def show
#land= Land.find(params[:id])
end
def land_params
params.require(:land).permit(:DateStart, :DateEnd)
end
end
Database Schema file look like - (schema.rb)
create_table "lands", force: :cascade do |t|
t.date "DateStart"
t.date "DateEnd"
t.index ["user_id"], name: "index_rooms_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
User model look like - (app/models/user.rb)
class User < ApplicationRecord
has_many :lands
end
Land model look like - (app/models/land.rb)
class Land < ApplicationRecord
belongs_to :user
end
Lands controller new action view file - (app/views/lands/new.html.erb)
<%= form_for #land do |f| %>
<p>Information</p>
<%= f.label :Start %><br />
<%= f.date_select :DateStart, :default => #DateStart, :order => [:month, :day, :year] %>
<%= f.label :End %><br />
<%= f.date_select :DateEnd,:default => #DateEnd,:order => [:month, :day, :year]%>
<%= f.submit "Submit" %>
<% end %>
Lands view page (app/views/lands/show.html.erb)
<p>Start : <%= #land.DateEnd %></p>
<p>End : <%= #land.DateEnd %></p>
I hope it should work.
I have two models: project and todo. Project has many todos.
So I wanna create a form, where I select project category from the combobox and then I add a todo to it.
For instance:
I have following categories: family, work, study.
In form in the combobox I select 'study', and then in textfield I spell a todo like 'make homework for monday' and press submit button.
project.rb
class Project < ActiveRecord::Base
has_many :todos
end
todo.rb
class Todo < ActiveRecord::Base
belongs_to :project
end
my data schema:
create_table "projects", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "todos", force: :cascade do |t|
t.string "text"
t.boolean "isCompleted"
t.integer "project_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
_form.html.erb
<%= form_for #project do |f| %>
<div class="form_control">
<%= f.select :title, options_for_select([["Work", "w"],
["Family", "f"],
["Study", "f"],
["TheRest", "t"]]) %>
</div>
<div class="form_control">
*** HERE I NEED TO FIGURE OUT HOW TO ADD SOME DATA TO todo.text ***
</div>
<div class="form_control">
<%= f.submit 'Add' %>
</div>
<% end %>
this is how I show all the projects with their todos:
<% #projects.each do |project| %>
<h2> <%= project.title %> </h2>
<% project.todos.all.each do |todo| %>
<p><%= todo.text %> <%= check_box('tag', todo.__id__, {checked: todo.isCompleted}) %></p>
<% end %>
<% end %>
GitHub link : https://github.com/NanoBreaker/taskmanager
In your todo form, you could have a select box to choose the project the todo belongs to:
# todos/_todo_form.html.erb
<%= select_tag "project_id", options_for_select(Project.pluck(:title, :id)) %>
And in your todos_controller create action:
def create
#project = Project.find(params[:project_id])
#todo = #project.todos.new(todo_params)
if #todo.save
# success
else
# error
end
end
finally, permit the project_id in todo_params:
def todo_params
params.require(:todo).permit(:text, :project_id) # add any other attributes you want
end
I'm trying to debug a controller that just won't play ball. I've narrowed it down to the strong parameters not working properly and so I've tried it out on the rails console.
params = ActionController::Parameters.new({
"user"=> {
"login"=>"username",
"password"=>"[FILTERED]"
},
"staff_id"=>"1"
})
This returns, as you'd expect:
=> {"user"=>{"login"=>"username", "password"=>"[FILTERED]"}, "staff_id"=>"1"}
So, I attempted to filter the parameters, like so...
params.require(:staff_id)
=> "1"
> params.require(:user).permit(:password,:login)
=> {"password"=>"[FILTERED]", "login"=>"username"}
That looks ok.
In my controller, I have:
def create
#staff=Staff.find(params[:staff_id])
#user = #staff.create_user(reg_params[:user])
DISASTER
Now, at the point of the disaster, the user object #user should have a login and password set but It doesn't. I've tried logging the #user object at that point, it's properties are nil.
Why isn't my user object being created properly? If I remove the DISASTER, it creates a database record with blank fields, except for the timestamps.
Models:
class Staff < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :staff
end
Schema:
ActiveRecord::Schema.define(version: 20150909102012) do
create_table "staff", force: :cascade do |t|
t.string "qualifications"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
add_index "staff", ["users_id"], name: "index_staff_on_users_id"
create_table "users", force: :cascade do |t|
t.string "login"
t.string "username"
t.string "password"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
It routes to the custom controller ok. Here's the route:
Rails.application.routes.draw do
resources :staff do
resource :user, shallow: true, controller: 'staff_register'
end
resources :users
There's nothing else in the app because it's just an experiment to help me work on a bigger problem I've been struggling with.
Basically, I want to be able to link a staff model to a user model. I think I've done that ok, I just need help figuring out the strong parameters bit.
EDIT: here's the form for the nested resource:
<%= form_for #user, :url => staff_user_path(#staff) do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :login %><br>
<%= f.text_field :login %>
</div>
<div class="field">
<%= f.label :password %><br>
<%= f.text_field :password %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In this line:
#user = #staff.create_user(reg_params[:user])
reg_params already gives you hash with all the permitted parameters:
{"password"=>"[FILTERED]", "login"=>"username"}
There is no user key there, hence reg_params[:user] is just nil. Instead you need to do:
#user = #staff.create_user(reg_params)
Now, you do not need to worry about staff_id here, as you are executing create_user method on already existing #staff model. This method will take care of the association.
I have three models: User, Post and, Reply. An user has many posts and comments. A post had many replies and belongs to an user, and a reply belongs to a post and an user.
routes.rb:
resources :posts do
resources :replies
end
schema.rb:
create_table "posts", :force => true do |t|
t.text "content", :limit => 255
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "title"
end
create_table "replies", :force => true do |t|
t.text "content"
t.integer "post_id"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
This is how I'm creating comments:
comments_controller.rb:
def create
#post = Post.find(params[:post_id])
#reply = #post.replies.build(params[:reply])
if #reply.save!
flash[:success] = "reply created!"
redirect_to post_path(#post)
else
redirect_to post_path(#post)
end
end
replies/_form.html.erb:
<%= form_for([#post, #post.replies.build]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Enter reply content" %>
</div>
<%= f.submit "Reply", class: "btn btn-large btn-primary" %>
<% end %>
After submitting the form I get this error:
Validation failed: User can't be blank
I assume its because the reply's attribute user_id is empty:
reply.rb
validates :user_id, presence: true
I'm not sure how to fill that attribute. I can't just put it in Reply attr_accesible, because that would compromise the security of the app (as far as I know).
Any suggestions to solve this?
attr_acessible only affects things when you're updating/creating a record from a hash of attributes. You can always set an attribute by calling the accessor directly, so after you've built the reply,
#reply.user = current_user
Should do the trick (assuming you're using something like devise or authlogic that defined current_user for you. You could also assign to #reply.user_id directly.