Custom setters & getters vs ActiveRecord - ruby-on-rails

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

Related

Multisteps form creation with relationships

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

Why throwing abort in a nested association raises “Failed to destroy the record” exception?

I need to validate if any CameraVectors has been associated to any MonitoredPlace before I destroy a Camera.
Camera's Model
class Camera < ApplicationRecord
belongs_to :location
has_many :camera_vectors, inverse_of: :camera, dependent: :destroy
validates :description, :device_serial, :device_name,
:device_type, :device_api_url, :device_user, :device_password,
presence: true
accepts_nested_attributes_for :camera_vectors, allow_destroy: true
end
CameraVector's model
class CameraVector < ApplicationRecord
belongs_to :camera, inverse_of: :camera_vectors
belongs_to :monitored_place, optional: true
validates :description, presence: true
validates :position, numericality: { greater_than_or_equal_to: 0 }, presence: true
before_destroy :has_monitored_place?
private
def has_monitored_place?
if monitored_place.present?
errors.add(:base, "cannot delete")
throw :abort
end
end
end
MonitoredPlace's model
class MonitoredPlace < ApplicationRecord
belongs_to :location
belongs_to :place_type
has_many :camera_vectors
validates :place_name, presence: true
validates :place_type_id, uniqueness: { scope: :location_id }, presence: true
scope :enabled, -> { where.not(enabled_on: nil).where(disabled_on: nil) }
end
Because of the accepts_nested_attributes_for whenever I try to update or destroy a camera this nested fields are sent as params
"camera_vectors_attributes"=>{"0"=>{"description"=>"A", "position"=>"1", "_destroy"=>"1", "id"=>"47"}}
I thought If I wrote a callback before_destroy in the model CameraVector I could validate it, but if the validation occurs it raises ActiveRecord::RecordNotDestroyed in the controller.
if #camera.destroy(camera_params)
redirect_to(action: :index, notice: t(".success"))
else
render :index
end
as you can read in the api documentation
ActiveRecord::RecordNotDestroyed
Raised by ActiveRecord::Base#destroy! when a call to #destroy would return false.
It is result of
before_destroy :has_monitored_place?
that calls a method and returns false.
def has_monitored_place?
if monitored_place.present?
errors.add(:base, "cannot delete")
throw :abort
end
end
to change this behavior implement a logic similar to the one described in the api
begin
complex_operation_that_internally_calls_destroy!
rescue ActiveRecord::RecordNotDestroyed => invalid
puts invalid.record.errors
end
or read
How do I 'validate' on destroy in rails

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)

Strong parameters in Ruby

I'm getting the error message about strong parameters. I think it's just that rails 4 doesn't use attributes anymore. the code for my toy.rb is:
class Toy < ActiveRecord::Base
attr_accessible :name, :price, :vendor
validates :name, :presence => true
validates :price, :presence => true
validates :price, :numericality => true
validates :vendor, :presence => true
end
how can I change this to strong parameters?
EDIT: I used a different rb i changed it to employees and this is what I have:
class Employee < ActiveRecord::Base
params.require(:employee).permit(:first, :last, :salary, :salary, :ssn)
validates :first, :presence => true
validates :last, :presence => true
validates :salary, :presence => true
validates :salary, :numericality => true
validates :ssn, :presence => true
end
It's still telling me "ndefined local variable or method `params' for #"
The code you need is
params.require(:toy).permit(:name, :price, :vendor)
You will put this in your controller. Typically, you create a private method:
def create
Toy.create(toy_params)
end
private
def toy_params
params.require(:toy).permit(:name, :price, :vendor)
end
See http://guides.rubyonrails.org/getting_started.html#saving-data-in-the-controller for more information.
Edit
I think I might have misled you with my original answer. The code goes in the controller, not the model.
Strong params are designed to help your controller send specific data to your model. It's meant to protect your app against unauthorized data being passed:
#app/controllers/toys_controller.rb
Class ToysController < ActiveRecord::Base
def new
#toy = Toy.new #-> creates a blank AR object
end
def create
#toy = Toy.new(toys_params) #->creates new AR object (populating with strong params)
#toy.save
end
private
def toys_params
params.require(:toys).permit(:your, :params, :here)
end
end

ActiveModel::MassAssignmentSecurity::Error even when using accepts_nested_attributes_for

My complete error message is:
ActiveModel::MassAssignmentSecurity::Error in
WorkoutsController#create Can't mass-assign protected attributes:
workout_entry
The params that I am sending looks like:
{"workout"=>{"unit"=>"kg", "name"=>"2013-02-20T21:26:19", "note"=>nil, "workout_entry"=> [{"workout_entry_number"=>"1", "exercise_id"=>2, "entry_detail"=>[{"set_number"=>"1", "weight"=>"32", "reps"=>"43"}]}]}}
I have a workout that has many workout entries and each workout entries can have many entry details. The note is optional.
workout.rb
class Workout < ActiveRecord::Base
has_many :workout_entries, dependent: :destroy
attr_accessible :id, :name, :note, :unit, :workout_entries_attributes
belongs_to :user
accepts_nested_attributes_for :workout_entries
validates_presence_of :name
validates_presence_of :unit, :inclusion => %w(kg lb)
validates_associated :workout_entries
default_scope order("created_at DESC")
end
workout_entry.rb
class WorkoutEntry < ActiveRecord::Base
belongs_to :workout
belongs_to :exercise
has_many :entry_details, dependent: :destroy
attr_accessible :workout_id, :exercise_id, :workout_entry_number, :entry_details_attributes
accepts_nested_attributes_for :entry_details
validates :exercise_id, presence: true, numericality: {only_integer: true}, :inclusion => { :in => 1..790 }
validates :workout_id, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 1}
validates :workout_entry_number, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 1}
end
workouts_controller.rb
class WorkoutsController < ApplicationController
respond_to :json
before_filter :authenticate_user!
def index
respond_with(current_user.workouts)
end
def show
respond_with(current_user.workouts.find(params[:id]))
end
def create
respond_with(current_user.workouts.create(params[:workout]))
end
def update
#workout = current_user.workouts.find(params[:id])
if #workout.update_attributes(params[:workout])
render json: #workout, status: :ok
else
render json: #workout.errors, status: :unprocessable_entity
end
end
def destroy
respond_with(current_user.workouts.destroy(params[:id]))
end
end
I tried switching the ordering of attr_accessible and accepts_nested_attributes_for within the workout.rb, but it does not work.
I even tried to set
config.active_record.whitelist_attributes = true
but creating was still prevented.
accepts_nested_attributes_for does not add any attributes to the whitelist. Whatever keys your trying to pass to update_attributes have to be listed in attr_accessible, in your case you need to add workout_entry to attr_accessible.
It does look like you have an error in the form, if your using fields_for then it should be using the key workout_entries_attributes, which you have accessible.
Try to add workout_entry_ids in attr accessible in your workout model.
I decided to not use accepts_nested_attributes_for in the workout and workout_entry models because it wasn't working for me. I also updated the format of my json that is sent. Details are in the link below
link

Resources