How to resolve associated models dropdown menu prob in active admin - ruby-on-rails

I m getting unreadable values # dropdown menu in active admin.
I have an attribute which has a inclusion of specific values(around 10 values) but when I am making a new object of that class by using active admin... firstly it is showing the unreadable drop down menu
second its showing that attribute to be blank even if I m choosing some unreadable value..
plz help
my admin/Resident.rb page: :
ActiveAdmin.register Resident do
permit_params :room_number,:roll_number,:name,:hostel,:hostel_id
index do
column :room_number
column :roll_number
column :name
column :hostel
actions
end
filter :name,:as => :string
filter :hostel, :as => :select
filter :room_number
filter :roll_number
form do |f|
f.semantic_errors *f.object.errors.keys
inputs 'Enter the student details' do
input :room_number
input :roll_number
input :name
input :hostel
actions
end
end
end
I have two models : Hostel And Resident :
models/hostel.rb
class Hostel < ActiveRecord::Base
has_many :residents
end
models/resident.rb
class Resident < ActiveRecord::Base
belongs_to :hostel
validates :room_number,presence: true,uniqueness: {case_sensitive: false}
validates :roll_number,presence: true,uniqueness:{case_sensitive: false}
validates :name, presence: true,length:{ maximum: 50 }
validates :hostel,presence: true
def display_name
hostel
end
end
schema: :
Resident
create_table "residents", force: :cascade do |t|
t.string "room_number"
t.string "roll_number"
t.string "name"
t.string "hostel"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "hostel_id"
end
Hostel:
create_table "hostels", force: :cascade do |t|
t.string "hostel"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

For making the text readable, you can reimplement the "to_s" method :
class Hostel < ActiveRecord::Base
#...
def to_s
self.name
end
end
But active admin is smart enough to use the "name" row if existing usually. The bad side of this method is everywhere in your log etc. it will use the name. Something like [self.id,self.name].join('-') seems better for debugging if the name field of your table hostels is not unique.
Your second problem is caused by one of theses two things:
Check your params permits, and allow "hostel_id"
Ensure your <select name="model[hostel_id]" > and not <select name="model[hostel]">. This should be done automatically, if not it's probably because you didn't defined well the link has_many / belongs_to in your two models.
Finally, the last way is you can still push your own collection as parameter of the f.input into the active admin:
f.input :hostel_id, collection: Hostel.all.map{|x| [x.name, x.id]}
One more time, this should be done automatically. In your case it's not, so look closely your db schema.
Happy Coding,
Yacine.

class Hostel < ActiveRecord::Base
def display_name
name # or that code will return a representation of your Model instance
end
end
in place of name use the column name whose value u want to be displayed in drop down

Related

Based on status_id want to populate an assigned string on an index page but it will only populate the status_id

I am using a drop down menu to determine the status of a Show is/was: "Watched," "Watching," or "To-Watch" I am trying to display the status on the shows page and it will only populate the status_id. This feels pretty basic, but I've tried many iterations and even did a nested attribute in my controller. The only way I can populate it is Status.last.watched, etc. Can anyone point me in the right direction?
***schema.rb***
create_table "statuses", force: :cascade do |t|
t.string "watched"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "show_id"
t.index ["show_id"], name: "index_statuses_on_show_id"
create_table "shows", force: :cascade do |t|
t.string "show_title"
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "note_id"
t.integer "status_id"
end
***params in shows_controller.rb***
def show_params
params.require(:show).permit(:show_title, :status_id, status_attributes: [:watched])
end
***index.html.erb***
<% #shows.each do |s| %>
<h4><li><%= link_to s.show_title, show_path(s.id) %></h4>
<p><%= s.status_id %>
<% end %>
Unfortunately you kind of failed at the database design stage. If you have shows and users and want to keep track of which users have watched what you want to setup a join table and put the status there.
class User < ApplicationRecord
has_many :user_shows
has_many :shows, through: :user_shows
end
# rails g model UserShow user:belongs_to show:belongs_to status:integer
# adding a unique compound index on user_id and show_id is a good idea
# if you are using PG you can use a native Enum column type instead of an integer
class UserShow < ApplicationRecord
enum status: { watching: 0, watched: 1, to_watch: 2 }
belongs_to :user
belongs_to :show
validates_uniqueness_of :show, scope: :user_id
end
class Show < ApplicationRecord
has_many :user_shows
has_many :shows, through: :user_shows
end
This creates a many to many association between Users and Shows. In your code a show can only be associated with a single status. That means you would have to duplicate everything on the shows table for every user.
I have no idea what you're trying to do with t.string "watched". A boolean would be slight improvement. But an ActiveRecord::Enum would let you keep track of the status without multiple boolean columns.
To get the status string to show in your view you want to change <%= s.status_id %> to <%= s.status.watched %>.
That being said, the way you have this setup, I would make another column in the shows table, call it status, and have it be of type string. Then you can set the status as one of three you listed. You could then get rid of the status_id column in shows and the whole statuses table.

How to get the size of belong_to record

I followed this tutorial for making the exact same comments section on my website and now i want to display the number of comments on the article but I can only have the number of answers to the article without including the number of answers of comments.
I don't want to add a column to my comment model with a reference of the article ID because my website is already online and all the old post will have 0 comments because they will not have this new column.
Any idea of how I can do ? I guess it as something to do with belong_to but on the official doc I cannot find it.
My model/comment.rb
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
has_many :comments, as: :commentable
serialize :report, Array
validates :commenter, presence: true, length: { in: 1..500 }
end
my model/article.rb
class Article < ApplicationRecord
include BCrypt
serialize :view, Array
serialize :upvote, Array
serialize :report, Array
has_many :comments, as: :commentable, dependent: :destroy
validates :title, presence: true, length: { in: 1..60 }
validates :content, presence: true
has_secure_password
end
EDIT:
Maybe I could do a method in my helper with a loop which will count every comment of a comment, but I don't how I could make this loop like Article.find(my_article_id).comments.each do and then I don't know how to do, then maybe I should do like Comment.comments.each do ?
I was thinking to do a recursive method but I always struggle to do recursive method
EDIT2:
schema/article
create_table "articles", force: :cascade do |t|
t.string "title"
t.string "author"
t.string "author_ip"
t.string "password_digest"
t.text "content"
t.string "upvote"
t.integer "upvote_count", default: 0
t.string "view"
t.integer "view_count", default: 0
t.string "report"
t.integer "report_count", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "activate", default: true
t.integer "comments_count", default: 0, null: false
end
schema/comment
create_table "comments", force: :cascade do |t|
t.text "commenter"
t.string "author"
t.string "author_ip"
t.string "date"
t.integer "commentable_id"
t.string "commentable_type"
t.string "report"
t.integer "report_count", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "article_id"
end
EDIT3:
comment migration
class AddCommentsCountToComments < ActiveRecord::Migration[5.2]
def change
add_column :comments, :comments_count, :integer, default: 0, null: false
Comment.reset_column_information # to reset cached values
Comment.find_each do |comment|
comment.update(comments_count: comment.comments.count) # updating old articles comments_counter
end
end
end
You can always use article.comments.size but it's not ideal as it will always make queries to the database.
Another way is to add counter cache comments_count column in your articles model, also you can update comments_count for any article created prior to the counter cache in the same migration.
You can start by adding migration
def change
add_column :articles, :comments_count, :integer, default: 0, null: false
Article.reset_column_information # to reset cached values
Article.find_each do |article|
article.update(comments_count: article.comments.count) # updating old articles comments_count
end
end
Now if you ran the migration and checked in the console for articles made prior to this migration, comments_count should reflect the number of comments for articles.
Now, the last step is to add counter_cache option to your comment model.
The counter_cache option makes sure the number in comments_count column is always updated whenever a comment is added or removed.
belongs_to :commentable, polymorphic: true, counter_cache: true
Now to get the cached value, you can either use:
article.comments.size, as if you use a counter_cache on a has_many association, size will use the cached count directly, and won't make any queries at all.
article.comments_count.
Similarly, to have comment.comments.count or comment.comments_count cached value, you need also to add another counter cache for comments table.
To get all the comments including nested comments for specific article, you need to get the article comments count(outer comments) + each comment comments count.
article.comments_count + article.comments.sum(:comments_count)
To DRY your code if you need to reuse it, you can add a instance method inside your article model as so:
def comments_count_including_nested
comments_count + comments.sum(:comments_count)
end
Then you can call article.comments_count_including_nested
You can get the number of comments and child comments like this:
article.comments.sum { |comment| 1 + comment.comments.count }
However, this will execute one query per parent comment on your article, which is not ideal.
Other ways of doing this:
adding an article_id to all comments, and filling the current ones with a migration
using a counter cache on comments

Using column modifiers from the command line in Rails Migrations

I am reading the Rails Guides on migrations. It states the following and I quote:
Some commonly used type modifiers can be passed directly on the
command line. They are enclosed by curly braces and follow the field type:
It provides an example:
$ bin/rails generate migration AddDetailsToProducts
'price:decimal{5,2}' supplier:references{polymorphic}
The Rails Guide also provides a list of column modifiers (SQL constraints):
limit
Sets the maximum size of the string/text/binary/integer fields.
precision
Defines the precision for the decimal fields, representing the total number of digits in the number.
scale
Defines the scale for the decimal fields, representing the number of digits after the decimal point.
polymorphic
Adds a type column for belongs_to associations.
null
Allows or disallows NULL values in the column.
default
Allows to set a default value on the column.
index
Adds an index for the column.
So now I want to use a couple of these column modifiers from the command line, but it does not generate the expected migration:
rails generate resource Employee first_name:string{limit,40, null,false} last_name:string{limit,40,null,false} birth_date:date sex:boolean salary:integer supervisor_id:integer{index,true} branch_id:integer{null,false,index,true}
The result:
class CreateEmployees < ActiveRecord::Migration[5.1]
def change
create_table :employees do |t|
t.string{limit,40, :first_name
t.string :null,false}
t.stringlimit :last_name
t.string40 :last_name
t.stringnull :last_name
t.stringfalse :last_name
t.date :birth_date
t.boolean :sex
t.integer :salary
t.integerindex :supervisor_id
t.integertrue :supervisor_id
t.integernull :branch_id
t.integerfalse :branch_id
t.integerindex :branch_id
t.integertrue :branch_id
t.timestamps
end
end
end
What am I doing wrong?
The format you are using is not quite correct. Take a look at rails g model --help for an explanation on how to use the modifiers. Here is a modified version of what you are looking for, though it doesn't handle all of the cases you are interested in:
rails generate resource Employee first_name:string{40} last_name:string{40} birth_date:date sex:boolean salary:integer supervisor_id:integer:index branch_id:integer:index
This generates the following:
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :first_name, limit: 40
t.string :last_name, limit: 40
t.date :birth_date
t.boolean :sex
t.integer :salary
t.integer :supervisor_id
t.integer :branch_id
t.timestamps null: false
end
add_index :employees, :supervisor_id
add_index :employees, :branch_id
end
end
You will have to manually add the null: false to those entries where you want it.
But, if Branch and Supervisor also AR objects, you can do the following:
rails generate resource Employee first_name:string{40} last_name:string{40} birth_date:date sex:boolean salary:integer supervisor:references branch:references
which generates the following:
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :first_name, limit: 40
t.string :last_name, limit: 40
t.date :birth_date
t.boolean :sex
t.integer :salary
t.references :supervisor, index: true, foreign_key: true
t.references :branch, index: true, foreign_key: true
t.timestamps null: false
end
end
end
which may be more what you are looking for

Associating two columns in the same Model class to the same column in another Model - Rails

Quick question - I have two model classes - Transactions and Accounts.
The Account Model looks as follows:
create_table "accounts", force: :cascade do |t|
t.string "account_name"
t.integer "account_number"
t.boolean "current_asset"
t.boolean "non_current_asset"
t.boolean "current_liability"
t.boolean "non_current_liability"
t.boolean "equity"
t.boolean "cost_of_sales"
t.boolean "operating_expense"
t.boolean "sales"
t.boolean "other_income"
t.boolean "bank"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
The Transaction Model looks as follows:
create_table "transactions", force: :cascade do |t|
t.date "date"
t.string "description"
t.string "reference"
t.integer "amount"
t.integer "account_id"
t.boolean "payment"
t.boolean "receipt"
t.integer "bank_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.boolean "vat"
t.integer "vat_amount"
t.integer "transaction_form_id"
t.integer "contact_id"
end
A user adds a new transaction which, through collection_select
they to choose an Account which displays all the accounts except for where bank == true (where bank == true it represents a bank account).
When a user selects a Bank, they can only choose a bank account - where bank==true (again through collection select).
I need to run a method that allows me to call all the Transaction amounts through the Account model based on the bank_id and not the account_id. So how would i associate two different columns of my Transaction model to the same column in the Accounts class through either account_id or bank_id.
The method will look something like:
<% Account.each do |account| %>
<% if account.bank == true && account.transaction(:bank_id) == account.id %>
<%= account.number %>
<%= account.number %>
<%= account.transactions.sum(:amount) %>
<% end %>
Ps: I know this is in my views, not controller, but that's for another discussion!
An image of Models example
I suggest you back up and re-think your approach.
A bank account and a GL account are very different things (even though they both use the word 'account'). Using a single model to represent both of these will lead to confusion and a lot of empty fields. Consider using a BankAccount and a GLAccount (you can use custom inflection on GLAccount, if you like, so you can do gl_account).
If you use BankAccount and GLAccount, then you can simply do a polymorphic association on your Transaction model, something like:
class Transaction < ActiveRecord::Base
belongs_to :account, polymorphic: true
end
You would need to change your transactions table to include account_type and account_id for that to work.
Then, your BankAccount and GLAccount models would look something like:
class BankAccount < ActiveRecord::Base
has_many :transactions, as: :account
end
and
class GLAccount < ActiveRecord::Base
has_many :transactions, as: :account
end
In which case, to iterate the BankAccounts, you would do something like:
<% BankAccount.all.each do |bank_account| %>
<%= bank_account.number %>
<%= bank_account.transactions.sum(:amount) %>
<% end %>
That'll end up with an N+1 query problem, but you can sort that out separately.
BTW, on your GL Accounts, you should use enums for the account type. As you currently have it set up, you will always have 8 empty boolean fields (not good). That might look something like:
class GLAccount < ActiveRecord::Base
has_many :transactions, as: :account
enum account_type: {
current_asset: 0,
non_current_asset: 1,
current_liability: 2,
non_current_liability: 3,
equity: 4,
cost_of_sales: 5,
operating_expense: 6,
sales: 7,
other_income: 8
}
end
Your gl_accounts table would need to include account_type as an integer for that to work.
A couple of other random notes:
if account.bank == true should just be if account.bank
You repeat <%= account.number %>
Given, however, what you have done, you could do something like:
class Account < ActiveRecord::Base
has_many bank_transactions, class_name: 'Transaction', foreign_key: :bank_id
end
In which case, you should be able to do:
<% Account.where(bank: true).each do |bank_account| %>
<%= bank_account.number %>
<%= bank_account.bank_transactions.sum(:amount) %>
<% end %>

Rails selection list issue

I have a model Member that has a new creation form. The form allows users to assign a department and position for each member to be a part of. The positions are relative to the department. In a separate part of the app, users can create departments and create/assign positions. I am trying to create a grouped_collection_select for the new members page, however my positions are not showing up, just departments as categories. I believe this is an association issue, where the positions are not being associated with their respective dept. I have the string department_id in my positions model, however I don't think the selection is able to read that as the parent. If anyone can point me in the correct direction that'd be awesome.
The line giving an error: (from the new members form_for)
<%= f.grouped_collection_select :title, Department.where(production_id: current_user.default_working_production_id).order(:department), :positions, :department, :id, :position, include_blank: true %>
My schema looks like:
create_table "departments", force: true do |t|
t.string "department"
t.datetime "created_at"
t.datetime "updated_at"
t.string "production_id"
end
create_table "positions", force: true do |t|
t.string "position"
t.string "department_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "production_id"
end
My model associations:
class Member < ActiveRecord::Base
belongs_to :company
validates :firstname, presence: true
validates :email, presence: true
def name
"#{firstname} #{lastname}"
end
end
class Department < ActiveRecord::Base
has_many :positions
attr_accessible :department
validates :department, presence: true
end
class Position < ActiveRecord::Base
belongs_to :department
attr_accessible :department_id, :position, :department
end
The primary keys (id) of each Model are integers, but the foreign keys for the associations are defined in your migrations as strings. (department_id, production_id). This may be causing problems when Rails tries to make the associations because for example, "1" == 1 evaluates to false. Change the foreign keys to integers via a new migration or by editing the existing migration and resetting the db.
You can confirm if you've fixed the association with the rails console:
$> rails console
Running via Spring preloader in process 20933
Loading development environment (Rails 4.2.5)
irb(main):001:0> Department.first.positions
This will query the First department in the db for all of its positions. If it throws an error then the association is not setup properly. If it returns a collection of positions, the association works.

Resources