Can't figure out how to update a model with new tables? - ruby-on-rails

I want to update my user Model with new tables, The tables being the following.
I need the user to be able to choose a location upon signing up.
I also need the users to be able to select up to 3 genres of music they'd prefer to listen to while signing up.
Now, The problem is I am going to let users select which genre they like, I also want them to be able to click on the genre link to see what other users on the website also enjoy that type of genre. So would that mean the genre would have to be a model while the location should be a table?
This is what I have in my user.rb,
class User < ActiveRecord::Base
has_secure_password
before_save { self.email = email.downcase }
validates :first_name, :last_name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :first_name, :last_name, presence: true, uniqueness: true
validates_inclusion_of :age, in: 10..100
validates :password, presence: true, length: { minimum: 4 }, allow_nil: true
has_many :posts
has_attached_file :profile_picture, :styles => { :medium => "300x300>", :thumb => "100x100>" },
:default_url => "app/assets/images/missing.png",
:path => ":rails_root/public/system/:class/:attachment/:id_partition/:style/:filename"
validates_attachment_content_type :profile_picture, :content_type => /\Aimage\/.*\Z/
def self.search(query)
where("email like ?", "%#{query}%")
end
end
If you guys need any other code just let me know, And thanks in advance!

If you're going to ask for help the least you could do is clean up the spacing and indentation. Group all of your validations together, etc.
As for how you want to setup your sign in process it looks like you're building your own. Feel free to post all files related to your sign in process but it'd be a lot easier to use devise and customize it's views (click for tutorial). You can have your users add their location & genre preferences here.
Before I go any further let's get on the same page for definitions. I think you mean columns instead of tables in your question above but let me know if I misunderstood you.
Moving along: you have a number of ways you can setup your genres. I'd recommend creating a new genre table (not column) and associating that with your users through a 'through' table (click for solution) I know this link is old but it'll set you on the right direction. This might be a lot for you to start with especially if you haven't done associations before but give it a try.
If all else fails you can reduce the number of features to start with and ask your users if they'd want that feature. Good luck!

You'd better to make new Genre model with genres as table. When user want to select its location you can add has_one association when user only choose one location.
genre.rb
attributes: id, name, decription
class Genre < ActiveRecord::Base
has_many: genres_users
has_many: users, through: genres_users
end
genres_user.rb
attributes: id, user_id, genre_id
class GenresUser < ActiveRecord::Base
belongs_to :user
belongs_to :genre
end
user.rb
attributes: your attribute field
class User < ActiveRecord::Base
has_many: genres_users
has_many: genres, through: genres_users
has_one: location
end
location.rb
class Location < ActiveRecord::Base
belongs_to :user
end
For details of generating migration and association basics

I want to update my user Model with new tables
As mentioned in other answers, each Model should represent a single table.
This is not always the case; STI models will not have tables, and you can use a model as you would a class (without DB integration).
However, the point is that you cannot just "add" tables to a model. You can add associations, which is a totally different concept. I'll explain it here:
ORM
To better understand what you're asking, it will help knowing that Rails is an abstraction layer for relational database & web server software. This means that when you deal with data, you're not pulling from a database, but dealing with objects which can be populated in various ways.
Rails is an MVC (Model View Controller) framework:
This means that your "data" is not tied to tables etc, it's bound to models which are meant to provide you with objects that you can use within your application's business logic.
In other words, you shouldn't be thinking of your Rails app as a way to access data, but to manipulate objects.
This is done with the ORM called ActiveRecord. ActiveRecord is the thing responsible for when you call the likes of User.find, and is behind the Rails' ActiveRecord associations.
What you're really asking is "How do I add associations to my User model?"
--
I need the user to be able to choose a location upon signing up.
I also need the users to be able to select up to 3 genres of music they'd prefer to listen to while signing up.
Here's what I'd do:
#app/models/user.rb
class User < ActiveRecord::Base
# columns id | user | etc | location_id | created_at | updated_at
belongs_to :location
has_and_belongs_to_many :genres
end
#app/models/location.rb
class Location < ActiveRecord::Base
# columns id | etc | etc | created_at | updated_at
has_many :users
end
#app/models/genre.rb
class Genre < ActiveRecord::Base
has_and_belongs_to_many :users
end
Now, there is another answer which mentions having a model called genre_user.rb. This is for has_many :through, and I don't believe is applicable in this case.
I'd recommend using a has_and_belongs_to_many join table, and simply validate the number of genres to 3 (I'll let you work that out):
To do this, you need a table called genres_users with the attributes genre_id and user_id. This will give you the ability to call:
$ #user = User.find 1
$ #user.genres #-> 1,2,6

Related

Active Record Table Joins

I have been racking my brain all day and can't get this to work. I am very new to ruby and rails so my apologies for any silly errors.
My problem is I am joining 3 tables together to get a #students object. This works but if I call for example #student.name then 'name' doesn't exist.
Below is my code:
Controller
note I have tried using .includes and .join and the same problem happens.
class MyprojectController < ApplicationController
def show
#project = Project.find(params[:id])
#dateformat = '%b %e - %H:%M'
#user = Student.includes("INNER JOIN researchers ON students.researcher_id = researchers.id
INNER JOIN users ON researchers.user_id = users.id").where('user_id = ?', current_user.id)
end
User Model
class User < ApplicationRecord
include EpiCas::DeviseHelper
has_many :event_registrations
has_many :events, through: :event_registrations
belongs_to :project
has_many :researchers
#has_many :students, :through => :researchers
#has_many :supervisors, :through => :researchers
# def self.authenticate(username)
# where(username: username).first
# end
end
Researcher Model
class Researcher < ApplicationRecord
#belongs_to :project
belongs_to :user
has_many :supervisor
has_many :students
end
Student Model
class Student < ApplicationRecord
#Must have the following
validates :name, :email, :surname, :email, :supervisor, :registration_number, presence: true
#ensures unique email addresses
validates :email, uniqueness: true
#assosiations
belongs_to :researcher
end
So every student has a researcher_id and every researcher has a user_id. So the joins should go student->researcher->user and then I want to be able to use all the attributes from all tables in an #user object.
I tried using Student.join(:researcher, :user) but that tried to do a join from the Student table to the researchers table and then tried to join the user table by using a user_id from the student table (but of the user_id is in the researcher table). So i have just done the query myself.
All the data seems to be there but as 'raw attributes'.
Any help would be greatly appreciated!
-James
Rather than try and join things into one return (like you would in sql) use includes so that you can access all your data in fewer queries but you still have access to your data in objects. The point of using an ORM like ActiveRecord is to be able to access your data using objects. The downside of using an ORM is that sometimes it's not as efficient at getting you the exact data you want, because the data is pushing into objects. Using includes provides a sort of middle ground where you can access the data you require in objects and you don't necessarily have to run queries for each association.
Try something like (depending on how you're getting your user id -- I'm assuming from project):
#user = User.includes(researcher: :student).find(project.user_id)
And then you can access things through the normal rails associations:
researcher = #user.researcher
student = researcher.student
I hope that helps and best of luck!

Rails: Validating an array of ids

Application
I am working on a college admissions system where a student can make an application to up to 5 courses. The way I have designed this is to have an Application model and a CourseApplication model. An application can consist of many course_applications:
class Application < ActiveRecord::Base
# Assosciations
belongs_to :user
has_many :course_applications, dependent: :destroy
has_many :courses, through: :course_applications
has_one :reference
# Validations
validates :course_applications, presence: true
end
Course Application
class CourseApplication < ActiveRecord::Base
# Intersection entity between course and application.
# Represents an application to a particular course, has an optional offer
# Associations
belongs_to :application
belongs_to :course
has_one :offer, dependent: :destroy
end
I want to make sure that a student cannot apply to the same course twice. I have already done some research but have had no success. This is the UI for a student making an application:
Screenshot of application form
When a course is selected, the course id is added to an array of course ids:
def application_params
params.require(:application).permit(:user, course_ids: [])
end
Right now a student can select the same course twice, I want to prevent them from doing this. Any help is much appreciated.
For the rails side, I would do on the CourseApplication
validates :course, uniqueness: { scope: :application }
For your reference this can be found at: http://guides.rubyonrails.org/active_record_validations.html#uniqueness
Also suggest on the database side to make a migration
add_index :course_applications, [:course, :application], :unique => true
For the validating on the form you will have to write javascript to make sure two things are selected, this will just return an error when someone tries to do it.

mongoid-4 how to validate uniqueness of belongs_to in 1 to 1 association

I have a 1-to-1 association between 2 mongoid models and I keep getting duplicates, that is having more than one child record(card) with same parent_id(that is user). I have tried validating uniqueness of the belongs_to association has shown below, but it doesn't work.
class User
include Mongoid::Document
field :name, type: String
has_one :card
end
The second model:
class Card
include Mongoid::Document
field :name, type: String
belongs_to :user
validates :user, :uniqueness => {:scope => :user_has_child}
def user_has_child
q = Segment.where(drop_id: {'$ne' => nil})
s = q.map(&:drop_id)
errors.add(:drop_id, "this user already has a card") if s.include?(:drop_id)
end
end
The syntax is more simple. You just want to make sure there are no 2 documents with the same user_id
class Card
belongs_to :user
validates_uniqueness_of :user
You need to use scope if you want the uniqueness of a tuple of n fields. For example, if a User can have at most one card per year, you can write
class Card
field :year
belongs_to :user
validates_uniqueness_of :user, scope: [:year] # A user can have one card per year
Note that validations apply when you save the model ie. you try to persist the changes. Calling .save will return true or false if some validations fail, but the object in memory is always modified! This is so, for example, you can display previous values in the HTML input fields, so the user knew what he wrote and can fix it (otherwise he'd have to re-write all his information in case of a single mistake)
Also, Mongoid by default handles dirty tracking (this is now the doc for v5.0 but it was the same for Mongoid 4). That is to say, you can call .changed? .changes, etc on the object in memory to see what are the changes compared to the object in the DB.

How to make a has_many_through association mandatory for one member?

I have the following models :
class City < ActiveRecord::Base
has_many :cities_regions_relationships
has_many :regions, through: :cities_regions_relationships
end
class Region < ActiveRecord::Base
has_many :cities_regions_relationships
has_many :cities, through: :cities_regions_relationships
end
class CitiesRegionsRelationship < ActiveRecord::Base
belongs_to :city
belongs_to :region
validates :city_id, presence: true
validates :region_id, presence: true
end
What I want is to make it impossible to create a city without it linked to a region. However, before attempting that, I have to be able to create a relationship on a city that's not yet saved.
I tried this in the console (with a region already created)
c = City.new(name: "Test")
c.cities_region_relationship.build(region_id: Region.first.id)
c.save
However, this fails because the relationship doesn't have a city_id (which is normal because I didn't save the city yet, it doesn't have an ID).
I could try other ways, but I will always have the same problem : How do I create a relationship on a new object, not yet saved in database ?
If you have other completely different solutions to my initial problem (Forcing cities to have at least one region), don't hesitate to suggest something entirely different.
You do not need to build a cities_region_relationship. By passing region_ids to a new City instance, this will create the cities_region_relationship for you.
You can try in the console:
c = City.new(name: "Test", region_ids: [an array of existing region ids])
c.save
for the validation, you can define a new validate method that checks if self.regions.blank? like mentioned in the SO post in my comment above.

More than one belongs_to

I’m new to Rails and I’ve been reading Michael Hartl’s tutorial and I am not sure if I’m doing this correctly. I have Users, Posts, and Categories:
Users can create Posts and Categories
A Post can be assigned to only one Category.
Currently, when the User creates a post they type in the Category (let’s just say the Category will always exist in the database) and from there it looks up the ID of the Category and passes that along to the post creation. This is what I’m running to create the post and assign it to a Category in my Post_Controller:
category_id = Category.find_by_name(post_params[:category])
#post = current_user.posts.build(title: post_params[:title], content: post_params[:content], category_id: category.id)
My question is: Is this the proper way to enter data with two belong_to’s? I’ve dug around and I can’t find a simple answer to this. To me it seems that passing a category ID like this is not secure but I don’t know of another way to do this. Here’s my basic Model information (just the belong_to’s, has_many, etc). Please let me know if you need more:
class User < ActiveRecord::Base
attr_accessible :username, :email, :password, :password_confirmation
has_secure_password
has_many :posts
has_many :categories
class Post < ActiveRecord::Base
attr_accessible :title, :content, :category_id
belongs_to :user
belongs_to :category
class Category < ActiveRecord::Base
attr_accessible :name
belongs_to :users
has_many :posts
validates :name, presence: true, uniqueness: true, length: {maximum:30}
validates :user_id, presence: true
Is this the proper way to enter data with two belong_to’s?
It's fine. Does it work? If it works, it's fine. Maybe there are things you could do to tighten it up later, if you find you're making the Category.find... call a lot, but, you're also just starting, so don't worry about stuff like that too much.
To me it seems that passing a category ID like this is not secure but I don’t know of another way to do this.
Again, don't worry about that too much at the moment. If you want to read up on Rails security, though, take a look at this.
If all the information you need to create a new post is in post_params, it seems you're doing that Category.find_by_name unnecessarily. You should be collecting the category_id in your params rather than the category's name.
And then in your PostsController:
#post = current_user.posts.build(post_params)
Just remember to allow :category_id along with the other regular attributes in your post_params and you'll be golden.

Resources