How can I get access to foreign_key? - ruby-on-rails

There are
a user-model/table,
a schedule-model/table and
a users_schedules-table.
The models has_and_belongs_to_many each other.
I can add a relationship with #user.schedules << #schedule in the controller.
How can I get access to the join-table 'users_schedules' ?
I want to show the users which has_and_belongs_to_many schedules:
I thought about something like this: schedules.users_belongs_to. As you can see in the view-code below.
view: (There is an example I want to add)
<table class="table table-hover">
<tbody>
<% #user_schedules_date.sort.each do |date_time, schedules| %>
<tr class="thead success">
<th colspan="4" scope="col"><p><%= date_time.strftime("%A, %d.%m.%Y") %></p></th>
</tr>
<% for schedule in schedules %>
<tr>
<th scope="row"><p><%= schedule.titel %></p></th>
<td><p><%= schedules.users_belongs_to #ALL USERS WHO ARE BINDED TO THIS SCHEDULE# %></p></td>
<td><p><%= schedule.date_time.strftime("%H:%M:%S") %></p></td>
<td><p><%= schedule.location %></p></td>
<td>
<p>
<%= link_to 'Bearbeiten', {:controller => 'schedule', :action => 'edit', :id => schedule.id} %>
oder
<%= link_to 'löschen', {:controller => 'schedule', :action => 'delete', :id => schedule.id} %>
</p>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
In the controller I tried the following, but I don't know how to replace/fit the placeholder (:email).
controller:
class WelcomeController < ApplicationController
def index
if(current_user)
#user_schedules = current_user.schedules
#user_schedules_date = #user_schedules.order(:date_time).group_by { |sched| sched.date_time.beginning_of_day }
#users_schedules_shared = User.find_by(:email) #HERE I NEED THE USER WHICH BELONGS_TO THIS SCHEDULE
end
end
end
I hope you can understand my problem.
Thanks for your help!
EDIT:
I gather all the data in the controller:
class WelcomeController < ApplicationController
def index
if(current_user)
#user_schedules = current_user.schedules
#user_schedules_date = #user_schedules.order(:date_time).group_by { |sched| sched.date_time.beginning_of_day }
#users_all = User.includes(user_schedules: :schedules)
end
end
end
and edit the view as the following:
<% #users_all.each do |user| %>
<% user.name %>
<% end %>
But I get the following error:
Association named 'user_schedules' was not found on User; perhaps you misspelled it?
I red this, as deyan said, but I dont understand it.
#users_all = User.includes(user_schedules: :schedules) <- returns an array ?!?? (If I understood it correctly)
So I need each array-item.name to show the Users name??
Can anyone tell me what I am doing wrong?
Database:
http://i.stack.imgur.com/Apg7y.png
users
schedules_users (join-table with fk)
schedules

I want to show the users which has_and_belongs_to_many schedules
This will use something called a many-to-many relationship - meaning that if you access the associated data through a model - you'll have an appended attribute / method to capture them.
So what you'd do is the following:
#app/models/schedule.rb
Class Schedule < ActiveRecord::Base
has_and_belongs_to_many :users
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_and_belongs_to_many :schedules
end
This will append a collection to each of these model objects, allowing you to call the collection as required:
#schedules = Schedule.all
#schedules.each do |schedule|
schedule.users #-> outputs a collection
schedule.users.each do |user|
user.name
end
end
Using includes with Rails is actually quite a bad thing, considering you can call ActiveRecord associations to do the heavy-lifting for you
--
has_and_belongs_to_many
Simply, you can't access the has_and_belongs_to_many table directly, as it has no primary_keys in place.
Rails basically uses the relational database infrastructure (through ActiveRecord) to access the associative data. This means that if you're using this particular type of table, you'll just be able to access the collection it provisions:
#app/models/user.rb
Class User < ActiveRecord::Base
has_and_belongs_to_many :schedules
end
#app/models/schedule.rb
Class Schedule < ActiveRecord::Base
has_and_belongs_to_many :users
end
This will allow you to access:
#user = User.find 1
#user.schedules #-> shows schedules collection. You will have to loop through this

If I understand you correctly, you want to show the schedules for your current user, and for each schedule to show what other users belong to it.
Based on your view, to get all users for a schedule, all you would need is to replace schedules.users_belongs_to withschedule.users.
If you want to get together all unique users for all the schedules the current user might have, then in the controller you could do current_user.schedules.collect{|s| s.users|}.uniq. This is only useful if you want to show all users, no matter which schedule each user belongs to.
I would advice you to gather all the data in the controller and then to print it in the view. You could join all tables in a single call to the DB, which might look something like this: User.includes(users_schedules: :schedules) but you would need to adapt it depending on what your models are called.
Methods 1 or 2 are solving your problem using your current code, but might be slow. I would suggest you read more here: http://guides.rubyonrails.org/active_record_querying.html and get all data at once (method 3).

Related

Rails: How to sort based on Average, Sum, Count with Associated Models?

I have 2 associated tables as follows, and currently I have a reporting page which displays average, count, sum etc.. I would like to add sorting functionality to all columns on the report table but I couldn't figured out how I can achieve it for ave,count,sum calculations on the model?
DB TABLES:
Student
------
id
name
city
Course
------
id
coursename
price
status
student_id
MODELS:
class Student < ApplicationRecord
has_many :course
class Course < ApplicationRecord
belongs_to :student
scope :ongoing, lambda {|ongoing| where(:status => ongoing)}
scope :finished, lambda {|finished| where(:status => finished)}
VIEW:
<% #students.each do |student| %>
<div class="row">
<span><%= student.id %></span>
<span><%= student.city %></span>
<span><%= student.name %></span>
<span><%= student.course.ongoing.count(:coursename) %></span>
<span><%= student.course.ongoing.average(:price) %></span>
<span><%= student.course.ongoing.sum(:price) %></span>
<span><%= student.course.finished.count(:coursename) %></span>
<span><%= student.course.finished.sum(:price) %></span>
I'd recommend sorting it with js on the user end. Create a table and you can use one of many js sorting plugins to allow user to sort based on any columns they choose.
Alternatively you can sort #student with scope:
## Student
scope :by_ongoing_course_count, -> {
select(:id, :city, :name).joins(:courses).merge(Course.ongoing).group(:student_id).order("count(coursename)")
}
end
## StudentsController
def index
#students = Student.by_ongoing_course_count
end
By the way I think it's perfectly find to call instance methods in views. But you might want to create some helpers for those chained queries, e.g.
#Student
def ongoing_course_counts
course.ongoing.count(:coursename)
end
First of all create a method (for eg: get_student_data) in Student class which will provide you all the required data. Method should accept a parameters as well. Its always bad practice to call model methods or do queries in views.
Now once you get the required data from database, iterate through the data and sort it based on the sorting column passed in as a parameter.

Rails 4 issue in includes and fetch associated items

I have an issue with the includes method in Rails.
I have this user model:
class User < ActiveRecord::Base
has_one :account
end
And this account model:
class Account < ActiveRecord::Base
belongs_to :User
end
Now the user model has :id and :names
and the account model has :user_id and :country
In another controller i want to fetch and display all the user names with their countries.
I am using this query
#users = User.includes(:account).where(some condition)
Please tell me what can I add in the query to fetch all the countries associated with the user names?
Thanks in advance
user should be plural in the controller as you are getting list of records . like this
#users = User.includes(:account)
in view you can do this.
<% #users.each do |user| %>
<%= user.names %>
<%= user.account.country %>
<% end %>
As names is user attribute, you can fetch directly, while country is account's attribute, you can fetch using user.account.country
If it gives you error like
NoMethodError (undefined method `account' for #)
then this means you are converting it into an array in controller itself.
Change your code
eg:
#users = User.includes(:account).where('id < 5')
And in View
<% #users.each do |user| %>
<%= user.name %>
<%= user.account.country %>
<% end %>
Whenever we are using includes it by default does an outer join, so there will may be users who does not have any account and you will get error while trying to access user.account.country as user.account is nil and on the nil object it will try to call country method which does not exist.

Display multiple model results in your views in rails app

I'm adding a new model to my equasion and I'm wondering if there is a way to associate two models into one model then display any/all results within a view. For example, here is what I've currently have;
#tweet_category.order("position").each do |tweet|
<%= tweet.title %>
end
just a short example... now what if I added facebook into this. I was first thinking of creating a model thats named stuff then associate it to tweet_category and facebook_category like so;
class Stuff < ActiveRecord::Base
attr_accessible :title
belongs_to :user
has_many :tweet_category
has_many :facebook_category
end
Now in my controller I'm guessing I would do the following;
class StuffController < ApplicationController
def index
#stuff_list = Stuff.find(:all)
end
end
and in my view I would just simply do the following from above view;
#stuff_list.order("position").each do |stuff|
<%= stuff.title %>
end
am I understanding the logic here??? would that work having two models / two tables db.. etc..
First of all, I don't understand why you would need that "stuff" model. It belongs to users and has_many tweet_category and facebook_category, and just does nothing but offering a "title", when your User model could do the job ( I mean, each user could have many tweets and fb category, instead of having one or several "stuff" which has/have many of them ).
Anyway, if you want to make links between your models and then display everything in a view, first in your User model you just have to do :
class User < ActiveRecord::Base
...
has_many :facebook_categories #( I don't know how rails would pluralize it, btw, I'm just making an assumption )
has_many :tweeter_categories
end
and
class Facebook_category
...
belongs_to :user
end
and do the same fot the tweeter category
Then in your controller :
def show_everything #Here it's a custom action, but you can call it wherever you want
#users = User.all
end
And finally in your view :
<% #users.each do |user| %>
<% user.facebook_categories.all.each do |fb_c| %>
<%= fb_c.title %>
<% end %>
<% user.tweeter_categories.all.each do |t_c| %>
<%= t_c.title %>
<% end %>
<% end %>
Maybe just try to grab a better name for your models, so the pluralization doesn't get messy ( and I saw that the ".all" method is deprecated, so maybe replace it with something
Hope it helps !
Edit :
Basically, when you're doing
#users = User.all
What rails' doing is putting every hash defining every "User" in an array. So, if you want to mix two tables' arrays inside a single array, you can do something like this :
#categories = [] << Facebook_category.all, Tweeter_category.all
You will then have an array ( #category ), filled with 2 arrays ( one ActiveRecord relation for Facebook_category and one for Tweeter_category ). Themselves filled with hashes of their model. So, what you need to do is :
#categories.flatten!
Here's the API for what flatten does ( basically removing all your nested arrays inside your first tarray )
Now, you got a single array of hashes, being the informations from both your model's instances. And, if these informations can be ordered, in your view, you just have to :
<% #categories.order("updated_at").each do |i| %>
<%= i.title %>
<% end %>

How to display only those Rails records where a specific field is not empty?

I think I'm overlooking something very simple here, but would really appreciate some help to work out what it is.
In the project show view, I'm displaying associated (has_many) tasks in a partial. I only want to display those records where a particular field is not empty. My view code looks like this.
<% for task in #tasks %>
<% unless task.user.notes.empty? %>
<tr>
<td><%= task.user.name %></td>
<td><%= task.user.notes %></td>
</tr>
<% end %>
<% end %>
This is returning undefined method 'notes' for nil:NilClass. This is strange as :notes is definitely in the User model.
The Project controller handling this is contains:
def show
#tasks = #project.tasks.paginate(:page => params[:page])
end
My models look as follows
Project
has_many :tasks
end
Task
belongs_to :project
belongs_to :user
end
User
has_many :tasks
end
What have I missed here? Am I using empty? correctly? Or should I be handling this in the controller? I currently have three partials on the Project show, all using the same Task query. Performance and/or best practice -wise, does it make more sense to have all three partials sourcing data from the same controller query, or to have a sperate query just for this case?
Thanks for any pointers.
The problem was that your User model was undefined when you called task.user.notes.
You can solve this problem as well as improve your overall design by making use of the #delegate macro provided by ActiveSupport in Rails. For example, inside of the Task model, try adding
delegate :notes, :to => :user, :prefix => true, :allow_nil => true
This adds a task.user_notes method to the Task model which will allow you to fetch the User's notes straight from the Task itself. Additionally, because we specified the allow_nil option, if there is no User associated with the Task, the result of the method will be nil instead of an exception.
You can also add this for the name attribute of the User allowing you to call task.user_name from within the view.
Resources:
delegate - http://apidock.com/rails/v3.0.9/Module/delegate
"Law of Demeter" - http://devblog.avdi.org/2011/07/05/demeter-its-not-just-a-good-idea-its-the-law/
In the controller
def show
#tasks = #project.tasks.paginate(
:page => params[:page],
:conditions=>["notes is not ? and notes !=?",nil,'']
)
end
OR, not in the controller
Write a helper method to abstract this.
Add a new helper method
def filter_tasks(tasks)
tasks.find(
:all,
:conditions=>["notes is not ? and notes !=?",nil,'']
)
end
And use helper in view
<% for task in filter_tasks(#tasks) %>
<% unless task.user.notes.empty? %>
<tr>
<td><%= task.user.name %></td>
<td><%= task.user.notes %></td>
</tr>
<% end %>
<% end %>

Drawing information from relational databases in Rails

I am trying to pull the name of the Artist from the Albums database.
These are my two models
class Album < ActiveRecord::Base
belongs_to :artist
validates_presence_of :title
validates_length_of :title, :minimum => 5
end
class Artist < ActiveRecord::Base
has_many :albums
end
And here is the Albums Controller
def index
# albums = Album.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #albums }
end
end
And the View from the index:
<% #albums.each do |album| %>
<tr>
<td><%=h album.id %></td>
<td><%=h album.title %></td>
<td><%=h album.artist.name %></td>
</tr
<% end %>
My end result html is coming out like this for the artist field!
#<Artist:0x000001022e4868>
and if I set it to artist.name
I get this:
undefined method `name' for nil:NilClass
What am I doing wrong?
You need to do something like:
<%=h album.artist.name %>
The way you used it you are displaying whole object, so to speak.
Sounds like you have an Album without an Artist (either artist_id is null or set to an artist_id that no longer exist).
You can try:
<%= h album.artist ? album.artist.name : 'N/A' %>
Another way to write what was enumerated earlier.
<%= h album.artist.name unless album.artist.blank? %>
I would recommend going into script/console and manually stepping through the process of pulling all your articles and then printing out all the artist names.
BTW, if you're running this code in production you should probably use eager loading
# albums = Album.find(:all, :includes => [:artist])
This will be much more efficient.
Do you actually have data correctly set up in your database tables? If your Artist and Album models are being saved correctly then it should look something like this:
artists table
id|name
---------------------
1|The Beatles
2|The Rolling Stones
albums table
id|artist_id|title
--------------------------------------------------
1|1 |The White Album
2|2 |Exile on Main Street
3|1 |Sgt. Pepper's Lonely Hearts Club Band
Something is going on the saving of your artists->album relationship. If you're getting that error you're getting a nil artist back which means the key isn't being saved in the table. Check your table manually and make sure the relation is there if it's not then you're looking in the wrong place, you should be fixing your save.

Resources