Associations in Rails 4 - ruby-on-rails

I am developing a web application for task management using ruby on rails.
I have two models user and task. The models look like this
class User < ActiveRecord::Base
has_many :tasks
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def role?(role_name)
role == role_name
end
def self.assigned_user(task_params)
User.where("name = ?", task_params["assigned_to"])
end
end
class Task < ActiveRecord::Base
belongs_to :user
end
what i want is assign task to user when a task is created. But i don't know how to do it in create action of tasks_controller. My create action looks like this
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
#user = User.assigned_user(task_params)
#user.tasks << Task.last
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render :show, status: :created, location: #task }
else
format.html { render :new }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
Whenever admin assigns a task when its created, it gives an error saying undefined method tasks for #<User::ActiveRecord_Relation:0x007fe7b1c60610>. Does anyone can help what can be the problem with this?

The error it is giving you is because the where method is returning a collection of users and you want just one user:
def self.assigned_user(task_params)
User.where("name = ?", task_params["assigned_to"]).first
end
You probably want to revise your code as I guess you could have problems if name is not unique. Make sure the field you use to retrieve the user is unique.
The code for creation can look like this:
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
#user = User.assigned_user(task_params)
#user.tasks << #task
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render :show, status: :created, location: #task }
else
format.html { render :new }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end

Well the error, speaks for itself:
undefined method tasks for User::ActiveRecord_Relation:0x007fe7b1c60610>
def self.assigned_user(task_params)
User.where("name = ?", task_params["assigned_to"])
end
where method returns an array(ActiveRecord_Relation), then since you need only to fetch one object, it shoud be:
User.where(name: task_params["assigned_to"]).first
or even better:
User.find_by(name: task_params["assigned_to"])
Also this line:
#user.tasks << Task.last
Will do a query in database unnecessarily.
So it should be changed to:
#user.tasks << #task
Hope it helps!!

Related

Can I validates_uniqueness_of email in two models at once in rails?

I have a model for a client, when I create a new client it creates a User with the client email. The same thing happens when I create an Affiliate. Can I use validates_uniqueness_of the email in both Client and user at the same time?
Or should I do something like, before save check if there is a User with the same email, and print an error?
I tried this, but it doesn't work
validate :uniqueness_of_user
private
def uniqueness_of_user
#user = User.find_by_email(:email)
if #user.present?
errors.add(:email, "Bang!")
end
end
Edit:
This is the controller:
def create
#affiliate = Affiliate.new(affiliate_params)
respond_to do |format|
if verify_recaptcha(model: #affiliate) && #affiliate.save
#user = User.create!(user_parameter)
pplicationMailer.confirmation(#affiliate).deliver_now
format.html {redirect_to :back, notice: 'Thanks for your submission, we will be in touch shortly. Check your email for your affiliate number and password.' }
format.json { render :show, status: :created, location: #affiliate }
else
format.html { render :signup, layout: "sign-ups" }
format.json { render json: #affiliate.errors, status: :unprocessable_entity }
end
end
end
I would have preferred to save the email_address in a different table. for example: address
class Address < ApplicationRecord
belongs_to :affilat
belongs_to :client
validates :email,
presence: true
uniqueness: true
end
and use nested form to save date here.
Check below code for a good article about this case, I copied the code but for full explanation check the article link :
# app/models/concerns/validate_identifier_uniqueness_across_models.rb
module ValidateIdentifierUniquenessAcrossModels
extend ActiveSupport::Concern
##included_classes = []
included do
##included_classes << self
validate :identifier_unique_across_all_models
end
private
def identifier_unique_across_all_models
return if self.identifier.blank?
##included_classes.each do |klass|
scope = klass.where(identifier: self.identifier)
if self.persisted? && klass == self.class
scope = scope.where('id != ?', self.id)
end
if scope.any?
self.errors.add :identifier, 'is already taken'
break
end
end
end
end
# app/models/product.rb
class Product < ActiveRecord::Base
include ValidateIdentifierUniquenessAcrossModels
end
# app/models/category.rb
class Category < ActiveRecord::Base
include ValidateIdentifierUniquenessAcrossModels
end
Reference:
https://www.krautcomputing.com/blog/2015/03/29/rails-validate-uniqueness-across-models/
UPDATE:
Solution 1 If data are saved at the same time, then the best thing is to include the data saved together in a Transaction as below:
def create
email_validity
#affiliate = Affiliate.new(affiliate_params)
respond_to do |format|
ActiveRecord::Base.transaction do
if verify_recaptcha(model: #affiliate) && #affiliate.save
#user = User.create!(user_parameter)
if #user.valid?
ApplicationMailer.confirmation(#affiliate).deliver_now
format.html {redirect_to :back, notice: 'Thanks for your submission, we will be in touch shortly. Check your email for your affiliate number and password.'}
format.json {render :show, status: :created, location: #affiliate}
else
email_validity = false
raise ActiveRecord::Rollback
end
else
format.html {render :signup, layout: "sign-ups"}
format.json {render json: #affiliate.errors, status: :unprocessable_entity}
end
end
if email_validity == false
respond_to do |format|
format.html {redirect_to :back, notice: 'Sorry, email is in use!'}
# you can add your json return as you like
end
end
end
end
Reference:
http://api.rubyonrails.org/classes/ActiveRecord/Rollback.html
Solution 2 before saving to affiliate or creating a user, check in both tables for email existence in your controller.

Can't associate post to Devise user

I'm having extreme difficulty associating post to a user registered in devise.
I generated a post scaffold and got everything set up correctly in Devise.
I added a migration to the post that included a user_id field
The user model has_many :posts
The Post model belongs_to :user
For some reason I cannot connect the user with the post. Am I missing something?
thanks all!
My controller for posts
def create
#user = User.find(params[:id])
#post = #user.posts.create(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render action: 'show', status: :created, location: #post }
else
format.html { render action: 'new' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
First of all you need a user to associate a post:
#user = User.find(params[:id]) # or just use current_user as you are using Devise
As long as you have has_many association you can do the following:
#post = #user.posts.build(params[:post]) # to return newly created object without saving it to the database
#post = #user.posts.create(params[:post]) # to create and save record to the database
That's it.

How to get name of model linked from another model in Rails?

I have set up 2 models in Rails:
class Category < ActiveRecord::Base
attr_accessible :name
has_many :platforms
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url, :country
validates :name, :presence => true, :length => { :minimum => 5 }
validates :url, :presence => true, :length => { :minimum => 5 }
belongs_to :categories
end
This is my platform controller :
class PlatformsController < ApplicationController
# GET /platforms
# GET /platforms.json
def index
#platforms = Platform.all
#categories = Category.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #platforms }
end
end
# GET /platforms/1
# GET /platforms/1.json
def show
#platform = Platform.find(params[:id])
#categories = Platform.categories
respond_to do |format|
format.html # show.html.erb
format.json { render json: #platform }
end
end
# GET /platforms/new
# GET /platforms/new.json
def new
#platform = Platform.new
#categories = Category.all
respond_to do |format|
format.html # new.html.erb
format.json { render json: #platform }
end
end
# GET /platforms/1/edit
def edit
#platform = Platform.find(params[:id])
#categories = Category.find(:all)
end
# POST /platforms
# POST /platforms.json
def create
#platform = Platform.new(params[:platform])
##categories = Category.new(params[:name])
#categories = #platform.categories.create(params[:categories])
respond_to do |format|
if #platform.save
format.html { redirect_to #platform, notice: 'Platform was successfully created.' }
format.json { render json: #platform, status: :created, location: #platform }
else
format.html { render action: "new" }
format.json { render json: #platform.errors, status: :unprocessable_entity }
end
end
end
# PUT /platforms/1
# PUT /platforms/1.json
def update
#platform = Platform.find(params[:id])
#categories = Category.find(:all)
respond_to do |format|
if #platform.update_attributes(params[:platform])
format.html { redirect_to #platform, notice: 'Platform was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #platform.errors, status: :unprocessable_entity }
end
end
end
# DELETE /platforms/1
# DELETE /platforms/1.json
def destroy
#platform = Platform.find(params[:id])
#platform.destroy
respond_to do |format|
format.html { redirect_to platforms_url }
format.json { head :no_content }
end
end
end
I do not understand what I do wrong, but it doesnt correctly assign categories to platforms, and also in the platforms index view, when I try to use :
<%= platform.categories %>
it gives me error cannot find Category with id= "and here the respective id"
I am really confused since I followed tutorial for this one.
I use Rails 3.2.8
Without your view, I can't say for sure what it is you're trying to do exactly. Most importantly, what is in your params[:categories] hash? Given the name, it sounds like you intended for it to be multiple categories. However, your code is written as if you intended it to be a single set of attributes which describe one Category.
Since I can't say for sure what you want to do, I'll answer your question by explaining what you are doing. Maybe that will help you figure out how to fix it.
Your create code currently looks like this:
# POST /platforms
# POST /platforms.json
def create
#platform = Platform.new(params[:platform])
##categories = Category.new(params[:name])
#categories = #platform.categories.create(params[:categories])
The first line creates the new Platform and is easy. Skipping over the comment to the third line. This is probably what's tripping you up.
You are selecting the associations for your newly created Platform and trying to create a new category with attributes as stored in the params[:categories] hash. I'm afraid this is not allowed. (I think it throws an ActiveRecord::RecordNotSaved exception, but I could be wrong.) You can not create on a #platform which hasn't been persisted yet. Instead, I think you want build.
Here is the relevant documentation:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
The difference between create and build is that build just sets up the association without actually saving it to the database yet. create saves it immediately. The nice thing about build is that you don't actually have to save it yourself. It tags along for free when you call #platform.save or #platform.update_attributes. Also, save is automatically wrapped in a transaction, so it won't create the new Category if it fails to create the new Platform for whatever reason.
The next interesting thing is that you are assigning the result of your create to #categories. I don't think this is what you want either. You don't need to save the new Category because it tags along with your #platform. However, if the save of the platform fails, then you are going to re-render your new view with this value of #categories whereas in new you set #categories = Category.all. This could certainly cause some confusion on the new view after a failed create.
In summary, I think your create code should look something like the following.
# POST /platforms
# POST /platforms.json
def create
#platform = Platform.new(params[:platform])
#platform.categories.build(params[:categories])
respond_to do |format|
if #platform.save
format.html { redirect_to #platform, notice: 'Platform was successfully created.' }
format.json { render json: #platform, status: :created, location: #platform }
else
#categories = Category.all
format.html { render action: "new" }
format.json { render json: #platform.errors, status: :unprocessable_entity }
end
end
end
If you're params[:categories] is not a hash of category attributes and is actually a comma delimited string of category names, then you would want to do something like the following instead of my second line above:
params[:categories].split(",").each do |category|
#project.categories.build(name: category)
end
You may also want to check out accepts_nested_attributes_for which can DRY out your controller even more.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I hope that helps.

Rails: Pass Parameters to New Controller during 'redirect_to'

I'm using Ryan Bates' Rails Cast on Wicked Wizard Forms to create a multi-step form. I don't have a current_user method defined (not using an authentication gem) - so, I'm trying to pass the user.id parameter during the redirect_to - unfortunately, I can't seem to get it to work. Any help is appreciated!
My user controller create method
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to controller: 'user_steps', id: 'user.id' }
#format.html { redirect_to #user, notice: 'User was successfully created.' }#
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
The user_steps controller that to which I am redirecting:
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :gender, :items, :brands, :final
def show
render_wizard
end
end
You should pass it through as a param, ideally, which the redirect_to method will do for you if you use a proper route path.
Example:
redirect_to(user_steps_path(#user))
In your case, if you don't have a named route, you might do this:
redirect_to(controller: 'user_steps', id: #user.to_param)
In URLs it's advisable to use the to_param method. id is used for database queries.
What you're passing in is literally 'user.id' as a parameter. It will not be evaluated.

How to automaticly assigned a entry a unique id for use in a relationship?

A basic overview of my app. There is currently two models. A jobs model and a clients model. Both models have a has_and_belongs_to_many relationship as I intended to allow the user to create a client entry and then assign them one or many jobs.
Here are both of my models.
Clients -
class Client < ActiveRecord::Base
has_and_belongs_to_many :job
end
Jobs -
class Job < ActiveRecord::Base
has_and_belongs_to_many :client
end
I have been doing some research and I think im right in thinking that the relationship needs a foreign key to function so have added a client_id column & a job_id column to my database.
The clients page is currently working and here is my controller for that.
class ClientsController < ApplicationController
def index
#clients = Client.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clients }
end
end
# GET /Clients/1
# GET /Clients/1.json
def show
#clients = Client.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #clients }
end
end
# GET /Clients/new
# GET /Clients/new.json
def new
#clients = Client.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #clients }
end
end
# GET /Clients/1/edit
def edit
#clients = Client.find(params[:id])
end
def create
#clients = Client.new(params[:client])
respond_to do |format|
if #clients.save
format.html { redirect_to #clients, notice: 'Client was successfully created.' }
format.json { render json: #clients, status: :created, location: #clients }
else
format.html { render action: "new" }
format.json { render json: #clients.errors, status: :unprocessable_entity }
end
end
end
# PUT /Clients/1
# PUT /Clients/1.json
def update
#clients = Client.find(params[:id])
respond_to do |format|
if #clients.update_attributes(params[:client])
format.html { redirect_to #clients, notice: 'Client was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #clients.errors, status: :unprocessable_entity }
end
end
end
# DELETE /Clients/1
# DELETE /Clients/1.json
def destroy
#clients = Client.find(params[:id])
#clients.destroy
respond_to do |format|
format.html { redirect_to :clients , notice: 'Client was successfully removed.'}
format.json { head :no_content }
end
end
def details
#clients = Client.find_by_id(params[:id])
#jobs = Client.job
end
end
And here's what I currently have for my jobs controller.
class JobsController < ApplicationController
def index
#jobs = Job.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #job }
end
end
def new
#jobs = Job.new
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #job }
end
end
def create
#jobs = Job.new(params[:job])
#cients = Client.find = Client.find(params[:id])
respond_to do |format|
if #jobs.save
format.html { redirect_to(#jobs,
:notice => 'Job was successfully created.') }
format.xml { render :xml => #jobs,
:status => :created, :location => #Job }
else
format.html { render :action => "new" }
format.xml { render :xml => #jobs.errors,
:status => :unprocessable_entity }
end
end
end
end
In my jobs form I was given thr following code which added a drop down with all the created clients.
<%= select("job", "client_id", Client.all.collect {|c| [ c.name, c.id ] }, {:include_blank => 'None'})%>
When I press save though. I recieve the following error.
unknown attribute: client_id
Application Trace | Framework Trace | Full Trace
app/controllers/jobs_controller.rb:22:in `new'
app/controllers/jobs_controller.rb:22:in `create'
I assume this is because I need to define a way of finding the client_id in my job creation as well as specifying one in my client creation.
This is my first rails app though so im not quite sure how.
Any help would be greatly appreciated.
Your jobs table doesn't have a client_id, nor should it. You need to create a junction table to facilitate a many-to-many relationship. It should be called clients_jobs and contain an integer client_id and job_id.
There is a lot more wrong here. Here are just the things I caught at a casual glance:
This line:
#cients = Client.find = Client.find(params[:id])
should be:
#cients = Client.find(params[:id])
Pluralization is important in Rails. A client doesn't have many "job". It has many jobs. Your models should reflect this:
class Client < ActiveRecord::Base
has_and_belongs_to_many :jobs
end
class Job < ActiveRecord::Base
has_and_belongs_to_many :clients
end
You'll need to create a junction table via a migration, which is where your foreign keys will exist:
$ rails g migration AddClientsJobsTable
In index and new, you first create #jobs = Job.new and then you render it via :xml => #job. Again, pluralization is important. You need #job = Job.new. You have the same problem in create, except you've dropped the 's' and capitalized the 'J': :location => #Job } You can't do that in programming. Case and spelling both matter.
Job.find(:all) or Client.all: Pick one. Don't mix find :all and .all.
#clients = Client.find(params[:id]). You're finding a single specific Client, not a collection of clients. Your variable should be called #client. This is not an error, but it is seriously ugly.
pluralize your jobs and clients in your associations. I.E
has_many_and_belongs_to :jobs
has_many_and_belongs_to :clients
And if you do not use the alternative to this many-to-many associations with the ActiveRecord :through method (the alternative to HMABT) You must create the join table yourself which is a table of job_id's and client_id's.

Resources