Multisteps form creation with relationships - ruby-on-rails

I'm trying to implement a multistep form following this article: https://medium.com/#nicolasblanco/developing-a-wizard-or-multi-steps-forms-in-rails-d2f3b7c692ce
The problem is that my model has relationships and these are not recognized in the step 3 presenting an error, see below my full code and the error message:
class Evaluation < ApplicationRecord
belongs_to :user
belongs_to :teacher
belongs_to :school
belongs_to :subject
has_many :evaluation_tags
has_many :tags, through: :evaluation_tags
accepts_nested_attributes_for :evaluation_tags
validates :teacher_id, presence: true
validates :subject_id, presence: true
validates :school_id, presence: true
validates :user_id, presence: true
validates :rating, presence: true
end
module Wizard
module Evaluation
STEPS = %w(step1 step2 step3).freeze
class Base
include ActiveModel::Model
attr_accessor :evaluation
delegate *::Evaluation.attribute_names.map { |attr| [attr, "#{attr}="] }.flatten, to: :evaluation
def initialize(evaluation_attributes)
#evaluation = ::Evaluation.new(evaluation_attributes)
end
end
class Step1 < Base
validates :teacher_id, presence: true
end
class Step2 < Step1
validates :subject_id, presence: true
end
class Step3 < Step2
validates :school, presence: true
validates :user, presence: true
validates :rating, presence: true
end
end
end
class EvaluationsController < ApplicationController
before_action :complete_sign_up, except: [:index]
# before_action :load_evaluation_wizard, except: %i(validate_step)
before_action :load_evaluation_wizard, except: [:validate_step, :new]
def step1
#teachers = Teacher.includes(:schools).where(schools: {id: current_user.student_details.last.school_id}).order(full_name: :ASC)
end
def step2
#teacher_id = session[:evaluation_attributes]["teacher_id"]
#subjects = Subject.includes(:teachers, :schools).where(teachers: {id: #teacher_id}).where(schools: {id: current_user.student_details.last.school_id}).order(name: :ASC)
end
def step3
pp session[:evaluation_attributes]
end
def validate_step
current_step = params[:current_step]
#evaluation_wizard = wizard_evaluation_for_step(current_step)
#evaluation_wizard.evaluation.attributes = evaluation_wizard_params
session[:evaluation_attributes] = #evaluation_wizard.evaluation.attributes
# pp session[:evaluation_attributes]
if #evaluation_wizard.valid?
next_step = wizard_evaluation_next_step(current_step)
create and return unless next_step
redirect_to action: next_step
else
render current_step
end
end
def load_evaluation_wizard
#evaluation_wizard = wizard_evaluation_for_step(action_name)
end
def wizard_evaluation_next_step(step)
Wizard::Evaluation::STEPS[Wizard::Evaluation::STEPS.index(step) + 1]
end
def wizard_evaluation_for_step(step)
raise InvalidStep unless step.in?(Wizard::Evaluation::STEPS)
"Wizard::Evaluation::#{step.camelize}".constantize.new(session[:evaluation_attributes])
end
def evaluation_wizard_params
params.require(:evaluation_wizard).permit(:teacher_id, :subject_id, evaluation_tags_attributes: {tag_ids: []}).merge(user: current_user, school: current_user.student_details.last.school)
end
class InvalidStep < StandardError; end
end
#STEP3.HTML.ERB
<%= f.collection_check_boxes(:tag_ids, Tag.all, :id, :name) do |tag| %>
<%= tag.label(class: "tags tags-bom") { tag.check_box(class: "checkbox_tags") + tag.text} %>
<% end %>
#ERROR
undefined method `tag_ids' for #<Wizard::Evaluation::Step3:0x00007fadb2be6a88>
How can i make that module recognizes the relationships?

I think you will need a custom validation for tag_ids. I don't think rails will validate multiple items in one step. Maybe add a custom validation at the bottom of your application controller:
def validate_tag_ids
if !tag_ids.is_a?
errors.add(:tag_ids, :invalid)
end
end
And then use:
validates: :validate_tag_ids
instead of:
validates :tag_ids, presence: true

Related

ActiveModel::MissingAttributeError in RequestsController#create, can't write unknown attribute `request_id'

I'm getting an error regarding saving a request for a job on a website I'm making. Basically the user (candidate) will make a request for a job through the job/show.html.erb page, the comment will then display on the show.html.erb page with any other candidates who have also applied for the job. When the user types their name in the text box and submits it I get the error mentioned above. After looking online it seems the problem lies in my realtionships in the Models. Any ideas?
RequestsController
class RequestsController < ApplicationController
before_action :authorise
#set_request, only: [:show, :edit, :update, :destroy]
def create
#job = Job.find params[:job_id]
#request = #job.requests.new(request_params) <- Error highlights this line
#request.candidate_id = #current_candidate.id #sets the user_id FK
#request.save #saves the #comment
# object to the comments table
respond_to do |format|
format.html{redirect_to #job}
end
end
private
def request_params
#This is the method ehich whitelists the data fields from the format
params.require(:request).permit(:content, :job_id, :candidate_id)
end
end
Request Model
class Request < ActiveRecord::Base
belongs_to :job, dependent: :destroy
has_many :candidates
end
Candidate Model
class Candidate < ActiveRecord::Base
has_secure_password
validates_uniqueness_of:can_email
belongs_to :request
validates :can_name, presence: true
validates :can_surname, presence: true
validates :college, presence: true
validates :can_email, presence: true
validates :address, presence: true
validates :experience, presence: true
validates :password_digest, presence: true
validates :college_year, numericality: { only_integer: true }
end
Job Model
class Job < ActiveRecord::Base
belongs_to :sector
has_many :requests, dependent: :destroy
validates :name, presence: true
validates :employer, presence: true
validates :sector, presence: true
validates :experience_req, presence: true
validates :job_info, presence: true
end
assuming that in your routes.rb file you have the following routes defined:
resources :jobs do
resources :requests
end
which nests the routes, (see http://guides.rubyonrails.org/routing.html#nested-resources)
It is completely unnecessary to pass in job_id in, via the requests part of the params. i.e. You don't need to include it as an input in your form because the url already passes the param in.
look at your server output the params hitting your 'create` action should look something like this:
params = { job_id: 1, request: {content: "hello world", candidate_id: "123"}}
in that case you permit the following:
def request_params
params.require(:request).permit(:content, :candidate_id)
end
and the first two lines of create will be correct:
#job = Job.find(params[:job_id])
#request = #job.requests.new(request_params)

Rails Client side Collection Validation fails - Simple Form

I have to build a simple app that allows users to loan and borrow books. Simply put a User can create books, and they can pick another user to loan the book to.
I have three models User, Book and Loan:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :books
has_many :loans, through: :books
has_many :borrowings, class_name: "Loan"
validates :username, uniqueness: true
validates :username, presence: true
end
class Book < ActiveRecord::Base
belongs_to :user
has_many :loans
validates :title, :author, presence: true
end
class Loan < ActiveRecord::Base
belongs_to :user
belongs_to :book
validates :user, :book, :status, presence: true
end
The LoansController looks like this:
class LoansController < ApplicationController
before_action :find_book, only: [:new, :create]
def new
#users = User.all
#loan = Loan.new
authorize #loan
end
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end
private
def loan_params
params.require(:loan).permit(:user_id)
end
def find_book
#book = Book.find(params[:book_id])
end
end
My form looks like:
<%= simple_form_for([#book, #loan]) do |f| %>
<%= f.input :user_id, collection: #users.map { |user| [user.username, user.id] }, prompt: "Select a User" %>
<%= f.submit %>
<% end %>
If I submit the form without selecting a user, and keep the "Select a User" prompt option, the form is submitted and the app crash because it can't find a user with id=
I don't know why the user presence validation in the form does not work...
you will change your Create method
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find_by_id(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end

Joint query across 2 models (has_many)

Hi I need help and all insight appreciated. I have two models Auctions and Bids and I want to retrieve the All auctions current_user won, the ones s/he has been outbid on and the ones s/he's winning
Here are the two models:
class Auction < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
before_save :populate_guid
mount_uploaders :images, ImageUploader
belongs_to :client
has_many :bids, dependent: :destroy
has_one :order, dependent: :destroy
validates_presence_of :title, :lien_price,
:end_time, :collateral_value,
:redemption_date, :current_interest_rate,
:additional_tax, :collateral_details,
:location, :client_id, :starting_bid
validate :end_time_in_the_future, :on => :update
validates_uniqueness_of :guid, case_sensitive: false
def end_time_in_the_future
errors.add(:end_time, "can't be in the past") if self.end_time && self.end_time < Time.now
end
def self.get_active_auctions
where("end_time > ?", Time.now)
end
def self.closed_auctions
where("end_time < ?", Time.now)
end
def highest_bid
self.bids.maximum("amount")
end
def highest_bid_object
self.bids.order(:amount => :desc).limit(1).first
end
def highest_bidder
self.highest_bid_object.user if highest_bid_object
end
def closed?
self.end_time < Time.now
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
and
class Bid < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
belongs_to :auction
belongs_to :user
before_save :populate_guid
validates_presence_of :amount, :user_id,
:auction_id
#validate :higher_than_current?
validates :amount, :numericality => true
validates_uniqueness_of :guid, case_sensitive: false
def higher_than_current?
if !Bid.where("amount > ? AND auction_id = ?", amount, self.auction.id).empty?
errors.add(:amount, "is too low! It can't be lower than the current bid, sorry.")
end
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
I thought
#auctions = Auction.closed_auctions.where(highest_bidder: current_user)
or
#auctions = Auction.closed_auctions.joins(:bids).where(highest_bidder: current_user)
would work but they both raise an error.
Edit this works
#auctions = Auction.closed_auctions.references(highest_bidder: current_user)
But there's probably a better way.
You probably can't access current_user from controller (devise?). So you need to pass the user as a parameter to the class or instance method. What you should look into are scopes and especially scopes that accept parameters. Scopes could really help you refactor your Auction model (you really don't need any methods that only return a where()), but also solve the inaccessible current_user.
Use it like this in your Auction model:
scope: :highest_bidder -> (current_user) { where(highest_bidder: current_user) }
And call it like this from your controller:
#auctions = Auction.closed_auctions.highest_bidder(current_user)

Custom setters & getters vs ActiveRecord

I have the following model:
class ActivityLog < ActiveRecord::Base
validates :user_id, :instance_id, :action, presence: true
validates :user_id, :instance_id, :action, numericality: true
belongs_to :user
def self.log(action, instance)
ActivityLog.create(
user_id: instance.user.id,
instance_id: instance.id,
action: action
)
end
def action
actions[:action]
end
def action=(action)
write_attribute(:action, actions.index(action))
end
def actions
['start','stop','create','destroy']
end
end
I am trying to substitute the keywords defined in def actions in the interface layer of the module, but save an integer in the database.
I have the following concerns:
def actions I believe should be defined on the class, but I'm not sure how to call it then from the instance.
How do I get it to write to the db?
What should be in private?
Standard way of doing this is using constant:
class ActivityLog < ActiveRecord::Base
validates :user_id, :instance_id, :action, presence: true
validates :user_id, :instance_id, :action, numericality: true
belongs_to :user
ACTIONS = ['start','stop','create','destroy']
def self.log(action, instance)
ActivityLog.create(
user_id: instance.user.id,
instance_id: instance.id,
action: action
)
end
def action
ACTIONS[:action]
end
def action=(action)
write_attribute(:action, ACTIONS.index(action))
end
end
If you're running rails 4.1 you can use enum:
class ActivityLog < ActiveRecord::Base
validates :user_id, :instance_id, :action, presence: true
validates :user_id, :instance_id, :action, numericality: true
belongs_to :user
enum action: ['start','stop','create','destroy']
def self.log(action, instance)
ActivityLog.create(
user_id: instance.user.id,
instance_id: instance.id,
action: action
)
end
end
ActivityLog.actions #=> ['start','stop','create','destroy']
a = ActivityLog.new
a.status = 'start'
a.status #=> 'start'
a.start? #=> true

Rails Nested Resource Routes

I'm fairly new to rails and I don't think I'm understanding the routing completely. When I try to access the edit action I get the following error:
ActiveRecord::RecordNotFound in StoreController#show
Couldn't find Gear with id=edit
Rails.root: /Users/dave/rails_projects/outdoor
Application Trace | Framework Trace | Full Trace
app/controllers/store_controller.rb:7:in `show'
Request
Parameters:
{"user_id"=>"104",
"id"=>"edit"}
Show session dump
Show env dump
Response
Headers:
None
Here is my view with the link that is throwing this error:
<li><%= link_to "Store Appearance", edit_user_store_path(#user) %></li>
Here is my nested route:
resources :users do
resources :store
end
Here is my controller
class StoreController < ApplicationController
def index
#store = current_user.gears.paginate(page: params[:page])
end
def show
#gears = Gear.find(params[:id]).user.gears.paginate(page: params[:page])
end
def edit
end
def update
end
end
Model Store
class Store < ActiveRecord::Base
attr_accessible :storeimage, :storename
belongs_to :user
validates :user_id, :presence => true
end
Model User
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password, :password_confirmation, :userimage, :remove_userimage
has_secure_password
has_many :gears
has_many :comments, :dependent => :destroy
has_one :store, :dependent => :destroy
before_save :create_remember_token
require 'carrierwave/orm/activerecord'
mount_uploader :userimage, UserpicUploader
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :first_name, presence: true,
length: {:maximum => 50 }
validates :last_name, presence: true,
length: {:maximum => 50 }
validates :email, presence: true,
format: {:with => email_regex},
uniqueness: {:case_sensitive => false}
validates :password, presence: true,
confirmation: true,
length: {within: 6..40}
include Tire::Model::Search
include Tire::Model::Callbacks
def name
first_name + " " + last_name
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
Please help.
You need to pass both the user_id and id params in the URL when you're accessing a store object nested under a user, so your URL should look like this:
/users/1/stores/3/edit
Versus:
/users/1/stores/edit
You also need to pass both of those as arguments to your path helper, ie:
edit_user_store_path(#user, #store)

Resources