undefined method `include?' for a relationship model - ruby-on-rails

I am trying to filter out some objects in an array by using the include? method, but I keep running into this error undefined method 'include?' for #<Trip:0xa01b7b0>.
I basically have two models, eventdeal and trip. I created a 3rd relationship model, eventdealtrip, that ties the two models together.
trip.rb
class Trip < ActiveRecord::Base
has_many :eventdealtrips, :dependent => :destroy
has_many :eventdeals, :through => :eventdealtrips
end
eventdeal.rb
class Eventdeal < ActiveRecord::Base
has_many :eventdealtrips
has_many :trips, :through => :eventdealtrips, :dependent => :destroy
end
eventdealtrip.rb
class Eventdealtrip < ActiveRecord::Base
belongs_to :eventdeal
belongs_to :trip
end
eventdealtrips/new.html.erb
<% if !#trips.blank? %>
<% #trips.each do |trip| %>
<% if !trip.include?(#eventdeal) %>
<!--Content-->
<% end %>
<% end %>
<% end %>
Basically, I only want to display the trips that do NOT include the current eventdeal (which is defined in the controller).
Any insight as to why I'm getting the undefined method error?
Thanks.

The include? method is usually for arrays, try this:
<% if !trip.eventdeals.include?(#eventdeal) %>

are you sure trip is an array? have you tried trip.inspect?
I'm not sure what exactly your #trips is, but I guess it's an array of Trips. You should post that too.
trip.eventdealtrips.include? #eventdealtrip
// or
trip != #eventdealtrip

Related

Can't access field through 3 models in Rails 5

I'm trying to do something very similar to this question
I have 4 models, one of which (CoffeeBlend) is a join table:
class CoffeeRoast < ApplicationRecord
has_many :coffee_blends
has_many :coffee_beans, through: :coffee_blends
has_one :country, through: :coffee_beans
end
class CoffeeBean < ApplicationRecord
has_many :coffee_blends
has_many :coffee_roasts, through: :coffee_blends
belongs_to :country
end
class Country < ApplicationRecord
has_many :coffee_beans
end
class CoffeeBlend < ApplicationRecord
belongs_to :coffee_bean
belongs_to :coffee_roast
end
My coffee_beans table has a column called country_id which is populated with the id from the countries table.
In my coffee_roasts_show I want to be able to pull the associated country of the coffee bean. My latest attempt looks like
<% #coffee_roast.coffee_beans.country.country_name %>
which gives undefined method 'country'
Or
<% #coffee_roast.coffee_beans.countries.country_name %>
returns undefined method 'countries'
Do I have my associations correct? Is my show code wrong?
The method #coffee_roast.coffee_beans returns you association, not a single record. That's why you cannot call #country on that. If you need all the countries, you can use #map:
<% #coffee_roast.coffee_beans.map {|cb| cb.country.country_name } %>
Edit:
If you want to show the list in the browser, add = to your ERB mark:
<%= #coffee_roast.coffee_beans.map {|cb| cb.country.country_name } %>
It may also be useful to explicitly convert contry names to a string with Array#join
<%= #coffee_roast.coffee_beans.map {|cb| cb.country.country_name }.join(', ') %>

How to loop through a joined table

The models I have:
Category:
class Category < ApplicationRecord
has_many :categorizations
has_many :providers, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Provider:
class Provider < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorization:
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :provider
has_many :games, dependent: :destroy
accepts_nested_attributes_for :games
end
Game:
class Game < ApplicationRecord
belongs_to :categorization
end
I need to display the games, that belongs to a specific provider. I tried to do it like:
<% #provider.categorizations.joins(:games).each do |game| %>
<%= game.title %>
<% end %>
It gives me an error: NoMethodError: undefined method 'title' for #<Categorization:0x007f2cf6ee49e8>. So, it loops through the Categorization. What is the best way to loop through the joined games table? Thanks.
First, you should do the request in your controller, or even better call a scope (defined in a model) from the controller.
Do not forget that Active Record is just an ORM, a tool allowing you to manipulate SQL.
With #provider.categorizations.joins(:games) you are not asking for games. You are asking for the categorizations and you do a JOIN with the games table. This joins is usually to allow to filter by games attributes.
To do what you want you should do the following :
#games = Game.joins(:categorization).where('categorization.provider_id = ?',#provider.id)
As you can see, the join do not return categorization, it allow me to use categorization as a filter.
You should always be aware of the SQL generated by Active Record. Look at the SQL query generated in your server's traces.
I'm guessing 'title' is an attribute of games and not categorization, so you either need to return an array of games, or add a select on the end to pull the title attribute into the categorization object, like so:
<% #provider.categorizations.joins(:games).select('dba.games.title').each do |game| %>
<%= game.title %>
<% end %>
Just to add- you shouldn't really be doing this in the view file. I'd go as far as not even doing this in the controller. I tend to encapsulate this sort of logic into a service class, which is instantiated in the controller to return a set of results. The controller should only be passing the result set on, which is then presented by the view.
class Provider < ActiveRecrord::Base
# this could be a scope instead, or in a seperate class which
# the provider model delegates to- whatever floats you boat
def get_games
# you could use pluck instead, which would return an array of titles
categorizations.joins(:games).select('dba.games.title')
end
end
class ProviderController < ApplicationController
def show
provider = Provide.find(params[:id])
#games = provider.get_games
end
end
<% #games.each do |game| %>
<%= game.title %>
<% end %>

Rails, how to avoid the "N + 1" queries for the totals (count, size, counter_cache) in associations?

I have a these models:
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
end
class Movie < ActiveRecord::Base
has_many :tickets
has_many :childrens, through: :tickets
belongs_to :cinema
end
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
What I need now is in the page of "Cinemas" I wanna print the sum (count, size?) of the childrens just for the movies of that cinemas, so I wrote this:
in the cinemas_controller.rb:
#childrens = #cinema.childrens.uniq
in the cinemas/show.html.erb:
<% #childrens.each do |children| %><%= children.movies.size %><% end %>
but obviously I have bullet gem that alert me for Counter_cache and I don't know where to put this counter_cache because of different id for the movie.
And also without the counter_cache what I have is not what I want because I want a count for how many childrens in that cinema taking them from the tickets from many days in that cinema.
How to?
UPDATE
If in my view I use this code:
<% #childrens.each do |children| %>
<%= children.movies.where(cinema_id: #cinema.id).size %>
<% end %>
gem bullet don't say me anything and every works correctly.
But I have a question: this way of querying the database is more heavy because of the code in the views?
This might help you.
#childrens_count = #cinema.childrens.joins(:movies).group("movies.children_id").count.to_a
You can use includes to load all associations ahead of time. For example:
#childrens = #cinema.childrens.includes(:movies).uniq
This will load all of the children's movies in the controller, preventing the view from needing access to the database in your loop.
You might agree, that the number of movies belongs to a child equals the number of tickets they bought.
That's why you could just cache the number of tickets and show it on the cinemas#show.
You can even create a method to make it more clear.
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
def movies_count
tickets.size
end
end
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children, counter_cache: true
end
class Movie < ActiveRecord::Base
belongs_to :cinema
has_many :tickets
has_many :childrens, through: :tickets
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
And then:
<% #childrens.each do |children| %><%= children.tickets.size %><% end %>
Or
<% #childrens.each do |children| %><%= children.movies_count %><% end %>
But if you want to show the number of tickets for every movie, you definitely need to consider the following:
#movies = #cinema.movies
Then:
<% #movies.each do |movie| %><%= movie.tickets.size %><% end %>
Since you have belongs_to :movie, counter_cache: true, tickets.size won't make a count query.
And don't forget to add tickets_count column. More about counter_cache...
P.S. Just a note, according to conventions we name a model as Child and an association as Children.
Actually is much more simpler than the remaining solutions
You can use lazy loading:
In your controller:
def index
# or you just add your where conditions here
#childrens = Children.includes(:movies).all
end
In your view index.hml.erb:
<% #childrens.each do |children| %>
<%= children.movies.size %>
<% end %>
The code above won't make any extra query if you use size but if you use count you will face the select count(*) n + 1 queries
I wrote a little ActiveRecord plugin some time ago but haven't had the chance to publish a gem, so I just created a gist:
https://gist.github.com/apauly/38f3e88d8f35b6bcf323
Example:
# The following code will run only two queries - no matter how many childrens there are:
# 1. Fetch the childrens
# 2. Single query to fetch all movie counts
#cinema.childrens.preload_counts(:movies).each do |cinema|
puts cinema.movies.count
end
To explain a bit more:
There already are similar solutions out there (e.g. https://github.com/smathieu/preload_counts) but I didn't like their interface/DSL. I was looking for something (syntactically) similar to active records preload (http://apidock.com/rails/ActiveRecord/QueryMethods/preload) method, that's why I created my own solution.
To avoid 'normal' N+1 query issues, I always use preload instead of joins because it runs a single, seperate query and doesn't modify my original query which would possibly break if the query itself is already quite complex.
In You case You could use something like this:
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children
end
class Movie < ActiveRecord::Base
has_many :tickets
has_many :childrens, through: :tickets
belongs_to :cinema
end
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
#cinema = Cinema.find(params[:id])
#childrens = Children.eager_load(:tickets, :movies).where(movies: {cinema_id: #cinema.id}, tickets: {cinema_id: #cinema.id})
<% #childrens.each do |children| %>
<%= children.movies.count %>
<% end %>
Your approach using counter_cache is in right direction.
But to take full advantage of it, let's use children.movies as example, you need to add tickets_count column to children table firstly.
execute rails g migration addTicketsCountToChildren tickets_count:integer,
then rake db:migrate
now every ticket creating will increase tickets_count in its owner(children) by 1 automatically.
then you can use
<% #childrens.each do |children| %>
<%= children.movies.size %>
<% end %>
without getting any warning.
if you want to get children count by movie, you need to add childrens_count to movie table:
rails g migration addChildrensCountToMovies childrens_count:integer
then rake db:migrate
ref:
http://yerb.net/blog/2014/03/13/three-easy-steps-to-using-counter-caches-in-rails/
please feel free to ask if there is any concern.
Based on sarav answer if you have a lot of things(requests) to count you can do:
in controller:
#childrens_count = #cinema.childrens.joins(:movies).group("childrens.id").count.to_h
in view:
<% #childrens.each do |children| %>
<%= #childrens_count[children.id] %>
<% end %>
This will prevent a lot of sql requests if you train to count associated records

rails relationship. Rails 4

I'm new in the world of rails developers. Please, help me to understand.
I've 3 tables:
Calls, Questions, Results
Calls is:
id, name, date
Questions is:
id, question
Results is:
id, call_id, question_id, result
I've read the Rails manual, as i understand i've created 3 models.
In my model Call.rb
I've done next relationship:
has_many :results
has_many :question, through: :results
My Result.rb
belongs_to :call
belongs_to :question
My Question.rb
has_many :result
So, there are can be many records in the table "results" with one call_id, and it's can be one relation with question through results table
If if try to launch code like this:
#calls = Call.all
Than on my view:
<% #calls.each do |call| %>
<%= call.result.result %>
<% end %>
i've error that "result is undefined method". But it's must be a property.
What i do wrong?
Thanks!
According to your schema, your associations should look like this
class Call < ActiveRecord::Base
has_many :questions
has_many :results
end
class Question < ActiveRecord::Base
belongs_to :call
end
class Result < ActiveRecord::Base
belongs_to :call
end
So in the view,
<% #calls.each do |call| %>
<% call.results.each do |result| %>
<%= result.result%>
<% end %>
<% end %>
A few things.
First, you need to fix your associations so the plural and singular tenses match. has_many :result does not work as Marcelo points out.
Second, you need to ensure that your tables actually have the correct id's to make the associations work. Use the rails console to inspect Result. From your question info, it should have attributes for call_id and question_id. Once you've confirmed this, create a few objects in the console and test your associations.
#call = Call.create(name: "test", date: Time.now)
#result = Result.create(call_id: #call.id, result: "some result")
Then
#call.result # should yield the Result record you just created
Lastly, you need to rename the result attribute for Result. That's super confusing and will only cause problems.
The first thing I notice is that your call should have many questions, and many results through questions. That's because calls own questions, which in turn own results themselves.
class Call < ActiveRecord::Base
has_many :questions
has_many :results, through: :questions
end
You didn't need call_id in Result class. But, if you wish to keep it there, you dont need through: :questions in your call class (given there is a direct relation between them)
In your Question class, I assume it is a typo, but it should be plural
has_many :results
Having said that, your loop through calls will bring results (plural) and not result (singular) given that a call may have many results. Therefore:
<% #calls.each do |call| %>
<% call.results.each do |result| %>
<%= call.result %>
<% end %>
<% end %>

Rails active record associations, nested models

Been going through http://guides.rubyonrails.org/association_basics.html but can't seem to get my head around this
I have 4 models: users, listings, comments, commentresponses. Somebody creates a listing, someone else can comment on the listing, then the original creator can respond to the comment.
class User < ActiveRecord::Base
has_many :comments, foreign_key: 'provider'
has_many :listings
has_many :comments
has_many :commentresponses
end
class Listing < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :listing
belongs_to :user
has_one :commentresponse
end
class Commentresponse < ActiveRecord::Base
belongs_to :comment
belongs_to :user
end
Everything is working well except I can't access comment.commentresponse; this give a no method error.
Any recommendations of where my logic is wrong?
Associations
I wouldn't use a separate model for CommentResponse; keep it all in the Comment model - using a gem such as ancestry to give a parent / child system to the different comments:
Above is an example of one our Category models - showing how you can order the different associations with the likes of the ancestry gem. The reason why I posted is because this is how you can create responses to your comments, rather than having a separate model:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :listings
has_many :comments
end
#app/models/listing.rb
class Listing < ActiveRecord::Base
belongs_to :user
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :listing
belongs_to :user
has_ancestry #-> make sure you have "ancestry" column with string in db
end
This basically allows you to use the various methods which ancestry appends to your objects:
Ancestry
I would recommend using the Ancestry gem to store the responses of the comments. You can then add to this by using several partials to provide a nested interface. This way, it will show you the comments you want, with the correct responses etc
IMPORTANT
When using ancestry - you define the parents of the rows with comment_1/comment_2. Many people think you have to just define the "parent"; not true. You have to define the entire "history" of the ancestors of an object
--
Tree
If you go with the ancestry approach, you'll be able to do something like the following:
To achieve this, you can use the nested partial we created here (obviously replace for use with comments):
#app/views/categories/index.html.erb
<%= render partial: "category", locals: { collection: #categories } %>
#app/views/categories/_category.html.erb
<ol class="categories">
<% collection.arrange.each do |category, sub_item| %>
<li>
<!-- Category -->
<div class="category">
<%= link_to category.title, edit_admin_category_path(category) %>
</div>
<!-- Children -->
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
</li>
<% end %>
</ol>
I know it's not a direct answer to your question; it certainly should help you though

Resources