Authlogic with nested attributes and polymorphic associations - ruby-on-rails

I'm having trouble with the following code:
User < AR
acts_as_authentic
belongs_to :owner, :polymorphic => true
end
Worker < AR
has_one :user, :as => :owner
accepts_nested_attributes_for :user
end
Employer < AR
has_one :user, :as => :owner
accepts_nested_attributes_for :user
end
I'd like to create registration forms based on user types, and to include authentication fields such as username and password. I currently do this:
UserRegistrationController < AC
#i.e. a new Employer
def new
#employer = Employer.new
#employer.build_user
end
...
end
I then include User fields with fields_for. All views render fine, but here's the catch: I cannot build a User, it tells me :password is a wrong method, so I guess the authentication logic has been bypassed.
What should I do? Am I doing it wrong altogether? Should I drop polymorphic associations in favor of Single Table Inheritance? Whatever I do, I have to make sure it plays nicely with Authlogic.

I'd approach the building of new users of either type in the opposite direction. ie:
#controller
#employer = Employer.new
#user = #employer.build_user
#view
form_for #user |f|
f.text_field :login
f.password_field :password
fields_for :owner, #employer |f_e|
f_e.some_field :some_value
#controller
def create
#owner = params[:owner][:some_employer_field_or_virtual_attribute] ? Employer.new params[:owner] : Worker.new params[:owner]
#owner.save
#user = User.new(params[:user].merge!(:owner => #owner)
if #user.save
...
re. mentioned virtual attribute - if there's no field in the model, and thus in the form, which distinguishes user type as employer or worker then set an virtual attribute within each which you can put as a hidden boolean field in the form

Related

Rails Avoid Duplicate Records on Join Table has_many to has_many

We have the regular model of a user (user.rb):
Model:
class User < ApplicationRecord
has_many :clientes, through: :clientes_users
end
And a simple model for clients (cliente.rb):
Model:
class Cliente < ApplicationRecord
has_many :clientes_users
has_many :users, through: :clientes_users
end
We created the join table (many to many) (clientes_user.rb):
class ClientesUser < ApplicationRecord
belongs_to :cliente
belongs_to :user
end
When we create the user we use:
#user = User.new(
:email => params[:email],
:access_level => params[:access_level],
:password => params[:password_first],
:password_confirmation => params[:password_confirmation]
)
params[:cliente_id].each do |cliente|
#user.clientes_users.build(
:cliente => Cliente.find(cliente)
)
end
#user.save
All works fine and our table store the data:
But when we update the relations, the record dupliate the entries.
What's the best way to avoid that behavior, if the relations doesn't exist insert the data but if exist just ignore
Defining a has_many association also gives you setters and getters. #user should have methods cliente_ids and clientes_ids=, that can be used for assignment. You can do the following:
#user = User.create!(
email: params[:email],
access_level: params[:access_level],
password: params[:password_first],
password_confirmation: params[:password_confirmation],
cliente_ids: params[:cliente_id]
)
Also note that the mismatches between password and password_first, as well as between cliente_ids and cliente_id are misleading, it would be nice to have the same name in both cases.
When param names are the same as the model attributes, you can then go one step further and do that:
#user = User.create!(user_params)
def user_params
params.require(:user).permit(:email, :access_level, :password, :password_confirmation, :cliente_ids)
end
Remove require(:user) if the controller params are not scoped under the key user, even though they probably should.
Hope this helped.

Rails - Polymorphic Association to Create Different Profiles

Attempting to make it so that when a user is created, based on whether they select to be a student or a corporate, rails will create that user either a student profile or a corporate profile.
Ive tried to set it up using Polymorphic associations however cant figure out how to generate the profile at the model layer based on what is selected in the view.
Models
class User < ActiveRecord::Base
has_secure_password
has_one :student_profile, dependent: :destroy
has_one :corporate_profile, dependent: :destroy
has_many :searches, dependent: :destroy
#attr_accessor :profile_type - removed due to Rails 4, pushed strong params in controller
before_create :create_profile
def create_profile
if profile_type == 1
build_student_profile
else
build_corporate_profile
end
end
end
Student and Corporate Profiles
class CorporateProfile < ActiveRecord::Base # or possibly inherit from ActiveRecord::Base if not using inheritance
belongs_to :user
end
class StudentProfile < ActiveRecord::Base # or possibly inherit from ActiveRecord::Base if not using inheritance
belongs_to :user
end
View
Here i have two radio buttons to decide which user type on the sign up form
<%= bootstrap_form_for(#user) do |f| %>
<div class="field">
<%= f.form_group :gender, label: { text: "Gender" }, help: "Are you a corporate or a student?" do %>
<p></p>
<%= f.radio_button :profileable, 1, label: "Student", inline: true %>
<%= f.radio_button :profileable, 2, label: "Corporate", inline: true %>
<% end %>
</div>
Users Controller
class UsersController < ApplicationController
def index
#users = User.paginate(page: params[:page], :per_page => 5).includes(:profile)
end
def show
if params[:id]
#user = User.find(params[:id])
# .includes(:profile)
else
#user = current_user
end
#searches = Search.where(user_id: #user).includes(:state, city: [:profile])
end
def new
#user = User.new
##corporateprofile = Corporateprofile.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to widgets_index_path
else
redirect to '/signup'
end
end
private
def user_params
params.require(:user).permit(:firstname, :lastname, :email, :password, :profile_type)
end
end
And there is no passing code on the controller (as im stuck on that). Any better suggestion or ways to fix this would be much appreciated!
Cheers
First of all, you want to rename your profile classes to StudentProfile and CorporateProfile. This will necessitate running migrations to change your table names too.
The answer to this question depends on how different you want StudentProfile and CorporateProfile to be. If they are completely different or even mostly different, make them separate classes. If they are mostly the same (in other words, they share many of the same methods) you should create a Profile (or UserProfile) model and have StudentProfile and CorporateProfile inherit from this model.
As for implementation, it should look something like this:
# user.rb
class User < ActiveRecord::Base
has_one :student_profile
has_one :corporate_profile
attr_accessor :profileable #probably change this to profile_type. Using attr_accessible because we want to use it during creation, but no need to save it on the user model, although it may not be a bad idea to create a column for user model and save this value.
before_create :create_profile
def create_profile
if profileable == 1
build_student_profile
else
build_corporate_profile
end
end
end
# student_profile.rb
class StudentProfile < UserProfile # or possibly inherit from ActiveRecord::Base if not using inheritance
belongs_to :user
# other student profile stuff here
end
And corporate profile model would look the same as student profile.
Also, you should be using Rails 4 at this point, especially if you're learning and don't understand controllers and params, as this is pretty different between rails 3 and 4. No use in learning something that's outdated, right?
Edit: I should mention, I don't thing you're understanding rails polymorphism. A model should be polymorphic when it will belong to multiple models, not when it will have different subclasses.
For example, if your app has a Like model and something else like a Post model, and a user can like other users' profiles or posts, that might be a good candidate for polymorphism, because Like may belong to StudentProfiles or CorporateProfiles or Posts.

Ruby on Rails - Need help associating models

Ok, am still a newbie in ruby on rails trying to learn my way around. I have two models (User model and Comment model). Basically a user has a simple profile with an 'about me' section and a photo's section on the same page. Users must be signed in to comment on other users profiles.
My User Model
class User < ActiveRecord::Base
attr_accessible :email, :name, :username, :gender, :password, :password_confirmation
has_secure_password
has_many :comments
.
.
end
My Comment Model
class Comment < ActiveRecord::Base
belongs_to :user
attr_accessible :content
.
.
end
In my comments table, I have a user_id column that stores the id of the user whose profile has been commented on and a commenter_id column that stores the id of the user commenting on the profile.
Comment Form
<%= form_for([#user, #user.comments.build]) do |f| %>
<%= f.text_area :content, cols: "45", rows: "3", class: "btn-block comment-box" %>
<%= f.submit "Comment", class: "btn" %>
<% end %>
My comments Controller
class CommentsController < ApplicationController
def create
#user = User.find(params[:user_id])
#comment = #user.comments.build(params[:comment])
#comment.commenter_id = current_user.id
if #comment.save
.........
else
.........
end
end
end
This works fine storing both user_id and commenter_id in the database. My problem comes when displaying the user comments on the show page. I want to get the name of the user who commented on a specific profile.
In my user controller
def show
#user = User.find(params[:id])
#comments = #user.comments
end
I want to get the name of the user from the commenter_id but it keeps throwing errors undefined method 'commenter' for #<Comment:0x007f32b8c37430> when I try something like comment.commenter.name. However, comment.user.name works fine but it doesn't return what I want. Am guessing am not getting the associations right.
I need help getting the correct associations in the models so as to get the name from the commenter_id.
My last question, how do I catch errors in the comments form? Its not the usual form_for(#user) where you do like #user.errors.any?.
routes.rb
resources :users do
resources :comments, only: [:create, :destroy]
end
Try something like this in your models
class User < ActiveRecord::Base
has_many :received_comments, :class_name => "Comment", :foreign_key => "user_id"
has_many :given_comments, :class_name => "Comment", :foreign_key => "commenter_id"
end
class Comment < ActiveRecord::Base
belongs_to :user # comment about profile
belongs_to :commenter, :class_name => "User", :foreign_key => "commenter_id"
end
check out: http://guides.rubyonrails.org/association_basics.html
you can probably come up with better naming on the has_many collections, received and given were the best I could do on short notice :)
Note: foreign_key is option in many cases, left it in above - i think it helps with clarity
has_many fk refers to the the column in the many table (other table)
belongs_to fk refers to the column in the many table (this table)

ActiveRecords Associations: undefined method for nil:NilClass

first of all this is my first Rails application, so please be indulgent... I read the Rails Guides for associations in http://guides.rubyonrails.org/association_basics.html and then started to code my own project. My problem is that I can't do stuff like #project.user.email because #project.user seems to be nil all the time. This happen for all objects. #user.role.name also throws "undefined method for nil:NilClass"; I'm sure I'm doing wrong someting with the models definitions but I don't understand what it is. I appreciate your help. Thanks.
class Role < ActiveRecord::Base
has_many :users
attr_accessible :name
end
class User < ActiveRecord::Base
belongs_to :role
has_many :projects
attr_accessible :email, :password, :password_confirmation, :role_id, :role
end
class Project < ActiveRecord::Base
belongs_to :user
belongs_to :project_type
attr_accessible :id, :project_type_id, :title, :description
end
class Project_Type < ActiveRecord::Base
has_many :projects
attr_accessible :name
end
An example would be for instance the index view for projects where I do (HAML):
%td= proyecto.user.email
which wouldn't work. However,
%td= proyecto.user_id
does work fine.
When you create a new Project, all of the associations will default to nil unless you have setup some type of default in your migrations. There are a few things you can do there.
First, you can set the user manually:
#user = User.find(5)
#project = Project.new
#project.user = #user
Additionally, you can build new projects from the user. The build method is added automatically from the has_many association.
#user = User.find(5)
#project = #user.projects.build
Now #project will contain a project associated with the user who has id 5. You also need to be sure that you tell Rails what the associations are, otherwise the associations won't work.

Checkboxes are getting but not putting many-to-many roles

I'm trying to set up a Rails 3 app to handle user roles with Devise and CanCan.
My relationships are as follows
class User < ActiveRecord::Base
has_many :users_roles
has_many :roles, :through => :users_roles
end
class Role < ActiveRecord::Base
has_many :users_roles
has_many :users, :through => :users_roles
end
class UsersRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
Actually everything is working fine. Devise is handling the authentication perfectly. CanCan is restricting user behaviour based on ability.rb.
But I have a ridiculous problem with setting a UsersRole.
I have defined checkboxes on the User edit page like so.
<% for role in Role.all %>
<%= check_box_tag "user[role_ids][]", role.id, #user.roles.include?(role) %>
<%=h role.name.camelize %>
<% end %>
<%= hidden_field_tag "user[role_ids][]", "" %>
If I create a UserRole via the console, then these checkboxes are checked according to the users role.
But I cannot set or change roles using these checkboxes!
I've been all around the houses with this — variations of syntax, switched to a HABTM and roles_mask approach, rebuilt my models and controllers several times — all to no effect.
Actually the title of my question is not entirely correct - the checkboxes are putting
_method put
authenticity_token XGl6s1iyXJfahdgftc3df8q1ZeehMVzs3LxiQH98jGw=
commit Update
user[current_password] password
user[email] user#example.com
user[name] User Name
user[password]
user[password_confirmatio...
user[role_ids][] 1
user[role_ids][] 4
user[role_ids][]
utf8 ✓
But these values are not being set in the database.
What am I doing wrong??!!!
My guess is that you have specified attr_accesible in your User model and that role_ids is not listed there (and it should not be)
If that is combined with an update_attributes call in your Controller then role_ids will never be set properly.
If this is the case then you should manually be able to set the role_ids in your Controller like this before you update or save:
#user.role_ids = params[:user][:role_ids]
Of course, I'm not certain this is the case since you did not include your controller code or any details with the User model.
Edit:
Instead if doing the #user.update_attributes in the Controller, you could change it to the following instead:
#The same as update_attributes, but without the save
#user.attributes = params[:user]
# Here the role_ids get assigned manually so it does not need to be in attr_accesible
#user.role_ids = params[:user][:role_ids] if params[:user]
# And here it gets checked if the save was successfull
if #user.save
render :action => :show
else
render :action => :edit
end

Resources