I have 4 models: SchoolClass, Timetable, Lesson, Reporting:
# class_code :string(255)
class SchoolClass < ActiveRecord::Base
has_many :timetables
end
class Timetable < ActiveRecord::Base
belongs_to :school_class
has_many :lessons
end
class Lesson < ActiveRecord::Base
belongs_to :timetable
has_one :reporting
end
# report_type :string(255)
class Reporting < ActiveRecord::Base
belongs_to :lesson
validates :report_type,
:presence => true,
:inclusion => { :in => %w(homework checkpoint)}
end
How can i validate that each SchoolClass can have only 1 Reporting with type "checkpoint"?
This gets super complicated because of the nesting associations.
I would start by using a custom validation method.
in the SchoolClass model:
validate :only_one_reporting_checkpoint
and then the method:
def only_one_reporting_checkpoint
timetables = self.timetables
reporting_checkpoint = nil
timetables.each do |t|
t.lessons.each do |l|
reporting_checkpoint = true if l.reporting.report_type == "checkpoint"
end
end
if reporting_checkpoint == true
errors.add(:reporting, "exception raised!")
end
end
There, I think that does it. If I understand your problem correctly.
Related
I need some help modeling my models and controller. Here is what I want to achieve:
I want to have a devise user named User (as usual) and a second model named Project. A Project should belong to a single User and at the same time should have many participants. The participants in a project should also be users (with devise registration/login) but the user, that created the project should not be able to participate.
So far, so good. Here comes the tricky part: In my controller I want to be able to write:
def participate
p = Project.find(id: params[:id])
p.participants << current_user unless p.participants.includes?(current_user) && !p.user_id.equal(current_user.id)
if p.save
redirect_back
else
render :project
end
end
This doesn't work because p.participants is not an array and the query (I tried it in rails console) does not check my n:m table.
Here is my current model setup:
class Project < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
belongs_to :user
has_and_belongs_to_many :participants, class_name: "User"
end
class User < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_and_belongs_to_many :projects
end
Finally my migrations:
class CreateProjects < ActiveRecord::Migration[6.0]
def change
create_table :projects, id: false do |t|
t.string :id, limit: 36, primary_key: true
t.string :title
t.belongs_to :user, index: true, foreign_key: true, type: :uuid
t.datetime :published_at
t.timestamps
end
end
end
class CreateJoinTableProjectsUsers < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :projects do |t|
t.index :project_id
t.index :user_id
end
end
end
It is better to use has_many: through instead of has_and_belongs_to_many. This allows you to write cleaner code for validation.
Remove has_and_belongs_to_many from User and Project models
Add has_many :through to User and Project models
rails g model UserProject user:references project:references
rails db:migrate
class User < ApplicationRecord
..
has_many :user_projects
has_many :projects, through: :user_projects
..
end
class Project < ApplicationRecord
..
has_many :user_projects
has_many :participants, through: :user_projects, source: 'user'
..
end
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
end
Add validation to UserProject model
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
validate :check_participant
private
def check_participant
return if project.participants.pluck(:id).exclude?(user.id) && project.user != user
errors.add(:base, 'You cannot be participant')
end
end
Update participate method
def participate
p = Project.find(id: params[:id])
begin
p.participants << current_user
redirect_back
rescue ActiveRecord::RecordInvalid => invalid
puts invalid.record.errors
render :project
end
end
I have 2 Models: Post, User. The User cannot like his post, so how can i prevent creating the instance of the Model Like (user_id: creator, post_id:created by the "creator") ?
You can validate that in your Like model:
class Like < ActiveRecord::Base
validates_presence_of :user_id, :post_id
validate :voter_not_author
private
def voter_not_author
if self.user_id == self.post.try(:user_id)
self.errors[:base] << "Author can't be the voter"
end
end
end
Another implementation I found...
#app/models/like.rb
class Like < ActiveRecord::Base
validates :user_id, exclusion: {in: ->(u) { [Post.find(u.post_id).user_id] }} #-> user_id cannot equal post.user_id
end
If you wanted to get rid of the db query, you'd have to associate the models and use inverse_of:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :likes
end
#app/models/like.rb
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :post, inverse_of: :likes
validates :user_id, exclusion: {in: ->(u) { u.post.user_id }}
end
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :likes, inverse_of: :post
end
I'm somewhat confused by my options for custom validations in Rails 3, and i'm hoping that someone can point me in the direction of a resource that can help with my current issue.
I currently have 3 models, vehicle, trim and model_year. They look as follows:
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
end
class ModelYear < ActiveRecord::Base
attr_accessible :value
has_many :model_year_trims
has_many :trims, :through => :model_year_trims
end
class Trim < ActiveRecord::Base
attr_accessible :value, :model_id
has_many :vehicles
has_many :model_year_trims
has_many :model_years, :through => :model_year_trims
end
My query is this - when I am creating a vehicle, how can I ensure that the model_year that is selected is valid for the trim (and vice versa)?
you can use custom validation method, as described here:
class Vehicle < ActiveRecord::Base
validate :model_year_valid_for_trim
def model_year_valid_for_trim
if #some validation code for model year and trim
errors.add(:model_years, "some error")
end
end
end
You can use the ActiveModel::Validator class like so:
class VehicleValidator < ActiveModel::Validator
def validate(record)
return true if # custom model_year and trip logic
record.errors[:base] << # error message
end
end
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
include ActiveModel::Validations
validates_with VehicleValidator
end
I have the following two models:
class Project < ActiveRecord::Base
has_one :start_date, :class_name => 'KeyDate', :dependent => :destroy
has_one :end_date, :class_name => 'KeyDate', :dependent => :destroy
and
class KeyDate < ActiveRecord::Base
belongs_to :project
Given a certain key date from the database related to a project:
#key_date = KeyDate.find(:first)
is there a way to introspect the relationship to check if the #key_date is related to the project as start_date or as end_date?
A nice way would be to use single table inheritance for the KeyDate class
class KeyDate < ActiveRecord::Base
belongs_to :project
end
class StartDate < KeyDate
end
class EndDate < KeyDate
end
class Project < ActiveRecord::Base
has_one :start_date, :dependent => :destroy
has_one :end_date, :dependent => :destroy
end
class CreateKeyDatesMigration < ActiveRecord::Migration
def up
create_table :key_dates do |t|
t.date :date
t.string :type #this is the magic column that activates single table inheritance
t.references :project
end
end
…
end
this lets you do
#key_date = KeyDate.find(:first)
#key_date.type # => "StartDate"
One clean way to do what you want is to create STI:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
See one example I gave here:
Rails devise add fields to registration form when having STI
Just thinking aloud...
class KeyDate < ActiveRecord::Base
belongs_to :project
def start_date?
project.start_date == self
end
def end_date?
project.start_date == self
end
date_type
[:start_date, :end_date].find {|sym| send("#{sym}?") }
end
end
To be honest I can't see why you'd ever need this. Surely you're always going to have a handle on a project anyway?
Consider:
class Person < ActiveRecord::Base
class << self
def setup
has_one :address, :as => :addressable
end
end
end
class Employee < Person
setup
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
# Shouldn't this be 'Employee'? Is it possible to override?
Employee.create.address.create.addressable_type == 'Person'
Edit: I got confused for a while there. This is not really STI, it's just inheritance, as Employee has its own table.
Thanks!
Bingo:
class Person < ActiveRecord::Base
self.abstract_class = true
end