Reputation: 2715
I am trying to make an app in Rails 4.
I just asked this related question and got a clear answer. It seems I can't understand how to take that logic and apply it elsewhere.
Rails How to show attributes from a parent object
I have a user model, profile model a projects model and a universities model.
Associations are:
Profile belongs to university
Profile belongs to user
University has many profiles
University has many projects
Projects HABTM user
Projects belong to universities
In my projects controller, I define @creator as follows:
def create
logger.debug "xxx create project"
#authorise @project
@project = Project.new(project_params)
@project.creator_id = current_user.id
@project.users << current_user
respond_to do |format|
if @project.save
format.html { redirect_to @project }
format.json { render action: 'show', status: :created, location: @project }
else
format.html { render action: 'new' }
format.json { render json: @project.errors, status: :unprocessable_entity }
end
end
end
I try to define creator_profile like this:
def show
#authorise @project
@project = Project.find(params[:id])
@creator = User.find(@project.creator_id)
@creator_profile = @creator.profile
end
In my uni table, I have attributes called logo and name. I use avatar uploader in which i have logo defined (that's why I have two .logo below).
In my projects, show, I want to display the university that the project creator belongs to.
I've tried this:
<%= image_tag(@creator_profile.university.logo.logo) %>
<div class="generaltext"><%= @creator_profile.university.name %> </div>
I get this result: undefined method `logo' for nil:NilClass
Based on the link to my problem above
<%= image_tag(creator_profile.university.logo.logo) %>
<div class="generaltext"><%= creator_profile.university.name %> </div>
I get this result:
undefined local variable or method `creator_profile' for #<#<Class:0x007f998f17ad88>:0x007f998d1ce318>
I'm not sure I understood the very detailed explanations given in the answer to my previous question. If the first version is right, then I don't understand the explanation at all. If the second version is right, then why does this error message come up?
Im wondering if the problem arises out of there not being an association between university and user? I was hoping, based on the user who created the project, to find the uni that the creator belongs to.
That's why i tried:
<%= image_tag(creator_profile.project.university.logo.logo) %>
<div class="generaltext"><%= creator_profile.project.university.name %> </div>
I get this error:
undefined method `project' for #<Profile:0x007f998ada41b8>
Upvotes: 1
Views: 2068
Reputation: 76774
It seems I can't understand how to take that logic and apply it elsewhere.
I don't think you appreciate how ActiveRecord associations work in Rails. I'll explain further down the page.
Your associations will be the likely cause of the problem.
Setting up complicated associations is always tricky - it's best to keep the data as separate as possible.
Here's how I'd construct the models / associations:
#app/models/university_student.rb
class UniversityStudent < ActiveRecord::Base
belongs_to :university
belongs_to :student, class_name: "User" #-> student_id
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :placements, class_name: "UniversityStudent", foreign_key: :student_id #-> user.placements
has_many :universities, through: :placements #-> user.universities
has_and_belongs_to_many :projects #-> user.projects
has_one :profile #-> user.profile (avatar etc)
has_many :created_projects, class_name: "Project", foreign_key: :creator_id
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user #-> store avatar here. This can be used across entire app
end
#app/models/university.rb
class University < ActiveRecord::Base
has_many :projects
has_many :students, class_name: "UniversityStudent" #-> university.students
end
#app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :university
belongs_to :creator, class_name: "User" #-> creator_id
has_and_belongs_to_many :users
delegate :profile, to: :creator, prefix: true #-> @project.creator_profile
end
This allows you to do the following:
def create
@project = curent_user.created_projects.new project_params
@project.users << current_user
Because the associations actually associate your data, you'll be able to do the following:
def show
@project = Project.find params[:id]
#@creator_profile = @project.creator.profile
@creator_profile = @project.creator_profile #-> if you use the delegate method outlined in the models
end
--
In my projects, show, I want to display the
university
that theproject creator
belongs to.
#app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def show
#@project = Project.find params[:id]
@project = current_user.created_projects.find params[:id]
end
end
#app/views/projects/show.html.erb
<%= @project.creator.universities.first %>
My code above allows for multiple universities. Thinking about it, it should be limited to one, but I'll leave it as is for now, maybe change it later.
In my uni table, I have attributes called logo and name. I use avatar uploader in which i have logo defined (that's why I have two .logo below).
Don't use two logo
method, it's an antipattern (explained below)
The fix for this is two-fold:
Firstly, make sure you're calling @creator_profile.university
with the following:
<%= @creator_profile.university %>
If this works, it means you have a problem with .logo.logo
(detailed below), if it doesn't, it means you've not defined @creator_profile
or the university
association correctly.
Secondly, you need to ensure you have the correct controller/view setup.
The problem for many people - especially beginners - is they simply don't understand the way Rails works with controllers & views. You need to appreciate that each time you render a view, the only data it has access to is that which you define in the corresponding controller
action...
#app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def show
@project = Project.find params[:id]
@creator_profile = @project.creator_profile
end
end
#app/views/projects/show.html.erb
<%= content_tag :div, @creator_profile.universities.first.name, class: "generaltext" %>
Trivia
@project.creator_id = current_user.id
This should not have to be defined.
You should be able to change the foreign_key
in the association, so that Rails will automagically define the creator_id
for you:
#app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :creator, class: "User" #-> foreign_key should be :creator_id
end
#app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def create
@project = current_user.created_projects.new project_params #-> populates foreign key automatically.
--
.logo.logo
This is an antipattern.
Calling the same method twice is simply bad practice - why are you doing it?
You either want to delegate any recursive data you're trying to access (such as the example with .creator_profile
above), or you'll want to restructure that functionality.
You want the following:
If you have to delegate to an assets model, you could get away with the following:
<%= @creator_profile.university.images.logo %>
Upvotes: 0
Reputation: 1170
I think that you need to understand some basic concepts of Ruby and Ruby and Rails to solve this question yourself.
In ruby, vars with @ are instance variables and are available all over the class. That means that they will be available in your view if you declare them in your controller.
EG @creator_profile = @profile.user
On the other hand, vars without @ are only available inside the same block.
An example:
#controller
@users = User.all #@users, instance variable
#view
<% @users.each do |user| %>
<h3><%= user.name %></h3> #user, local variable. This will work
<% end %>
<h3><%= user.name %></h3> #this won't work because it is outside the block
Google about ruby vars and scopes.
Also, I think that you are relying too much on 'rails magic' (or you are skipping some code lines), if you don't declare an instance var, it won't exist. Naming conventions don't work that way.
At last but not at least, having a look at your relations, I think that they need some refactor. Also the use of singular and plural is not correct. I know that it's not real code but it denotes that they don't reflect real relationships between entities.
Don't try to make 'octopus' models, where everybody belongs to everybody, and think about the relationships itself, not only trying to associate models. EG:
Profile
belongs_to :creator, class_name: 'User'
This way you can write:
#controller
@profile_creator = Profile.find(params[:id]).creator
#view
@profile_creator.university
You will understand better what you are doing.
Hope it helps.
Upvotes: 1