I'm building a nested model form that will allow a client to register (client model) and create a login (user model) at the same time. However, for new registrants, I would like to set the user.role_code (say to "A" for admin), but I've been struggling with this. From a data model perspective, and client has multiple users, since after registration, the client can access the app to add other users. From that perpective, I don't think I can default user_role in the model, since for added users, their role_code may be different. I would like to set the default in the controller.
Also, after form submission, users are sent as an array, so I would like to ensure only ONE user array object is sent upon registration (clearly for vulnerability reasons).
Here's my code
#client registration form new.html
<%= form_for #client do |client_form| %>
.....
<%= client_form.fields_for :users do |user_form| %>
<div class="field">
<%= user_form.label :first_name %>
<%= user_form.text_field :first_name %>
</div>
.....
#client Controller
def create
logger.info params.inspect
#client = Client.new(params[:client])
if #client.save
......
#client model
class Client < ActiveRecord::Base
attr_accessible :business_name, ... #and more
attr_accessible :users_attributes
has_many :users
has_many :items
accepts_nested_attributes_for :users, allow_destroy: true
#logger.info params.inspect
"...users_attributes"=>{"0"=>{"first_name"=>..."
Found it!!! After much researching, Googling, pulling out my hair, and lastly coming across Get nested params, the answer is as follows:
#client.users.first.role_code = "A"
and to test the number of user array objects, use the following
if #client.users.size != 1
...
end
Related
I have two models, User, and Product. Product belongs to User, User has many Products.
When a Product is created I also want to update multiple fields in the User model. I've been developing with Ruby for like 2 years now and still don't understand forms fully when it comes to this stuff. I'm still getting permitted: false. Now I know that for instance if I was creating a user while also creating a product I would just do #product.user.build but in this case I just want to update an already existing record.
I also realize that I probably can't call f.fields_for :user as #product doesn't know about user yet. In my head I believe I should be able to just pass additional params to the form, grab the current_user in the product#create action and then update the attributes manually by calling update_attributes on user.
product.rb
accepts_nested_attributes_for :user
product controller
def new
#product = Product.new
end
params.require(:product).permit(:product_name, user_attributes: [:phone_number, :email_address])
product view
form_for #product do |f|
f.fields_for :user do |c|
c.text_field :phone_number
c.text_field :email_address
f.text_field :product_name
end
I also realize that I probably can't call f.fields_for :user as #product doesn't know about user yet.
You can assign attributes to #product without saving it.
def new
#product = Product.new(
user: current_user
)
end
Now #product.user works.
I have this structure in my view:
<%= simple_form_for #user ... %>
...
...
<%= render '/hobbies/form', :user => #user %>
...
...
<% end %>
The render part loads data from a partial - there's a form. The form-fields are successfully loaded (like inputs, submit input, radio buttons etc), but when I take a look at the generated HTML, in the rendered form is missing <%= form_for... and the closing tag.
Which means when I click to the submit button from the rendered form, this rendered form is not sent out, but instead of it is sent out the "big" for - simple_form_for.
How to make the rendered form sendable?
Thank you
Simply, you can't have two separate nested forms.
You can have nested associated forms using accepts_nested_attributes_for - this is dependent on the structure of your models in the backend, which I'll detail below.
--
Forms
The most important thing you need to know is that Rails just builds HTML forms - which means they have to abide by all the constraints & rules these forms stipulate:
Each form has its own action attribute - which is how your browser knows where to send the data. If you have multiple / nested forms, you'll basically have two of these "action" attributes; preventing HTML from sending the form correctly
--
Nested
If you want to use some Rails conventions, you'll be better using the accepts_nested_attributes_for method. This resides in the model, and in order to use it effectively, you need to ensure you have your ActiveRecord associations set up correctly:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :hobbies
accepts_nested_attributes_for :hobbies
end
#app/models/hobby.rb
Class Hobbies < ActiveRecord::Base
belongs_to :user
end
The importance of the association is crucial - Rails will pass "nested" model data through to each other. The nested model data is only there if the models are associated correctly with ActiveRecord (as above)
You can then pass the data through your controller / models as follows:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def new
#user = User.new
#user.hobbies.build # -> essential for nested attributes
end
def create
#user = User.new(user_params)
#user.save #-> notice how this saves the #user model only?
end
private
def user_params
params.require(:user).permit(:user, :attributes, hobbies_attributes: [:hobbies, :attributes])
end
end
This will allow you to include the hobbies part of the form as follows:
<%= form_for #user do |f| %>
<%= f.fields_for :hobbies do |h| %>
<%= h.text_field :your_attributes %>
<% end %>
<% end %>
Nested forms is not valid html. To achieve this you have to either un-nest your forms or use javascript. If you opt for the javascript approach, you simple catch the form submit event, prevent the default, and manually submit the form you want.
EDIT:
If you opt for the javascript approach, consider that you might not need two forms at all. Instead you can just add a click handler for the button on your 'sub action' which makes an ajax request.
Alternatively, if javascript is not an option, and rearranging the html is not an option, you could handle this on the server by submitting the data from both forms and processing each differently depending on which submit input was clicked.
You can't have a form inside a form. If you have your associations correctly setup somthing like
class User < ActiveRecord::Base
has_many :hobbies
accepts_nested_attributes_for :hobbies
end
class Hobby < ActiveRecord::Base
belongs_to :user
end
then you can use fields_for in your form like
<%= form_for #user do |u| %>
// user fields
<%= u.fields_for :hobbies do |h| %>
// hobbies fields
<% end %>
<%= f.submit %>
<% end %>
This will give you params like
users_attributes: { name: "xyz", hobbies_attributes: { attribute: "value"} }
For details checkout accept_nested_attributes_for
I have a comment model and when a comment is created it makes a note of the profile_name of the user and saves it. So it is basically saving #user.profile_name as comment.profile_name.
If I now want to show additional information from the user who has that profile_name such as #user.avatar - how would I query it without having to add extra fields to be saved when a comment is created?
I Imagined I could do something like
#user = User.all
#comment_user = User.where(:profile_name => #user.profile_name)
And then run
<% #comment_user.each do |user| %>
<%= user.first_name %>
<% end %>
In the view but I get an error
undefined method `profile_name' for #<ActiveRecord::Relation::ActiveRecord_Relation_User:0x007fc2081c7290>
I'm not even sure if that is the correct way to proceed even if I didn't get the error.
The error is because you are trying to call profile_name on User.all, but what you want is a single user. Try:
#user = User.first
#comment_user = User.where(:profile_name => #user.profile_name)
That being said, you should be using ActiveRecord associations instead. Instead of storing the user's profile_name in the comment record, you should store the user_id . Create a belongs_to :user association in the Comment model and then you can access the other user attributes directly.
First add a :user_id integer column to the the comments table, and then define your associations:
class Comment < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
end
When creating a new comment, you can do something like:
#comment = #user.comments.create(text: 'my awesome comment')
Then in your view:
<%= #comment.text %>
<%= #comment.user.profile_name %>
This is for Rails 4.04 and Ruby 2.1. I'd like to allow my users to have addresses. So I generated an address table and gave it columns (NUMBER, STREET, CITY, STATE). Now when I go to the following url, I'd like to be able edit this information:
webapp.com/users/edit/
However I noticed it only showed the same old information (name, password, email). So I went to the view and added simple_fields for my new relationship so the view now looks like this:
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<div class="form-inputs">
<%= f.input :email, required: true, autofocus: true %>
<%= f.input :name, required: false %>
<%= f.simple_fields_for :addresses do |a| %>
<%= a.input :number %>
<%= a.input :street %>
<%= a.input :city %>
<%= a.input :state %>
<%= a.input :country %>
<% end %>
<%end%>
However it still doesn't generate the fields needed for address. I think this is because none of my users currently have any addresses attached to their account profile (because this migration was just created). However, in this case there should be blank fields generated so I can ADD address information.
I feel like I need to do something in the Users#Edit action like this
#users.each do |user|
user.address.build
end
Is that right? How can I override the users controller because this was created by Devise and I don't actually have a users controller (I looked for it it and couldn't find it).
UPDATE
Ok, I'm getting closer. I had to create my own controller to override Devise's default registrations controller as explained in the second answer of this stack overflow article:
Override devise registrations controller
So now I am getting into that controller which currently looks like this:
class Users::RegistrationsController < Devise::RegistrationsController
def edit
super
end
end
However, when I get to my view, it's still SKIPPING the block that starts like this:
<%= f.simple_fields_for :addresses do |a| %>
However, if I go manually into my DB and add a record in the addresses table and link it to my currently_signed in user via the foreign key, then the block does not get skipped. So whats the best way to generate this connection if the address record does not yet exist? Is it the build method? e.g.
user.address.build
in the controller
SOLUTION
Yes, I needed to added this method to my new registrations_controller.rb file
def edit
if resource.addresses.size == 0
resource.addresses.build
end
super
end
It is now working the way I intended it.
You need to do it like this:
#app/models/user.rb
Class User < ActiveRecord::Base
has_one :address
before_create :build_address, unless: Proc {|x| x.address.present? } #-> not sure about the if statement
end
#app/models/address.rb
Class Address < ActiveRecord::Base
belongs_to :user
end
--
Devise does come with Controllers
These controllers are not shown in your app (they are installed with the Devise gem, but only visible in production):
- confirmations_controller.rb
- omniauth_callbacks_controller.rb
- passwords_controller.rb
- registrations_controller.rb
- sessions_controller.rb
- unlocks_controller.rb
--
You Don't Need Them
Whilst you can override these controllers, you won't need to, as your edit action will be tied to the users controller:
#config/routes.rb
resources :users
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def edit
#user = User.find params[:id]
end
end
#app/views/users/edit.html.erb
<%= form_for #user do |f| %>
...
<%= f.fields_for :address do |a| %>
<% end %>
<% end %>
Update
Sorry for not explaining. You can use resources :users with devise_for :users:
#config/routes.rb
devise_for :users
resources :users, only: [:edit, :update]
If this does not work, you may need to change the path_names argument for devise, like so:
#config/routes.rb
devise_for :users, path_names: { sign_in: 'login', password: 'forgot', confirmation: 'confirm', unlock: 'unblock', sign_up: 'register', sign_out: 'logout'}
-
Form
The form can have resource, but I think you need to work out what you're trying to change. If you're trying to change a devise object, then use the resource helper; but if you're trying to change the User model directly - I'd be partial to changing that!
The issue I think you have is if you're using resource, it's going to route to Devise controller actions. I would try setting #user in the edit action of your users controller, and use the conventional way to update
it looks like you didn't made the relation between those models add to addresses column called user_id and add to user model:
has_many :addresses
and into addresses:
belongs_to :users
then in your view you can build this form using nested attributes see this post:
Rails 4 Nested Attributes Unpermitted Parameters
another option that you can do is to show that addresses form after the user already signed in then when he update the form find the user_id with current_user and build the record using this id but using strong params in Rails 4 is recommended to solve your issue.
I have a classified model that is hook up to the student model in a way that it looks like this
classified.rb
belongs_to :student
in my student model it
has_many :classifieds
The classifieds table contain a column for student_id
when I head to the rails console
I can create a new classified for that particular student record by doing
Student.find(19).classifieds.create(:ad_title => "blah", :ad_content => "blah", :location => "blah")
The record automatically gets a student_id generated because it was created in the student standpoint.
Now the problem is I have a classifieds controller with a new and a create method and a new form in the view
I am creating a new ad in the classifieds standpoint here is the form
<div>
新广告: <br><br>
<%= simple_form_for #advertisement do |f| %>
<%= f.input :ad_title, label: "Title" %><br>
<%= f.input :ad_content, label: "Content"%><br>
<%= f.input :location, label: "Location"%><br>
<%= f.input :student_id, label: "Your Student ID"%><br>
<%= f.button :submit, "Add an Advertisement" %>
<% end %>
</div>
The problem is the student_id must exist in order for that particular record to show up in index and my users don't know their id.
How do I create a classified in the student standpoint using forms
I consider going to my students controller and adding a method like this
def create_classified_ad
#student = Student.find(params[:id])
#classified = #student.classified.create(params[:classified])
end
I am using devise so there is user session? I don't know how sessions work entirely I want that particular student to be found after logged in and have that student create a classified ad with the inputs from the form so the record will be created correctly?
The question is more like how do you insert data using forms to a hooked up table correctly? (and not merely inserting data into a simple no relationship table)
You should not be passing the student_id from the form. If you have a devise session, you should have access to current_user:
def new
#classified = current_user.classifieds.build
end
def create
#classified = current_user.classifieds.build(params[:classified])
if #classified.save
# do something
else
# handle failure
end
end
In the index, you can do this:
def index
#classifieds = current_user.classifieds.all
end
All of this assumes you have a devise session. If not, just fetch the student like so:
#student = Student.find(params[:student_id])
#classified = #student.classifieds.build
Set up a nested route for the classifieds under the user:
resources :users do
resources :classifieds
end
This allows you to fetch the student from a student_id param. Your route would like this: new_student_classified_path(#student).