I'm a noob trying to build up my first rails app. It's a "betting game" where users try to predict football results and get points for correct result or correct "tendency" (win, draw, loose).
Creating and updating bets is working fine, also creating games and inserting results as an admin user. But I'm having problems in displaying all bets from all users for each game in a kind of an index page, which should roughly look like this:
| User1 | User2 | User3 ...
Game Result | Bet | Bet | Bet
Team1 : Team2 1:0 | 1:1 | 3:2 | 1:0
Team3 : Team4 1:2 | 1:2 | -:- | 3:0
Team5 : Team6 -:- | 1:2 | -:- | 3:0
...
My Model structure looks like this:
class User < ActiveRecord::Base
has_many :bets
class Game < ActiveRecord::Base
has_many :bets
class Bet < ActiveRecord::Base
belongs_to :user, :class_name => 'User', :foreign_key => 'user_id'
belongs_to :game, :class_name => 'Game', :foreign_key => 'game_id'
Indicated by the -:- in the above scetch, not every user will have placed a bet on every game, and not every game will have a result.
I tried a thousand ways of getting the data together for the index view and got issues with multiple errors (nil object, no method etc.). The current (not working) approach is getting everything in the controller:
def index
#users = User.all
#games = Game.all
#bets = Bet.all
end
and in the view trying to iterate to the games and within each games through the users and bets in the view, while checking if a bet of a user and a game exists, which leads to unreadable messy code there:
<% #games.each do |game| %>
<tr>
<td><%= game.home_team %> - <%= game.away_team %></td>
<td>
<% if game.away_score.nil? %>
-:-
<% else %>
<%= game.home_score %> : <%= game.away_score %>
<% end -%>
</td>
<% #users.each do |user| %>
<% bet = Bet.where( :user_id => user.id, :game_id => game.id) %>
<% if bet.exists? %>
<td><%= bet.home_bet %> : <%= bet.away_bet %></td>
<% else %>
<td>-:-</td>
<% end -%>
<% end -%>
</tr>
<% end -%>
I hope you can suggest a cleaner way to accomplish this. Unfortunately, none of the dozens of posts I read could solve my problem.
So, the first question is: what is the best way to retrieve the needed data from the different tables? Corresponding second Question: Is it best to build the data in the controller and pass it to the view or put it all together in the view?
I'm using rails 3.1.1 with squlite3.
I hope, someone can help this stupid newbee...
So many issues here... let's start
there's no need to iterate through ALL users for EACH game. You can iterate through game.bets and for each 'bet' get a user by bet.user, according to the relationship you have built
To answer your 1st question: since you are trying to make a 'report' kind of a view with an intent to show 'all' games, your approach to have #games = Game.all is right. You don't need 2 other .all collections though since you can infer the data from the relationships
2nd question: In Rails it is considered a good approach to have 'fat models, skinny controllers' which means that your data crunching/logic code should be in a model and controller should have only code used by the correspondent views.
About your view: In your case your view is not terribly bad since you have a sparsely populated tables and you should use ifs for cases where score is not available etc
I would probably do something like this:
controller:
#users = User.all
#game_bets = Game.all.map { |game| [game, game.bets.index_by(&:user)] }
view:
<% #game_bets.each do |game, bets| %>
...
<% #users.each do |user| %>
<% if bets.has_key?(user) %>
<td><%= bets[user].home_bet %></td>
<% else %>
<td>-:-</td>
...
Related
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 %>
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).
I have the following view to show the categories with the count
<% #categories.each do |category| %>
<% category.sub_categories.sort.each do |sub_category| %>
<li><%= link_to sub_category.name, "category/#{sub_category.slug}", title: sub_category.name.capitalize %> <%= sub_category.posts.where(status: 1).count %></li>
<% end %>
<% end %>
But I dont think using where in view is not good idea. Is there any other way to perform such operation.
I am getting correct count but I need better way to do this. can anyone help
Your Post model should have a scope on it that encapsulates this status logic:
class Post < ActiveRecord::Base
def self.active
where(status: 1)
end
end
Then call it like so from the view:
sub_category.posts.active.count
use scope to do the same thing, your solution is ok otherwise. you don't need to do this controller.
scope :active_posts, lambda{ where(status: 1)}
The only problem I see with this is it causes N+1 queries, because you do 1 query for the categories THEN another query for EACH category. This is ok with small quantities, however can cause serious performance problems later on.
1) I recommend you look into "counter_cache"ing:
- http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to
2) OR upon activation/deactivation of Posts, create a method that will increment/decrement a attribute on the Post's Category (i.e. "active_posts")
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 %>
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.