updating user record with devise that has_one relationship - ruby-on-rails-3

Im using devise to handle my user authentication and in my user model Ive stated that each user has_one :role.
Im using another table to hold all my user roles/permissions and I was wondering how to update the role?
EDIT - here is my user model
has_one :role, :dependent => :destroy
accepts_nested_attributes_for :role, :allow_destroy => true
attr_accessible :stuff.... :role
My role model
belongs_to :user
Ive added this to my form:
<%= f.fields_for :role, Role.new do |r| %>
<li class="full_width">
<%= r.label "User type" %>
<%= r.select(:status, %w[member artist commercial],{:include_blank => false}) %>
</li>
<% end %>
but it never saves the role record, I guess its because the user model didnt have attr_accessible :role so I set that up and now when I try to save I get a AssociationTypeMismatch error
EDIT - added the accepts_attributes_for and now I dont get the error but the role record isnt saved. Console shows
WARNING: Can't mass-assign protected attributes: role_attributes

See http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_accessible. You have to declare
attr_accessible :role_attributes

From the code snippet that you have pasted, it's not clear where you are building the association between the new Role and the User. You may need to do something like #user.build_role(...) or #role.build_user(...) in order to associate the user and the role prior to saving.

Related

How to create new records with nested attributes in a ruby on rails "has many through" relationship?

I need some advice on building a has many through relationship between USER, THING and EXTRA models.
My USER model is slightly modified inside Devise gem and is noted as Creator whereas other models belonging to USER receive :created_things form.
In my app, USERS create THINGS can later add EXTRAS to their THINGS.
I chose has many through because I want to have unique data on all three models and be able to call both THINGS and EXTRAS from the USER "CREATOR" model.
I have built this many different ways and after 10 years of solving my problems by reading stackoverflow, I am finally submitting this request for support! Thank you for your help.
I have tried creating user and extra references on the THING model and declaring nested attributes in the USER and THING model. I have tried several examples from stackoverflow inside the create and new methods but nothing seems to work.
class User < ApplicationRecord
has_many :created_things, class_name: Thing, foreign_key:
:creator_id, :dependent => :destroy
has_many :extras, through: :created_things
accepts_nested_attributes_for :extras, :reject_if => :all_blank,
allow_destroy: true
class Thing < ApplicationRecord
belongs_to :creator, class_name: User
has_many :extras
accepts_nested_attributes_for :extras, :reject_if => :all_blank,
allow_destroy: true
class Extra < ApplicationRecord
belongs_to :creator, class_name: User, inverse_of: :thing
belongs_to :created_things
Members Index.html.erb
<% if thing.extras.exists? %>
<% thing.extras.each do |extra| %>
<%= extra.title %> <%= link_to "[+]", edit_extra_path(extra) %>
<% end %>
<% else if thing.extras.empty? %>
<%= link_to "+1 EXTRA", new_extra_path(current_user) %>
<% end %>
<% end %>
class MembersController < ApplicationController
before_action :authenticate_user!
def index
#user = current_user
#created_extras = #user.extras
#created_things = #user.created_things
end
class ExtrasController < ApplicationController
def new
#extra = Extra.new
end
def create
#extra = current_user.extras.build(extra_params)
if #extra.save
I am able to create a new EXTRA but the :thing_id remains nul as it does not display when called on the show extra view. Therefore I am not surprised that when I return to the member index page that my thing.extras.exists? call is returning false and the created extra never displays under the THING view. My attempts to modify the extra controller have failed and I some of my reading sugested the extras controller is not necessary in this relationship so I am really at a loss on how this is built. I'm assuming I am missing something in new and create methods maybe in things or user controller? Perhaps I'm missing something in routes resources? Any advice is greatly appreciated. Thank you.
Ok, I figured it out. I really didn't need has many through for this model and I did a lot of testing of the syntax on each model.rb and in the end was able to figure it out from this stackoverflow . . .
[Passing parent model's id to child's new and create action on rails
Here are my the various parts of setting up a has many and belongs to relationship with nested attributes.
class Thing < ApplicationRecord
belongs_to :creator, class_name: User
has_many :extras, inverse_of: :thing, :dependent => :destroy
accepts_nested_attributes_for :extras, allow_destroy: true
class Extra < ApplicationRecord
belongs_to :thing, inverse_of: :extras
extras_controller.rb
class ExtrasController < ApplicationController
def new
#extra = Extra.new(thing_id: params[:thing_id])
end
def create
#user = current_user
#extra = Extra.new(extra_params)
#extra.user_id = #user.id
if #extra.save
flash[:success] = "You have added a new Extra!"
redirect_to #extra #extras_path later
else
flash[:danger] = "The form contains errors"
render :new
end
end
edit.html.erb things
<% if #thing.extras.exists? %>
<p>current extras associated with <%= #thing.title %>: </p>
<% #thing.extras.each do |extra| %>
<p><%= extra.title %> <%= link_to "[+]", edit_extra_path(extra) %>
/ <%= link_to "[-]", extra_path(extra), method: :delete %> </p>
<% end %>
<% end %>
<%= link_to "+1 EXTRA", new_extra_path(thing_id: #thing.id) %>
<%= render 'things/form' %>

Rails scoped form not assigning belongs_to

I have two models in Rails 3 - a User model and a Profile model.
class User < ActiveRecord::Base
has_one :profile, :dependent => :destroy
end
class Profile < ActiveRecord::Base
belongs_to :user
end
They are scoped in my routes.rb file, as such:
resources :users do
resources :profiles
end
So now, my form to create a profile reads like this (Using SimpleForm):
<%= simple_form_for([#user, #profile]) do |f| %>
<%= f.error_notification %>
...(Other Inputs)
<% end %>
However, the user ID doesn't seem to be automatically sent to the profile model as I had assumed. Do I have to set that manually through the controller? Or am I missing something?
You should start by making sure that the relationship between User and Profile is indeed working correctly. You've actually put "has_one :user" in your User model, when I think you mean:
class User < ActiveRecord::Base
has_one :profile, :dependent => :destroy
end
In order to send the user ID with the form, the form should be on a page with a URL of something like "localhost:3000/users/5/profiles/new" which you can link to with the helper "new_user_profile_path(5)", for a user with ID 5.
When you submit the form, it will hit the create action in your ProfilesController. The following should result in the creation of the profile:
def create
#user = User.find(params[:user_id])
#profile = #user.build_profile(params[:profile])
#profile.save!
end
add :method => :post to your form since ur html request is GET which should be POST
simple_form_for([#user, #profile], :method => :post) do |f| %>

a two-dimensional array as a params value

I just finished Ruby on Rails 3 Tutorial. The final chapter is quite complex. The tutorial throughout the book basically creates a site where Users can post Microposts. Also, each User can Follow any other User and then the following User's Microposts will show up on the original User's Micropost feed.
My question has to do with why the create action inside the RelationshipsController has the params variable contain a two-dimensional array.
Here's the code.
User
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
has_many :microposts, dependent: :destroy
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship", dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
end
Micropost
class Micropost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
end
Relationship
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
end
I think this is the code that creates the two-dimensional params variable (but why?)
<%= form_for(current_user.relationships.build(followed_id: #user.id), remote: true) do
|f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
RelationshipsController
class RelationshipsController < ApplicationController
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
So maybe I just answered my own question, but I've never seen a two-dimensional array for a params variable. Can someone maybe shed some light on this?
oh, maybe I should post my routes.rb file as well:
SampleApp::Application.routes.draw do
resources :users do
member do
get :following, :followers
end
end
resources :sessions, only: [:new, :create, :destroy]
resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
root to: 'static_pages#home'
end
thanks,
mike
Short Answer:
This is not a 2-dimensional array, it is a nested associative array. The reason you use it is to get at the field that you actually want.
Long Aswer:
Assumption from tutorial: When a user clicks the follow button, the goal is to call current_user.follow!(other_user). I'll walk you through how the code is achieving this. All of the magic is in the Relationship Controller and the form_for function in the view.
First, you make a form for a new relationship. Because it is a nested resource, you build it through the association.
current_user.relationships.build
But a brand new Relationship object that only corresponds to one user doesn't mean much. Instead, pass in an associative array of values to initialize the object to. In this case, the other user's id. So you assign the :followed_id attribute of the Relationship object you are building to #user.id, or the user you are trying to follow.
current_user.relationships.build(followed_id: #user.id)
When you form_for on an object, you can access the attributes of the object. In this case if we look at the Relationship model, only :followed_id is accessible.
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
Finally, we need to capture the followed_id in the form submission because the goal of the form is to be able to call current_user.follow!(other_user) when the follow button is clicked. So we pass in the followed_id as a hidden field so it is accessible in params in the controller, but also the user does not see it in the view itself.
<%= f.hidden_field :followed_id %>
Finally, when the button is clicked, because the form is for a new Relationship object, the create action is called for the Relationship controller. In there, to access the relationship object corresponding to the form you do it the same way as other forms in the tutorial -
params[:relationship]
But you don't want the relationship object, you want the user object of the one to follow so you can call follow!. This is easy. Just find the user in the database from the id. How to get the followed_id? It is an attribute of the Relationship object from the form.
params[:relationship][:followed_id]
I think its worth noting that when you make a new user object, you use params[:user]. This is just an associative array and you could access fields of it if you wanted to like
params[:user][:name]
Hopefully that made sense. It is just an nested associative array Rails uses to keep track of parameters such as those from submitting a form.

Rails 3 current user id to comments?

So Im working on a rails app where users can comment on photos or videos another user has uploaded and so far everything is great except I am not able to get the current user_id associated with the person who has commented on the post. This is what I have so far.
user.rb
has_many :comments, :dependent => :destroy
photo.rb
has_many :comments, :as => :commentable
video.rb
has_many :comments, :as => :commentable
comments_controller.rb
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
redirect_to :id => nil, :notice => "Successfully created comment."
else
render :action => 'new'
end
end
How can I get the user id to appear with the current comments? I have the comment type and comment id I am just looking for a way to have it so the user_id can appear. Any suggestions?
You should add a hidden_field to your form partial where you store the current_user.id
something like:
<%= f.hidden_field :user_id, :value => current_user.id %>
of course you should have a field user_id in your comment model, as a comment belongs_to user and a user has_many comments.
update:
what ofca pointed out, this can approach can lead to security issues as the hidden field could be modified by the user in the browser, e.g. using firebug.
In this case it is probably better to to leave out this field in the view and create the comment in the controller by using
<%= current_user.comments.create(params[:comment]) %>
The way you have it now, it is only set up one way.
Plus you have to make it polymorphic
try adding:
comment.rb
belongs_to :user
belongs_to :commentable, :polymorphic => true

Does accepts_nested_attributes_for work with belongs_to?

I have been getting all kinds of conflicting information regarding this basic question, and the answer is pretty crucial to my current problems. So, very simply, in Rails 3, is it allowed or not allowed to use accepts_nested_attributes_for with a belongs_to relationship?
class User < ActiveRecord::Base
belongs_to :organization
accepts_nested_attributes_for :organization
end
class Organization < ActiveRecord::Base
has_many :users
end
In a view:
= form_for #user do |f|
f.label :name, "Name"
f.input :name
= f.fields_for :organization do |o|
o.label :city, "City"
o.input :city
f.submit "Submit"
Nested attributes appear to work fine for a belongs_to association as of Rails 4. It might have been changed in an earlier version of Rails, but I tested in 4.0.4 and it definitely works as expected.
The doc epochwolf cited states in the first line "Nested attributes allow you to save attributes on associated records through the parent." (my emphasis).
You might be interested in this other SO question which is along the same lines as this one. It describes two possible solutions: 1) moving the accepts_nested_attributes to the other side of the relationship (in this case, Organization), or 2) using the build method to build the Organization in the User before rendering the form.
I also found a gist that describes a potential solution for using accepts_nested_attributes with a belongs_to relationship if you're willing to deal with a little extra code. This uses the build method as well.
For belongs_to association in Rails 3.2, nested model needs the following two steps:
(1) Add new attr_accessible to your child-model (User model).
accepts_nested_attributes_for :organization
attr_accessible :organization_attributes
(2) Add #user.build_organization to your child-controller (User controller) in order to create column organization.
def new
#user = User.new
#user.build_organization
end
For Ruby on Rails 5.2.1
class User < ActiveRecord::Base
belongs_to :organization
accepts_nested_attributes_for :organization
end
class Organization < ActiveRecord::Base
has_many :users
end
Just got to your controller, suppose to be "users_controller.rb":
Class UsersController < ApplicationController
def new
#user = User.new
#user.build_organization
end
end
And the view just as Nick did:
= form_for #user do |f|
f.label :name, "Name"
f.input :name
= f.fields_for :organization do |o|
o.label :city, "City"
o.input :city
f.submit "Submit"
At end we see that #user3551164 have already solved, but now (Ruby on Rails 5.2.1) we don't need the attr_accessible :organization_attributes