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.
Related
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.
I have 3 models: Employers, Partners and Collaborations.
As an Employer, I want to add a record to my Partner model and to my Collaboration model to be able to indicate a collaboration between a Partner and a Employer. I therefore have the following columns in my database/tabels.
Models
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :partners, :through => :collaborations
end
class Partner < ActiveRecord::Base
has_many :collaborations
has_many :employers, :through => :collaborations
accepts_nested_attributes_for :collaborations
end
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
end
Tables
Collaborations
employer_id:integer
partner_id:integer
tarive:string
Partners
added_by:integer
name:string
Because I want to be able to add a Partner/Collaboration within 1 form, I use nested forms. So I can add a partner (name, etc) and a collaboration (tarive, etc) in one go.
My (simple_form) form looks like this (I have named_space resource).
Te reduce clutter, I removed as much HTML mark_up as I could, this is not the issue.
Form
/views/employer/partners/_form
= simple_form_for [:employer, #partner], html: { multipart: true } do |f|
Partner
= f.input :name, input_html: { class: 'form-control' }
= f.simple_fields_for :collaborations do |ff|
Tarive
= ff.input :tarive, input_html: { class: 'form-control' }
= f.button :submit, "Save"
My controller looks like
class Employer::PartnersController < ActionController::Base
def new
#partner = Partner.new
#partner.collaborations.build
end
def create
#partner = Partner.new(partner_params)
#partner.collaborations.build
#partner.added_by = current_employer.id
#partner.collaborations.employer_id = current_employer.employer_id
#partner.collaborations.partner_id = #partner.id
#partner.collaborations.added_by = current_employer.id
if #partner.save
redirect_to employer_partner_path(#partner), notice: "Succes!"
else
render 'new'
end
end
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:id, :employer_id, :partner_id, :tarive])
end
end
Problem
The problem/question I have is this. The attributes are assigned nicely and added in the model. But I want to add a employer_id as well, which I have in current_employer.employer.id (Devise). I do not want to work with hidden forms, just to avoid this issue.
I assigned 'parent' models always like #partner.added_by = current_employer.id and that works beautifully.
When I use:
#partner.collaborations.employer_id = current_employer.employer_id
I get an error, saying #partner.collaborations.employer_id is empty.
Question
How can I assign a variable to the nested_form (Collaboration) in my controller#create?
Or more specifically: how can I assign current_employer.employer_id to #partner.collaborations.employer_id?
There are several ways:
Merge the params
Deal with objects, not foreign keys
Personally, I feel your create method looks really inefficient. Indeed, you should know about fat model skinny controller - most of your associative logic should be kept in the model.
It could be improved using the following:
#app/controllers/employers/partners_controller.rb
class Employers::PartnersController < ApplicationController
def new
#partner = current_employer.partners.new #-> this *should* build the associated collaborations object
end
def create
#partner = current_employer.partners.new partner_params
#partner.save ? redirect_to(employer_partner_path(#partner), notice: "Succes!") : render('new')
end
private
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:tarive]) #when dealing with objects, foreign keys are set automatically
end
end
This would allow you to use:
#app/views/employers/partners/new.html.erb
= simple_form_for #partner do |f| #-> #partner is built off the current_employer object
= f.input :name
= f.simple_fields_for :collaborations do |ff|
= ff.input :tarive
= f.submit
... and the models:
#app/models/partner.rb
class Partner < ActiveRecord::Base
belongs_to :employer, foreign_key: :added_by
has_many :collaborations
has_many :employers, through: :collaborations
accepts_nested_attributes_for :collaborations
end
#app/models/collaboration.rb
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
belongs_to :creator, foreign_key: :added_by
before_create :set_creator
private
def set_creator
self.creator = self.employer_id #-> will probably need to change
end
end
#app/models/employer.rb
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :employers, through: :collaborations
end
This may not give you the ability to set tarive, however if you cut down the manual declarations in your model, we should be able to look at getting that sorted.
The main thing you need to do is slim down your code in the controller. You're being very specific, and as a consequence, you're encountering problems like that which you mentioned.
As I am learning RoR now, I would like to know a more appropriated (rails) way to achieve that the application only shows associated resources.
Right now I have the following models:
class Account < ActiveRecord::Base
has_many :billing_accounts
has_many :addresses
end
class BillingAccount < ActiveRecord::Base
belongs_to :invoice_address,
class_name: "Address",
foreign_key:"invoice_address_id"
end
class Address < ActiveRecord::Base
has_many :billing_accounts
belongs_to :account
end
In my edit.billing_account I have this form:
= simple_form_for([:account, #billing_account]) do |f|
= f.association :invoice_address
I expected that only the associated address will be shwon, but this shows "all" address records in the database (also from other user accounts).
Users only should be able to see account.addresses and for now I do this with:
= f.association :invoice_address, collection: current_user.account.addresses.all
But I am sure there is better way to do this inside the models. For every form I now use current_user.account.MODEL.all but that is not very DRY I think.
So basically what I want is only to use =f.association :invoice_address and BillingAccount should know it only can show the account.addresses.
Suggestions are welcome. Thanks!
You just need to set default_scope for nested models:
class Address < ActiveRecord::Base
default_scope { where(account_id: current_user.account_id) }
But in this case you should define current_user in models
In your case you should use f.simple_fields_for instead of f.association as described here: https://github.com/plataformatec/simple_form/wiki/Nested-Models
class BillingAccount < ActiveRecord::Base
belongs_to :invoice_address,
class_name: "Address",
foreign_key:"invoice_address_id"
accepts_nested_attributes_for :invoice_address
end
View:
= simple_form_for([:account, #billing_account]) do |f|
= f.simple_fields_for :invoice_address do |f_address|
= f_address.input :street
= f_address.input :zipcode
...
Don't forget to build invoice_address of account in a controller if it is needed. For example:
class BillingAccountController < ApplicationController
def new
#billing_account = BillingAccount.new
#billing_account.build_invoice_address
end
Since you're using has_many you can use the plural version of the model name rather than current_user.account.MODEL.all.
Like this:
current_user.account.addresses
or
current_user.account.billing_accounts
It even works the other way with belongs_to:
#address = Address.last
#address.accounts
Try to add conditions to belongs_to association:
class BillingAccount < ActiveRecord::Base
belongs_to :invoice_address,
->(billing_account) { where "account_id = #{billing_account.account_id}" },
class_name: "Address",
foreign_key:"invoice_address_id"
end
I have a new form that creates an Item (all the codes are obviously simplified):
<%= simple_form_for #item do |f| %>
<%= f.input :brand_name %>
<%= f.button :submit %>
<% end %>
The current user will create an item and link it to a new or to an existing brand.
This field doesn't exist in the database; it'll be used as a way to associate all models. Hence, I create its getter and setter.
def Item < ActiveRecord::Base
belongs_to :user
belongs_to :brand
attr_accessible :brand_name
def brand_name
brand.try :name
end
def brand_name=(name)
if name.present?
brand = user.brands.find_or_initialize_by_name(name)
brand if brand.save
end
end
end
class ItemsController < ApplicationController
def new
#item = current_user.items.build
end
def create
#item = current_user.items.build(params[:item])
if #item.save
...
end
end
end
The problem is that when the form is submitted, I get this error, which lies in the product_name=() method. I've done some debugging through Rails' console and it goes all fine, but in the browser the setter method is called before the create action. That is, the record doesn't even have a user associated to it. I tried leaving the create method empty, for example, but nothing different happens.
undefined method `brands' for nil:NilClass
What is really weird is that this was working a couple of weeks ago (I've checked my git commits and the code is identical).
I though about calling the before_create callback, but there's no way to know which user should be linked.
UPDATE
I'm using Sorcery as the authentication handler. Everything always works fine, except for this create action.
class User < ActiveRecord::Base
authenticates_with_sorcery!
belongs_to :company
has_many :items
end
class Company < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :brands, dependent: :destroy
end
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)