conditional validation in one model but two different forms - ruby-on-rails

I have one model but two different forms ,one form i am saving through create action and another one through student_create action.I want to validate a field in student_create action form and leave other one free.How do i do it?Any help will be appreciated
class BookController < ApplicationController
def create
if #book.save
redirect_to #book #eliminated some of the code for simplicity
end
end
def student_create
if #book.save #eliminated some of the code for simplicity
redirect_to #book
end
end
I have tried this but it didnt work
class Book < ActiveRecord::Base
validates_presence_of :town ,:if=>:student?
def student?
:action=="student_create"
end
end
Also this didnt work
class Book < ActiveRecord::Base
validates_presence_of :town ,:on=>:student_create
end

In the one that should not be validated you do this:
#object = Model.new(params[:xyz])
respond_to do |format|
if #object.save(:validate => false)
#do stuff here
else
#do stuff here
end
end
the save(:validate => false) will skipp the validation.

I was able to acomplish it what i wanted to do by giving it an option :allow_nil=>true

Sounds like you have two types of books. not sure what your domain logic is but the normal flow I would do nothing.
class Book < ActiveRecord::Base
end
Then for the path you want an extra validation you could do this:
class SpecialBook < Book
validates :town, :presence => true
end
If this is the case you might want to consider Single Table Inheritance.
In another case you might want to save the student_id on the book.
Then
class Book < ActiveRecord::Base
validate :validate_town
private
def validate_town
if student_id
self.errors.add(:town, "This book is evil, it needs a town.") if town.blank?
end
end
end

Related

Rails - Polymorphic Association to Create Different Profiles

Attempting to make it so that when a user is created, based on whether they select to be a student or a corporate, rails will create that user either a student profile or a corporate profile.
Ive tried to set it up using Polymorphic associations however cant figure out how to generate the profile at the model layer based on what is selected in the view.
Models
class User < ActiveRecord::Base
has_secure_password
has_one :student_profile, dependent: :destroy
has_one :corporate_profile, dependent: :destroy
has_many :searches, dependent: :destroy
#attr_accessor :profile_type - removed due to Rails 4, pushed strong params in controller
before_create :create_profile
def create_profile
if profile_type == 1
build_student_profile
else
build_corporate_profile
end
end
end
Student and Corporate Profiles
class CorporateProfile < ActiveRecord::Base # or possibly inherit from ActiveRecord::Base if not using inheritance
belongs_to :user
end
class StudentProfile < ActiveRecord::Base # or possibly inherit from ActiveRecord::Base if not using inheritance
belongs_to :user
end
View
Here i have two radio buttons to decide which user type on the sign up form
<%= bootstrap_form_for(#user) do |f| %>
<div class="field">
<%= f.form_group :gender, label: { text: "Gender" }, help: "Are you a corporate or a student?" do %>
<p></p>
<%= f.radio_button :profileable, 1, label: "Student", inline: true %>
<%= f.radio_button :profileable, 2, label: "Corporate", inline: true %>
<% end %>
</div>
Users Controller
class UsersController < ApplicationController
def index
#users = User.paginate(page: params[:page], :per_page => 5).includes(:profile)
end
def show
if params[:id]
#user = User.find(params[:id])
# .includes(:profile)
else
#user = current_user
end
#searches = Search.where(user_id: #user).includes(:state, city: [:profile])
end
def new
#user = User.new
##corporateprofile = Corporateprofile.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to widgets_index_path
else
redirect to '/signup'
end
end
private
def user_params
params.require(:user).permit(:firstname, :lastname, :email, :password, :profile_type)
end
end
And there is no passing code on the controller (as im stuck on that). Any better suggestion or ways to fix this would be much appreciated!
Cheers
First of all, you want to rename your profile classes to StudentProfile and CorporateProfile. This will necessitate running migrations to change your table names too.
The answer to this question depends on how different you want StudentProfile and CorporateProfile to be. If they are completely different or even mostly different, make them separate classes. If they are mostly the same (in other words, they share many of the same methods) you should create a Profile (or UserProfile) model and have StudentProfile and CorporateProfile inherit from this model.
As for implementation, it should look something like this:
# user.rb
class User < ActiveRecord::Base
has_one :student_profile
has_one :corporate_profile
attr_accessor :profileable #probably change this to profile_type. Using attr_accessible because we want to use it during creation, but no need to save it on the user model, although it may not be a bad idea to create a column for user model and save this value.
before_create :create_profile
def create_profile
if profileable == 1
build_student_profile
else
build_corporate_profile
end
end
end
# student_profile.rb
class StudentProfile < UserProfile # or possibly inherit from ActiveRecord::Base if not using inheritance
belongs_to :user
# other student profile stuff here
end
And corporate profile model would look the same as student profile.
Also, you should be using Rails 4 at this point, especially if you're learning and don't understand controllers and params, as this is pretty different between rails 3 and 4. No use in learning something that's outdated, right?
Edit: I should mention, I don't thing you're understanding rails polymorphism. A model should be polymorphic when it will belong to multiple models, not when it will have different subclasses.
For example, if your app has a Like model and something else like a Post model, and a user can like other users' profiles or posts, that might be a good candidate for polymorphism, because Like may belong to StudentProfiles or CorporateProfiles or Posts.

remove specific user from joined table

In Ruby on Rails I have a user models and a jobs model joined through a different model called applicants. I have a button for the users when they want to "remove their application for this job" but I don't know how to remove the specific user, and for that matter I don't know if I'm doing a good job at adding them either (I know atleast it works).
user.rb
class User < ActiveRecord::Base
...
has_many :applicants
has_many:jobs, through: :applicants
end
job.rb
class Job < ActiveRecord::Base
...
has_many :applicants
has_many:users, through: :applicants
end
applicant.rb
class Applicant < ActiveRecord::Base
belongs_to :job
belongs_to :user
end
when someone applies for a job my jobs controller is called:
class JobsController < ApplicationController
...
def addapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id)
redirect_to #job
end
...
end
Does that .update indicate that whatever is there will be replaced? I'm not sure if I'm doing that right.
When someone wants to remove their application I want it to go to my jobs controller again but I'm not sure what def to make, maybe something like this?
def removeapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id).destroy
redirect_to #job
end
does it ave to sort through the list of user_ids save them all to an array but the one I want to remove, delete the table then put them all back in? I'm unsure how this has_many works, let alone has_many :through sorry for the ignorance!
thanks!
Let's assume the user will want to remove their own application. You can do something like this:
class UsersController < ApplicationController
def show
#applicants = current_user.applicants # or #user.find(params[:id]), whatever you prefer
end
end
class ApplicantsController < ApplicationController
def destroy
current_user.applications.find(params[:id]).destroy
redirect_to :back # or whereever
end
end
And in your view:
- #applicants.each do |applicant|
= form_for applicant, method: :delete do |f|
= f.submit
Don't forget to set a route:
resources :applicants, only: :destroy
Some observations, I would probably name the association application instead of applicant. So has_many :applications, class_name: 'Applicant'.

Child not being created from Parent model?

I have a checkbox that if checked allows my child resource called Engineer to be created. I'm trying to create it through my model since that's where I can call the after_save method.
Here is my code:
models/user.rb
class User < ActiveRecord::Base
has_many :armies
has_many :engineers
end
models/army.rb
class Army < ActiveRecord::Base
has_many :engineers
attr_reader :siege
after_save :if_siege
private
def if_siege
if self.siege
Engineer.create!( :user_id => current_user.id, :army_id => self.id )
end
end
end
models/engineer.rb
class Engineer < ActiveRecord::Base
belongs_to :user
belongs_to :army
end
controllers/armies_controller.rb
def new
#army = Army.new
end
def create
#army = current_user.armies.build(params[:army])
if #army.save
redirect_to new_army_path
else
render :new
end
end
end
This gives me an error though for my if_siege method:
undefined local variable or method `current_user'
How can I fix this or is there another way to do this? Not sure if this should go in the controller or model but I only can wrap my head around putting this in the model.
Thanks.
Add belongs_to :user to the Army model
In Army#if_siege, update Engineer.create! as follows
Engineer.create!( :user_id => self.user.id, :army_id => self.id )
First, the current_user object won't exist within the context of the Model layer unless your authentication is doing something to make it available. This is usually a non Threadsafe approach though. Maybe for you this isn't the issue.
Current User Instantiation
Having said that, one way (perhaps not the ideal way) to address this is by creating an attr_accessor in the model on the object called Army. Then set the current_user to this in the Army new action in the controller where the current_user instance is available.
# in the Army model
attr_accessor :the_user
# in the Army Controller
#army = Army.new(:the_user => current_user.id)
You will also have to add a hidden field to store this value in your view to carry this through to the create action.
Just an observation, but I'm fairly sure in the "if_seige" method the self calls are redundant. self should already be scoped to the Army object in that method.

Rails 3: How to create a new nested resource?

The Getting Started Rails Guide kind of glosses over this part since it doesn't implement the "new" action of the Comments controller. In my application, I have a book model that has many chapters:
class Book < ActiveRecord::Base
has_many :chapters
end
class Chapter < ActiveRecord::Base
belongs_to :book
end
In my routes file:
resources :books do
resources :chapters
end
Now I want to implement the "new" action of the Chapters controller:
class ChaptersController < ApplicationController
respond_to :html, :xml, :json
# /books/1/chapters/new
def new
#chapter = # this is where I'm stuck
respond_with(#chapter)
end
What is the right way to do this? Also, What should the view script (form) look like?
First you have to find the respective book in your chapters controller to build a chapter for him. You can do your actions like this:
class ChaptersController < ApplicationController
respond_to :html, :xml, :json
# /books/1/chapters/new
def new
#book = Book.find(params[:book_id])
#chapter = #book.chapters.build
respond_with(#chapter)
end
def create
#book = Book.find(params[:book_id])
#chapter = #book.chapters.build(params[:chapter])
if #chapter.save
...
end
end
end
In your form, new.html.erb
form_for(#chapter, :url=>book_chapters_path(#book)) do
.....rest is the same...
or you can try a shorthand
form_for([#book,#chapter]) do
...same...
Try #chapter = #book.build_chapter. When you call #book.chapter, it's nil. You can't do nil.new.
EDIT: I just realized that book most likely has_many chapters... the above is for has_one. You should use #chapter = #book.chapters.build. The chapters "empty array" is actually a special object that responds to build for adding new associations.
Perhaps unrelated, but from this question's title you might arrive here looking for how to do something slightly different.
Lets say you want to do Book.new(name: 'FooBar', author: 'SO') and you want to split some metadata into a separate model, called readable_config which is polymorphic and stores name and author for multiple models.
How do you accept Book.new(name: 'FooBar', author: 'SO') to build the Book model and also the readable_config model (which I would, perhaps mistakenly, call a 'nested resource')
This can be done as so:
class Book < ActiveRecord::Base
has_one :readable_config, dependent: :destroy, autosave: true, validate: true
delegate: :name, :name=, :author, :author=, :to => :readable_config
def readable_config
super ? super : build_readable_config
end
end

Model and controller design approach for polymorphic assocation

Below I have outlined the structure of a polymorphic association.
In VacationsController I put some comments inline describing my current issue. However, I wanted to post this to see if my whole approach here is a little off. You can see in business_vacations_controller and staff_vacations_controller that I've had to make 'getters' for the model and controller so that I can access them from within vacations_model so I know which type of object I'm dealing with. Although it works, it's starting to feel a little questionable.
Is there a better 'best practice' for what I'm trying to accomplish?
models
vacation.rb
class Vacation < ActiveRecord::Base
belongs_to :vacationable, :polymorphic => true
end
business.rb
class Business < ActiveRecord::Base
has_many :vacations, :as => :vacationable
end
staff.rb
class Staff < ActiveRecord::Base
has_many :vacations, :as => :vacationable
end
business_vacation.rb
class BusinessVacation < Vacation
end
staff_vacation.rb
class StaffVacation < Vacation
end
controllers
business_vacations_controller.rb
class BusinessVacationsController < VacationsController
private
def controller_str
"business_schedules"
end
def my_model
BusinessVacation
end
def my_model_str
"business_vacation"
end
end
staff_vacations_controller.rb
class StaffVacationsController < VacationsController
private
def controller_str
"staff_schedules"
end
def my_model
StaffVacation
end
def my_model_str
"staff_vacation"
end
end
vacations_controller.rb
class VacationsController < ApplicationController
def create
# Build the vacation object with either an instance of BusinessVacation or StaffVacation
vacation = #class.new(params[my_model_str])
# Now here's the current issue -- I want to save the object on the association. So if it's a 'BusinessVacation' object I want to save something like:
business = Business.find(vacation.vacationable_id)
business.vacations.build
business.save
# But if it's a 'StaffVacation' object I want to save something like:
staff = Staff.find(vacation.vacationable_id)
staff.vacations.build
staff.save
# I could do an 'if' statement, but I don't really like that idea. Is there a better way?
respond_to do |format|
format.html { redirect_to :controller => controller_str, :action => "index", :id => vacation.vacationable_id }
end
end
private
def select_class
#class = Kernel.const_get(params[:class])
end
end
It feels like a lot of hoops to jump through in the VacationsController to make it aware of the context. Is there a reason that the StaffVacationsController and BusinessVacationsController couldn't each have a #create action and the views would submit to whichever is appropriate? These actions would already know the model context and be able to redirect to the appropriate url afterward.

Resources