Create new and select existing value from a has_many relationship - activeadmin

Networks contains a has_one relationship for vlans.
Vlans contains a belongs_to relationship for networks.
When I create a new network, I want to have the option of either selecting an existing vlan by vlanid -or- creating a new vlan for the new network.
I have made it as far as being able to create a brand new vlan when I create a network, but I would like to be able to select an existing vlan -or- create a new one.
app/models/network.rb
class Network < ApplicationRecord
has_one :vlan
accepts_nested_attributes_for :vlan, allow_destroy: true
end
app/models/vlan.rb
class Vlan < ApplicationRecord
belongs_to :network
end
db/migrate/create_network.rb
class CreateNetworks < ActiveRecord::Migration[5.2]
def change
create_table :networks do |t|
t.string :comment
t.string :name
t.timestamps
end
end
end
db/migrate/create_vlan.rb
class CreateVlans < ActiveRecord::Migration[5.2]
def change
create_table :vlans do |t|
t.string :comment
t.integer :vlanid
t.belongs_to :network
t.timestamps
end
end
end
app/admin/networks.rb
ActiveAdmin.register Network do
permit_params :comment, :name, vlan_attributes: [ :id, :vlanid, :comment, :_destroy, :_edit ]
index do
selectable_column
column "Network Name" do |i|
i.name
end
column :comment
column "VLAN ID" do |i|
i.vlan.vlanid
end
column "VLAN Comment" do |i|
i.vlan.comment
end
actions
end
show do
attributes_table do
row :name
row :comment
end
panel 'vlan' do
table_for network.vlan do
column :vlanid
column :comment
end
end
end
form do |f|
f.inputs 'Details' do
f.input :name
f.input :comment
end
f.inputs 'Vlans' do
f.has_many :vlan do |c|
c.input :vlanid
c.input :comment
end
end
f.actions
end
end
app/admin/vlans.rb
ActiveAdmin.register Vlan do
belongs_to :network
permit_params :comment, :vlanid
end
I've tried using something like the following in app/admin/networks.rb
panel 'vlan' do
table_for network.vlan do
column :vlanid, :as => :select
column :comment
end
end
I do get a drop-down in this instance, but the only values are "Yes" and "No".
This is my first time delving into activeadmin and I'm stumped.
I've seen some similar questions posted around, but none of the answers I've seen seemed to apply to this specific situation.
Any help is appreciated.

Related

VBA Gantt Chart, Parent-activities dictionary

I am brand new to VBA and would need your help on a personal project.
I want to create a custom Gantt chart on Excel VBA.
Excel version is Microsoft® Excel® for Microsoft 365 MSO (Version 2208 Build 16.0.15601.20446) 64-bit
One aspect of the project I am struggling with is to manage "Parent-activities". Lets say that I have an activity identified as "Child", that can not start before other activities identified as "Parents" finish.
I would like to build a Public dictionary, each entry/key being a "Child", each child having a collection of "Parents".
My issue is that my code is very buggy, sometimes it works, sometimes not and I can not tell why...
Here is what I tried. I would really appreciate if you could flag any issue.
The Parent List is buggy (sometimes works, sometimes not).
The Parent Count function does not work, I guess I dont have the right syntax.
Public Parents_Dict As New Scripting.Dictionary 'I want this dictionary accessible from any Sub and I want to keep its content throughout the project
Public Child As Variant 'This will store the ID of the Child depending on the context
Public Parent As Variant 'This will store the ID of the Parent depending on context
'I have a button "Manage Parent" that triggers a user form and save the Child ID
Sub Open_Parents_Manager() 'We want to open the user form to manage parent activities
'We find the activity ID of the cell that was active when we opened the form
'We store this ID as the Variable "Child" that is recognize in this whole module
Child = CStr(ActiveSheet.Cells(ActiveCell.Row, 1).Value)
Manage_Parents.Show vbModeless 'VbModeless allow to interact with the speadsheet while the user form is still open
End Sub
'In this user form I have a Listbox that should list the Parents of the selected Child
Sub UserForm_Activate()
'We want the title of the box to mention Child ID to help the user confirm they are working on the right activity
Me.Caption = "Manage parent activities of " & Child 'Mention the ID of the Child as a title of the window
'We want to display the list of the current parents of this activity
Call Display_Parents_List
'We want to display the number of parents of this activity
Call Count_Parents
End Sub
Sub Display_Parents_List()
UserForm.Parents_ListBox.Clear
Dim Val As Variant
On Error Resume Next 'Override error if no dictionary exists
For Each Val In Parents_Dict(Child)
UserForm.Parents_ListBox.AddItem Val 'We add each item in the collection in our ListBox
Next Val
End Sub
'In this user form I also have a label that should return the number of parents for the selected child
Sub Count_Parents() 'We inform the user of how many parents this Child has
If Parents_Dict.Exists(Child) Then
UserForm.Label1.Caption = Parents_Dict(Child).Count 'This one does not work
Else
UserForm.Label1.Caption = "Currently, this activity has no parent"
End If
End Sub
'Finally I have button to add parents.
Sub Add_Parent()
Parent = "TP9" 'for testing I change the values manually here
If Parents_Dict.Exists(Child) Then
On Error Resume Next
Parents_Dict(Child).Add Item:=Parent, Key:=Parent 'Collection can have multiple times the same item but not under the same Key, Using Parent as Item & Key avoids duplicates
On Error GoTo 0
Else
Dim newList As Collection
Set newList = New Collection
newList.Add Item:=Parent, Key:=Parent
Parents_Dict.Add Key:=Child, Item:=newList
End If
End Sub

Pass subroutine name as string to use subs as general purpose input validation

I read through several posts about similar problems and tried many solutions offered by this and other communities. I cannot tailor any of these to my specific needs.
I have an Excel workbook that generates a timesheet and a detailed job report based on the information provided in a userform.
The job report and the timesheet are exported to an Access table (or imported from said table to be edited or deleted).
I have a working version with repetitive code for validating the userform inputs.
There are eight inputs that must meet criteria.
i) must be a number
ii) must not be less than a minimum value
iii) must not be greater than a maximum value
I have a subroutine for each of these inputs that checks these criteria using BeforeUpdate, and calls another subroutine to make visible changes to the userform to alert the user of an invalid entry (alter the label color and caption, textbox or dropbox color, etc.).
Using AfterUpdate, I have a subroutine for each of the eight inputs that reverts these changes once a valid entry has been made.
This means I have 24 subroutines with basically the same code, where I feel there should only be three subroutines that can be used more generally.
Here is the code I have for these subroutines, as it is being used for one specific input:
Sub #1
Private Sub NumberOfTechs_BeforeUpdate(ByVal CAncel As MSForms.ReturnBoolean)
If Not IsNumeric(numberOfTechs) Then
Call NumberOfTechsInvalid("Must be a number!", numberOfTechsLabel, numberOfTechs)
CAncel = True
Else
If numberOfTechs < 1 Then
Call NumberOfTechsInvalid("Cannot be less than 1!", numberOfTechsLabel, numberOfTechs)
CAncel = True
ElseIf numberOfTechs > 6 Then
Call NumberOfTechsInvalid("Cannot exceed 6!", numberOfTechsLabel, numberOfTechs)
CAncel = True
End If
End If
End Sub
Sub #2
Private Sub NumberOfTechsInvalid(errorCaption As String, targetLabel As Object, targetControl As Object)
targetLabel.caption = errorCaption
targetLabel.ForeColor = rgbRed
targetControl.BackColor = rgbPink
targetControl.SelStart = 0
targetControl.SelLength = Len(targetControl)
End Sub
Sub # 3
Private Sub NumberOfTechs_AfterUpdate()
numberOfTechsLabel.ForeColor = Me.ForeColor
numberOfTechsLabel = "Number Of Techs"
numberOfTechs.BackColor = rgbWhite
' Call next subroutine
End Sub
I have a comment at the bottom of sub#3 that says "call next subroutine". This is where I am having difficulty.
I can pass the minimum and maximum values as variables, as well as specify the target control and label based on which user input triggers the call to sub#1.
The issue is passing the next subroutine as a string.
I tried placing these subroutines in their own module and using Application.Run. I tried using CallByName with these subs within the userform code.

Validate a TextBox before the _Change event is fired

I've got a form that has 3 TextBox controls on it: stock code, quantity, certificate number. The stock code TextBox is set to focus automatically when the form is loaded.
I've also attached a bar code scanner to my PC, as the user wants to be able to either scan a bar code to populate the TextBox, or manually type the data in.
The labels being scanned contain two bar codes. One is a certificate number and the other a stock code.
The stock bar code has a prefix of "SBC/", whilst a certificate bar code is prefixed with "C/".
When the user scans a bar code, if the TextBox in focus is the stock code TextBox, then I want to run a check as below.
Private Sub txtStockCode_Change()
On Error GoTo errError1
If Len(txtStockCode.Text) >= 5 Then
If bChangeCode Then
If Left(txtStockCode.Text, 2) = "C/" Then
msgbox "You have scanned the certificate barcode; please scan the stock barcode."
txtStockCode.Text = ""
Else
bChangeCode = False
txtStockCode.Text = Replace(txtStockCode.Text, "SBC/", "")
txtStockCode.Text = Replace(txtStockCode.Text, "*", "")
End If
End If
End If
Exit Sub
Let's say the focus is currently on the stock code TextBox.
If the stock bar code is scanned, the following should happen:
Stock code length is greater than 5
Left 5 characters do not = "C/", so correct code has been scanned
TextBox text value is updated to remove all * and the prefix of "SBC/"
E.g. "SBC/A12-TR0*" becomes "A12-TRO"
and
Certificate number length is greater than 5
Left 5 characters do = "C/", so incorrect code has been scanned
MsgBox to user
TextBox value is reset to ""
However, no matter which code is scanned into the stock code TextBox, the value is never validated.
E.g. "SBC/A12-TR0*" remains as "SBC/A12-TR0*" and "C/29760" remains as "C/29760"
As the validation code is the same in the certificate TextBox, the same pattern is repeated vice versa.
Why are my values not updating, or how can I validate the input before the _Change is fired?
EDIT
I've now changed my code to
Private Sub txtStockCode_Change
If txtStockCode.Text <> "" Then
txtStockCode.Text = Replace(txtStockCode.Text, "SBC/", "")
txtStockCode.Text = Replace(txtStockCode.Text, "*", "")
End If
End Sub
But it still displays the prefix of SBC/, yet is removing the two * characters (at the start and end of the barcode as is required for the scanner to read it as a barcode)
You could try to set the barcode reader to return Enter key at the end of the scanned barcode and then use the Keypress event to check it and make your changes.
Sub txtStockCode_KeyPress(KeyAscii As Integer)
If KeyAscii = vbKeyReturn Then
If Len(txtStockCode.Text) >= 5 Then
If bChangeCode Then
If Left(txtStockCode.Text, 2) = "C/" Then
msgbox "You have scanned the certificate barcode; please scan the stock barcode."
txtStockCode.Text = ""
Else
bChangeCode = False
txtStockCode.Text = Replace(txtStockCode.Text, "SBC/", "")
txtStockCode.Text = Replace(txtStockCode.Text, "*", "")
End If
End If
End If
End If
End Sub

ActiveAdmin 'show' layout not same as edit / new

I have customized the form for new/edit to use two column layout for the form fields.
The customization uses formtastic.
Shouldn't the same layout display for "show" by default? Instead I get the activeadmin default one field per row display.
How do I get to display the same layout in "show" as my form (new/edit) pages?
By default the show page has its own layout, so you have to use some css to arrange the inputs, or you can override this method:
https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/views/components/attributes_table.rb#L22-L43
#collection.each_slice(2) do |records|
td do
content_for(record[0], block || title)
end
td do
content_for(record[1], block || title)
end
end
Or create a new one, and use this in the show block:
module ActiveAdmin
module Views
class AttributesTable < ActiveAdmin::Component
builder_method :attributes_table_for
def row_with_two_fields(*args, &block)
title = args[0]
options = args.extract_options!
classes = [:row]
if options[:class]
classes << options[:class]
elsif title.present?
classes << "row-#{title.to_s.parameterize('_')}"
end
options[:class] = classes.join(' ')
#table << tr(options) do
th do
header_content_for(title)
end
#collection.each_slice(2) do |records|
td do
content_for(record[0], block || title)
end
td do
content_for(record[1], block || title)
end
end
end
end
end
end
Or something similar... I haven't tried it.
I ended up doing the CSS hack
show do
panel "Mandatory Details" do
attributes_table_for contract do
row :account, :class => "column1"
row :customer, :class => "column2"
end
end
panel "Dates And Financials" do
attributes_table_for contract do
row :start_date, :class => "column1"
row :current_end_date, :class => "column2"
row :end_date_w_options, :class => "column1"
end
end

How to use modules to split very long code in active admin?

Activeadmin registers a page on a single file, in which it has all the logic: Index, Show, Edit, etc.
I would like to split, let's say, task.rb into task_index.rb, task_show.rb, task_edit.rb, etc.
So, how should you do that?
NOTE: I know that making an ActiveAdmin.register block in each file (it appends if Task exists) will do the work, but this question aims for a general approach rather than solving this specific inquiry.
-- admin/task.rb
#encoding: utf-8
ActiveAdmin.register Task do
[Lot's of actions]
member_action....
member_action....
member_action....
batch_action....
[Index stuff]
filter....
scope....
scope....
scope....
index do
column...
column...
column...
column...
end
[Edit stuff]
form do |f|
f.input....
f.input....
f.input....
f.input....
f.input....
end
[etc etc etc]
end
----------------
I'm thinking of modules, but I can't figure out how to.
That is how I do this
module source
module ResourceDSL
module ActsAsClone
def acts_as_clone
controller do
def new
instance_variable_name = active_admin_config.resource_class.to_s.underscore
resource = active_admin_config.resource_class.find(params[:id]) rescue nil
attrs = resource.nil? ? {} : resource.attributes
resource = active_admin_config.resource_class.new(attrs)
instance_variable_set("##{instance_variable_name}", resource)
end
end
action_item :only => [:show, :edit] do
if can? :create, resource and (!resource.respond_to?(:live?) or resource.live?)
link_to "Copy", :action => :new, :id => resource.id
end
end
end
end
end
including to ActiveAdmin::ResourceDSL
ActiveAdmin::ResourceDSL.send :include, ResourceDSL::ActsAsClone
And then you can
ActiveAdmin.register Account do
menu :parent => "Billing", :priority => 10
acts_as_clone
end

Resources