ActiveAdmin form validation before save - ruby-on-rails-4

I have form fields from one model status_history that I am including in my member edit. I am wanting to make it so that if the fields for status_history are empty, then it will not save. Currently it is saving blank items to status_history when I save a members edit.
My member form looks like this
form(:html => { :multipart => true }) do |f|
f.semantic_errors *f.object.errors.keys
columns do
column do
...
end
column do
f.inputs "Status" do
f.semantic_fields_for :status_histories, StatusHistory.new do |sh|
sh.inputs :class => "" do
sh.input :status, as: :select, collection: {Active: "active", Inactive: "inactive", Separated: "separated"}
sh.input :date, :as => :datepicker
sh.input :reason
end
end
table_for member.status_histories do
column "status" do |status_histories|
status_histories.status
end
column "date" do |status_histories|
status_histories.date
end
column "reason" do |status_histories|
status_histories.reason
end
end
end
...
end
end
f.actions
end
models/status_histories
class StatusHistory < ActiveRecord::Base
belongs_to :member
STATUS_TYPES = [ "active", "inactive", "separated" ]
validates :status, inclusion: STATUS_TYPES
validates :date, :presence => true
validates :reason, :presence => true
end
Even adding a button that would toggle the semantic_fields_for would work but currently if I leave them blank I get validates errors.
How would I override the save method to check if status and date are present and if so save the status_history and if not, do not save the status_history but save the rest of the member fields?

Try this:
in Member ActiveRecord model
accept_nested_attributes_for :status_histories, reject_if: :all_blank
http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for

Related

Active Admin has many form use same model twice in tabs

I have this PostApplication Model and PostApplication has_many Documents in this below activeadmin resource which contains has_many form.
I want to show various records of Document model based on the boolean is_scanned and I want to create documents in the different tabs through postapplication model only but in the second tab whenever i upload a new file it updates the last existing document
Please Help
ActiveAdmin.register PostApplication do
form do |f|
tab "Documents" do
tabs do
tab "Not Scanned" do
f.has_many :documents, for: [:documents, f.object.documents.where(is_scanned: false) || Document.new(is_scanned: false)] do |la|
la.semantic_errors *la.object.errors.keys
la.input :verified
la.input :document, as: :file, required: false, :hint => (la.object.new_record? || !la.object.document.exists? ? "" : link_to("Download", "#"))
la.input :_destroy, as: :boolean, required: false, label: t('remove')
end
end
tab "Scanned" do
f.has_many :documents, for: [:documents, f.object.documents.where(is_scanned: true) || Document.new(is_scanned: true)] do |la|
la.semantic_errors *la.object.errors.keys
la.input :verified
la.input :document, as: :file, required: false, :hint => (la.object.new_record? || !la.object.document.exists? ? "" : link_to("Download","#"))
la.input :_destroy, as: :boolean, required: false, label: t('remove')
end
end
end
end
end
end

How to validate radio button is selected?

I am trying to validate that the "Never" radio button is selected, or "Date" (with datepicker) is selected with a date, hence :expiry_date.
:expires is a string type.
:expiry_date is a date type.
f.input :expires, as: :radio, :collection => ["Never", "Date"]
f.input :expiry_date, as: :string, :wrapper_html => { :class => "date_picker"}
This is my validations so far.
validates :expires,
inclusion: { in: ["Never", "Date"],
presence: { message: "none selected"}}
validates :expiry_date, presence: true
You can use a custom validation:
class Food < ActiveRecord::Base
validate :has_expiry_date
def has_expiry_date
if expires == "Date" && !expiry_date.present?
errors.add(:expiry_date, 'needs to be present if food expires')
end
end
end

Rails :has_many :through sort order issue in model

I am having some weird behavior in a Rails model and I'm not quite sure why. Thanks to anyone who can point me as to what I'm missing or not understanding.
The Problem
I have a model Car with a has_many :through relationship on Part through CarsPart. I expect accessing a car's parts to be in alphabetical order. I created the model specs and the controller specs. In the controller, the parts are ordered in alphabetical order as called out in the has_many relationship. In the controller spec, they are in order. In the model spec, they are not. I don't think it's tied to the testing framework because i've debugged the running server and see the same behavior.
The Code
Car
class Car < ActiveRecord::Base
has_many :cars_parts
has_many :parts, -> { order('name asc') }, through: :cars_parts, :dependent => :destroy
accepts_nested_attributes_for :cars_parts, :allow_destroy => true
validates :make, :model, presence: true
validate :validate_cars_parts
def validate_cars_parts
errors.add(:parts, "wrong number") if self.cars_parts.size < 1
self.cars_parts.each do |car_part|
part = Part.find(car_part.part_id)
errors.add(:parts, "doesn't exist") if part == nil
end
end
end
Part
class Part < ActiveRecord::Base
belongs_to :cars_part
has_many :cars, through: :cars_parts
validates :name, presence: true
end
CarsPart
class CarsPart < ActiveRecord::Base
belongs_to :car
belongs_to :part
end
Cars Controller Spec Passes
require 'rails_helper'
RSpec.describe CarsController, type: :controller do
render_views
context 'property features' do
describe "GET #show/id returns ordered parts" do
before :each do
#p4 = FactoryGirl.create(:part, :name => 'BPart2')
#p3 = FactoryGirl.create(:part, :name => 'APart2')
#p6 = FactoryGirl.create(:part, :name => 'BPart3')
#p5 = FactoryGirl.create(:part, :name => 'APart3')
#p2 = FactoryGirl.create(:part, :name => 'BPart1')
#p1 = FactoryGirl.create(:part, :name => 'APart1')
end
it 'returns parts in alphabetical order' do
car = FactoryGirl.create(:car, make: 'Nissan', model: 'Murano', parts: [#p4, #p3, #p6, #p5, #p2, #p1])
get :show, id: car.id, format: :json
expect(response).to have_http_status(:success)
expect(response).to render_template(:show)
response_json = JSON.parse(response.body)
response_json['parts'].each do |part|
puts part['id']
end
expect(response_json['parts'].size).to eq(6)
expect(response_json['parts'][0]['id']).to eq(#p1.id)
expect(response_json['parts'][1]['id']).to eq(#p3.id)
expect(response_json['parts'][2]['id']).to eq(#p5.id)
expect(response_json['parts'][3]['id']).to eq(#p2.id)
expect(response_json['parts'][4]['id']).to eq(#p4.id)
expect(response_json['parts'][5]['id']).to eq(#p6.id)
end
end
end
end
Car Model Spec Fails
require 'rails_helper'
RSpec.describe Car, type: :model do
context 'car parts' do
before :each do
#p4 = FactoryGirl.create(:part, :name => 'BPart2')
#p3 = FactoryGirl.create(:part, :name => 'APart2')
#p6 = FactoryGirl.create(:part, :name => 'BPart3')
#p5 = FactoryGirl.create(:part, :name => 'APart3')
#p2 = FactoryGirl.create(:part, :name => 'BPart1')
#p1 = FactoryGirl.create(:part, :name => 'APart1')
end
it 'returns parts in alphabetical order' do
car = FactoryGirl.create(:car, make: 'Nissan', model: 'Murano', parts: [#p4, #p3, #p6, #p5, #p2, #p1])
expect(car).to be_valid
expect(car.errors.messages.size).to eq(0)
expect(car.parts.size).to eq(6)
car.parts.each do |part|
puts part.id
end
expect(car.parts[0]['name']).to eq('APart1')
expect(car.parts[1]['name']).to eq('APart2')
expect(car.parts[2]['name']).to eq('APart3')
expect(car.parts[3]['name']).to eq('BPart1')
expect(car.parts[4]['name']).to eq('BPart2')
expect(car.parts[5]['name']).to eq('BPart3')
end
end
end
Summary
Please help me understand why when accessing them through the model they are not ordered, but when the controller looks them up and renders them, they are.
Thank you.
EDIT
The only way I have managed to get this to work is to create a 'ordered_parts' method that does the ordering and remove the order from the has_many. I had to change all my controllers to call 'ordered_parts' instead of the 'parts' method. Surely there is a better way. If anyone knows, please help.
def ordered_parts
self.parts.order('name asc')
end

Rails - Multiple Nested Attributes

I am trying to use a select tag to save multiple nested children in a parent.
This is the error I'm looking at
Couldn't find all UserLocations with IDs (1, 2) (found 0 results, but was looking for 2)
I have the following setup under Rails 4 and Devise:
User
class User < ActiveRecord::Base
has_many :user_locations
accepts_nested_attributes_for :user_locations, :allow_destroy => true
end
UserLocation (locations that the user has)
class UserLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
Location (predefined list of locations the user can choose upon)
class Location < ActiveRecord::Base
has_many :user_locations
has_many :users, through: :user_locations
end
However, when trying to save the selected UserLocations, they won't be saved.
Rails Select Tag (You can choose multiple items)
<%= f.select :user_location_ids, options_for_select(Location.all.collect { |l| [ l.name, l.id ] }, #user.user_locations.collect{ |l| l.id }), {}, { multiple: true } %>
I have put the user_location_ids in my application_controller as user_location_ids: []
Cheers
Solved
The solution is to overwrite the default setter method for multiple nested models model_ids=(value). Do not use the plural of the model, e.g. models_ids=(value), because that is wrong!
def user_location_ids=(value)
for slot in value do
unless slot == ""
location = Location.find_by(id: slot.to_i)
unless location.nil?
self.user_locations << UserLocation.create(user_id: self.id, location_id: location.id)
end
end
end
end

Why am I getting "Can't mass-assign protected attributes" after adding a field to a product on spree?

I'm trying to add a field to products on spree which is just a checkbox which is just ment to mark products if they are for sale or they're internal products.
I've added the migration and finally figured out how to add the checkbox on the form, but when I click Update I get Can't mass-assign protected attributes: for_sale
This is the migration
class AddProductForSaleField < ActiveRecord::Migration
def up
add_column :spree_products, :for_sale, :boolean
end
def down
remove_column :spree_products, :for_sale
end
end
Here's the field being added
Deface::Override.new(:virtual_path => "spree/admin/products/_form",
:name => "for_sale",
:insert_before => "code[erb-silent]:contains('track_inventory_levels')",
:partial => "spree/admin/products/for_sale")
And this is the partial
<%= f.field_container :for_sale do %>
<%= f.label :for_sale, t(:for_sale) %>
<%= f.check_box :for_sale, { :checked => true } %>
<% end %>
got it, was missing the model part
Spree::Product.class_eval do
attr_accessible :for_sale
end
Mass Assignment is the name Rails gives to the act of constructing your object with a parameters hash. It is "mass assignment" in that you are assigning multiple values to attributes via a single assignment operator.
The following snippets perform mass assignment of the name and topic attribute of the Post model:
Post.new(:name => "John", :topic => "Something")
Post.create(:name => "John", :topic => "Something")
Post.update_attributes(:name => "John", :topic => "Something")
In order for this to work, your model must allow mass assignments for each attribute in the hash you're passing in.
There are two situations in which this will fail:
You have an attr_accessible declaration which does not include :name
You have an attr_protected which does include :name
It recently became the default that attributes had to be manually white-listed via a attr_accessible in order for mass assignment to succeed. Prior to this, the default was for attributes to be assignable unless they were explicitly black-listed attr_protected or any other attribute was white-listed with attr_acessible.
If it's a permission issue then you can add :
Spree::Product.class_eval do
attr_accessible :variable_1, :variable_2 :as => [:default, :product]
end
Marking it as default for a specific model, will remove the mass-assignment warning message !