Calling a method on an attribute in rails - ruby-on-rails

I am working on a tutorial and I wanted to really learn by deviating from the tutorial a bit and am encountering some problems. I am creating a showtime app that lists all the movies, times, genres, etc.
I have a Movie model with the attributes name, genre, showtime, showdate, release year, and title. In my movie.rb file, I added an attr_accessor :genre so I can use the setter and getter methods, but when I go into rails console and create a movie with all the attributes, the :genre keeps coming up as "nil" but when I call the movie.genre, it comes up as the correct listed genre. I am not sure why that is?
Also, since genre is not a class, are there any methods I can call on it to list all the genres in the database?
Thank you!

Is genre a column in your movies table? If so, why use attr_accessor?
For the second part of your question, you could write a class method in movie.rb to list all genres. Something like:
def self.genres
Movie.all.map(&:genre).uniq
end

Related

Looking for a way to track history in rails database

I'm considering this an add-on question of sorts to the thread below:
Using join tables in ruby on rails
So we have 'Student' and 'Course' scaffolds joined by a many-to-many association, but in addition there is also a 'Semester' scaffold and what I wish to do is, for each student that is added to a course, for the application to search for previous iterations of the same course through past semesters, to that it's known how many times a student has taken that class before. I'm kind of mixed up at the moment as to how to implement this, so I was hoping someone could help me pin down the logic and code I should be operating by.
Some underlying assumptions I have so far:
'Course' and 'Semester' should, like 'Student' and 'Course', be joined
by a many-to-many association (many courses are taught per semester,
and a course is taught for more than one semester).
There should be an action (let's say get_student) within the course
controller to locate the student via student_id. This would be the main area I'm scratching my head as to what to do. What would this method look like in code?
Within the student-course join table I should have an attribute
'attempts' which increments each time get_student finds this
student_id combined with the course_id that calls the method.This
would be the mechanism that actually tells how many times the course
had been attempted by the student.
I initially wondered if there should be a 'semester' controller
action to activate get_student across all semesters, but now I'm
thinking that get_student should work fine without that.
Appreciate any help I can get on this. Thanks.
This is not a good answer, just a comment.
I would comment, but hear will be more clear. I ll update for the other points. This is just an ongoing feedback/discussion, not an answer.
class Semester < ApplicationRecord
has_many :courses
end
class Course < ApplicationRecord
has_many :students
end
And
semester.courses[0].students => outputs the students array for that
This could be the method to calculate the number of student that did that course:
def studentForCourse
#input_params.course_id => course id you are selecting
semester = Semester.find(input_params)
semester.courses.each do |course|
if course.id = input_params.course_id
nstudents = course.students.size
end
end

Rails - Creating and Validating Many-to-Many relationship with the same model?

This is probably a really simple question, but I've been searching the web for probably around an hour and I can't really find an answer to my problem. It should be clear by what follows that I am very new to Rails, so my terminology and explanation might be a bit confusing.
Let's say that I were making a social media app on Rails, where one of the models is User. I want to make a many-to-many relationship called "friends", which links two users together. Let's say in this situation I also wanted to make a many-to-many between two users called "enemies".
This is all completely hypothetical, but the idea is the same one that I want to use for something I'm working on.
Because a user can have many friends and enemies, but also be many friends and enemies, I would use:
class User < ActiveRecord::Base
has_and_belongs_to_many :users #this should be the friends association
has_and_belongs_to_many :users #this should be the enemies association
end
Now I'm guessing I can't just do that, because I would have to have two tables both named users_users. So, then I switch to:
class User < ActiveRecord::Base
has_and_belongs_to_many(:users, join_table: 'friends',
foreign_key: 'user_id', associate_foreign_key: 'friend_id')
end
With a similar statement for the enemies table. Now, my problem is that I want to have a form that the user can use when they sign up, where they can input their information (this is the User object details), and also list their friends and enemies.
Because the user won't have the database id key for their friends or enemies, they'll have to input the users' names. This is fine, though because the name is also a unique key, guaranteed by the validation.
However, if the user types in the name of a friend, I can't join the two if the friend happens to not exist. So, I use a custom validation class that looks something like this:
class FriendValidator < ActiveModel::Validator
def validate(object)
#lookup user and throw error if not found.
end
end
which will access the variable (object.friends) and (object.enemies)
With something similar for enemies. So therefore, above my has_and_belongs_to_many statements, I have lines that say:
attr_accessor :friends, :enemies #these are attrs because they don't exist within the model's db
validates_with FriendValidator
When I create the form with erb, I have the standard form_for block
<%= form_for(#user) do |f| %>
It seems to me that I can't just stick
<%= f.text_area :friends %>
because friends isn't actually something that will get passed to the User object, but rather a separate table. (Can I, though? Because the attr_accessor is declared in the user's model class?)
So now, we have my main problem. I have two many-to-many tables with a model to its own model class, and I don't know how to ensure that the validation class will take the two attributes, lookup and throw necessary errors, and then add a row to the join tables using the id of the user, rather than the string inputted. What form fields should I use to pass the input to the right place? Where do I change the controller methods so that the input gets sent to the join table rather than the user object?
This definitely seems like a pretty specific situation, so I can't really find an answer in the Rails documentation, which I've been learning from.
My initial impression of this problem has to do with your associations. To me, a user has_many enemies and has_many friends.
friends belong_to user
enemies belong_to user
Not sure if a many to many relationship makes sense in this case. Maybe that's why you are having such a hard time finding an answer online. Just my two cents.

Having trouble in tracing code in Rails

I am currently struggling with this piece of code
#play = current_user.playlist.find_by_id(params[:id])
What does current_user.playlist.find_by_id() mean? How can I trace this code to find current_user, playlist and find_by_id() function?
You could be using devise, if that is the case, current_user returns an instance of class User which is the currently logged in user. Or nil if there is no logged in user.
playlist is a method defined in class User, you should find this class in app/models/user.rb, usually this method would be defined with:
has_one :playlist
or:
belongs_to :playlist
find_by_id is a method defined by Rails for class User, you won't see this directly in file. It is created when you have something like this:
class User < ActiveRecord::Base
ActiveRecord::Base creates a lot of methods more.
Debug your code
I would print each part of the sentence
p current_user
p current_user.playlist
p params[:id]
p current_user.playlist.find_by_id(params[:id])
and check results in my server console, you could spot which of these is the first nil.
In Ruby, everything is an object, and objects have methods. Objects also have types.
Your top-level object there is current_user. This is an instance of some class - you can find out what kind by looking at current_user.class. I suspect you're going to find that it's an instance of User.
So, you find where your User is defined. This is likely a model, defined in app/models/user.rb. This model will specify a number of attributes and associations. In this case, you likely have a has_many :playlists association. What this does is set up an association between a User instance and a number of Playlist instances. Given a user instance, user_instance.playlists accesses this association. Your Playlist model will have a user_id field that associates a playlist with a user record. You can read more about associations in the relevant documentation.
Finally, this association will have a number of methods from Rails. ActiveRecord has a standard set of finders, as well as some "magic" finders like find_by_id, which infer the field to find from based on the method name. find_by_id(params[:id]) is functionally equivalent to something like find_by(:id => params[:id]), but it's a little more English-y. You can read more about this in the Dynamic Finders method of the documentation.
find_by_id will generate the SQL necessary to find the playlist records with that ID that also have a user_id matching current_user's ID. If it finds a matching record, it will instantiate a Playlist record with the data it retrieved and return it. If no matching record is found, it will return nil.

has_many through blowing away the association's metadata on mass association

Hey,
Not a Rails noob but this has stumped me.
With has many through associations in Rails. When I mass assign wines to a winebar through a winelist association (or through) table with something like this.
class WineBarController
def update
#winebar = WineBar.find(params[:id])
#winebar.wines = Wine.find(params[:wine_bar][:wine_ids].split(",")) // Mass assign wines.
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
This will delete every winelist row associated with that winebar. Then it finds all of the wines in wine_ids, which we presume is a comma separated string of wine ids. Then it inserts back into the winelist a new association. This would be expensive, but fine if the destroyed association rows didn't have metadata such as the individual wine bar's price per glass and bottle.
Is there a way to have it not blow everything away, just do an enumerable comparison of the arrays and insert delete whatever changes. I feel like that's something rails does and I'm just missing something obvious.
Thanks.
Your problem looks like it's with your first statement in the update method - you're creating a new wine bar record, instead of loading an existing record and updating it. That's why when you examine the record, there's nothing showing of the relationship. Rails is smart enough not to drop/create every record on the list, so don't worry about that.
If you're using the standard rails setup for your forms:
<% form_for #wine_bar do |f| %>
Then you can call your update like this:
class WineBarController
def update
#winebar = WineBar.find(params[:id])
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
You don't need to explicitly update your record with params[:wine_bar][:wine_ids], because when you updated it with params[:wine_bar], the wine_ids were included as part of that. I hope this helps!
UPDATE: You mentioned that this doesn't work because of how the forms are setup, but you can fix it easily. In your form, you'll want to rename the input field from wine_bar[wine_ids] to wine_bar[wine_ids_string]. Then you just need to create the accessors in your model, like so:
class WineBar < ActiveRecord::Base
def wine_ids_string
wines.map(&:id).join(',')
end
def wine_ids_string= id_string
self.wine_ids = id_string.split(/,/)
end
end
The first method above is the "getter" - it takes the list of associated wine ids and converts them to a string that the form can use. The next method is the "setter", and it accepts a comma-delimited string of ids, and breaks it up into the array that wine_ids= accepts.
You might also be interested in my article Dynamic Form Elements in Rails, which outlines how rails form inputs aren't limited to the attributes in the database record. Any pair of accessor methods can be used.

ActiveRecorded associated model back reference

It is easy to associate a model to another using has_many/belongs_to methods. Let's suppose the following models:
class Movie < ActiveRecord::Base
has_many :actors
end
So, I can find the actors from a given movie instance. But now, given an actor instance obtained through the actors association, I'd like to find the movie instance related in the association. Some method like 'associated_instance' or 'back_association' that would make the following statement return true:
movie_instance.actors[0].**associated_instance** == movie_instance
Is there any built in way to do that?
Thanks
Assuming you have your relationships correctly defined, I'm guessing your encountering the situation where you want to effectively traverse an association but then traverse backwards eg.
movie.actors.movie
With a HABTM relationship rails doesn't build the .movie method for you on the actors collection, but what you can do is extend the association to include such a method:
class Movie < ActiveRecord::Base
has_and_belongs_to_many :actors do
def movie
proxy_owner
end
end
end
There is an excellent guide on association extensions by Mike Gunderloy on the Rails Guides site: http://guides.rubyonrails.org/association_basics.html#association-extensions
Hope I've stabbed at this question in the right direction :)
Yes you can do what you want using
movie_instance.actors[0].movie
The question still remains why you would want to do this as you already have it
...given an actor instance obtained
through the actors association...
If you're using the actors association, you already have the movie object. What is the problem that you're trying to solve here?
My suspicion is that if we finally get to the bottom of what you're trying to accomplish, the answer is going to be "read the docs on 'has_and_belongs_to_many' and/or 'has_many :through'."
Edit:
I just now noticed your clarification below (although movies and plots could be considered many-to-many as well, since they get recycled endlessly).
Assuming that you really are trying to use a many-to-one relationship, is the root of your problem that you forgot the following?
class Plot < ActiveRecord::Base
belongs_to :movie
end

Resources