Mel
Mel

Reputation: 2715

Rails - how to show attribute of an associated model

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

Answers (2)

Richard Peck
Richard Peck

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 the project 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

  1. @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.

--

  1. .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

CV-Gate
CV-Gate

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

Related Questions