Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marlosirapuan/bbda37a72eda7149a59b9c2d25ceddbd to your computer and use it in GitHub Desktop.
Save marlosirapuan/bbda37a72eda7149a59b9c2d25ceddbd to your computer and use it in GitHub Desktop.
Custom field example for Ruby on Rails with Activerecord & PostgreSQL & Metaprogramming. Example: https://github.com/lab2023/postgresql_jsonb_ransack_rails_5 Demo: https://rails-custom-field-ransack.herokuapp.com
begin
require 'bundler/inline'
rescue LoadError => e
$stderr.puts 'Bundler version 1.10 or later is required. Update your Bundler'
raise e
end
gemfile(true) do
source 'https://rubygems.org'
gem 'activerecord', '5.1.3'
gem 'pg', '~> 0.18.4'
end
# Require gems
require 'active_record'
require 'minitest/autorun'
require 'logger'
# DB configurations
db_config = {
adapter: 'postgresql',
host: 'localhost',
database: 'custom_field',
username: 'postgres'
}
admin_config = {
database: 'postgres',
schema_search_path: 'public'
}
db_config_admin = db_config.merge(admin_config)
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(db_config_admin)
ActiveRecord::Base.connection.drop_database(db_config[:database])
ActiveRecord::Base.connection.create_database(db_config[:database])
# Migrations
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.integer :status, default: 0
t.jsonb :metadata, default: {}, null: false
end
add_index :posts, :metadata, using: :gin
end
# Helpers
def custom_field_method(field)
"metadata_#{field}"
end
# Models
class Post < ActiveRecord::Base
end
# Custom fields
# You can store this information in database as jsonb datatype
POST_FIELDS = {
title: {
name: 'Title',
validations: [],
input_type: 'string'
},
description: {
name: 'Description',
validations: [],
input_type: 'string'
},
number: {
name: 'Number',
validations: [:number],
input_type: 'number'
}
}.freeze
POST_FIELDS.each do |field, options|
field = field.to_s
Post.class_eval do
method = custom_field_method(field)
options[:validations].each do |validation|
validates_numericality_of method if validation == :number
end
end
Post.instance_eval do
method = custom_field_method(field)
define_method "#{method}=" do |val|
metadata[field] = val
end
define_method method do
metadata[field]
end
end
end
# Tests
class PostTest < Minitest::Test
def test_metadata_fields
post = Post.new(
metadata_title: 'title',
metadata_description: 'description',
metadata_number: 1
)
assert_equal true, post.save
assert_equal 'title', post.metadata_title
assert_equal 'description', post.metadata_description
end
def test_valid_metadata_number
post = Post.new(metadata_number: 1)
assert_equal true, post.save
assert_equal true, post.valid?
end
def test_valid_negative_metadata_number
post = Post.new(metadata_number: -122)
assert_equal true, post.save
assert_equal true, post.metadata_number.is_a?(Integer)
post = Post.new(metadata_number: -122.33)
assert_equal true, post.save
assert_equal true, post.metadata_number.is_a?(Float)
end
def test_invalid_metadata_number
post = Post.new(metadata_number: 'title')
assert_equal false, post.save
assert_equal true, post.errors[:metadata_number].any?
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment