Skip to content

Instantly share code, notes, and snippets.

@svoboda-jan
Created September 25, 2013 13:24
Show Gist options
  • Save svoboda-jan/6699529 to your computer and use it in GitHub Desktop.
Save svoboda-jan/6699529 to your computer and use it in GitHub Desktop.
Rails code sample with CouchDB database, INI config files and custom email validation
# rails initializer to load *.ini into Rails.application.config
Dir.glob(File.join Rails.root, "/config/**/*.ini").each do |file_name|
config_name = File.basename(file_name, ".ini")
Rails.application.config.send "#{config_name}=", IniFile.new(file_name).to_struct.send(Rails.env)
end
require 'resolv'
require 'mail'
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
email = Mail::Address.new(value) rescue nil
return invalid(record, attribute) unless email.present?
# verify domain and parsed email address
return invalid(record, attribute) unless email.domain.present? and email.address == value
# valid domain must have at least two dot_atom_text elements (domainname.tld)
domain_tree = email.send :tree # mail gem does not expose the tree method
return invalid(record, attribute) unless domain_tree.domain.dot_atom_text.elements.size >= 2
# check if the email domain has MX records
Resolv::DNS.open do |dns|
mx_records = dns.getresources(email.domain, Resolv::DNS::Resource::IN::MX)
return invalid(record, attribute) unless mx_records.present?
end
end
private
def invalid(record, attribute)
record.errors[attribute] << I18n.t('errors.messages.invalid')
end
end
require 'ostruct'
require 'stringio'
class IniFile
def initialize(stream_or_path)
@options = {}
if stream_or_path.respond_to? :read
read_from_stream(stream_or_path)
else
read_from_stream(StringIO.new(File.read(stream_or_path)))
end
end
def to_hash
@options
end
alias_method :to_h, :to_hash
def to_struct
struct = OpenStruct.new
@options.each do |section,values|
struct.send("#{section}=", OpenStruct.new(values))
end
struct
end
private
def read_from_stream(stream)
section = :general
while l = stream.gets
l.strip!
next if l.start_with? "#" or l.start_with? ";"
if l.start_with? "[" and l.end_with? "]"
section = l[1..-2].to_sym
@options[section] ||= {}
else
key, val = l.split("=", 2)
next if key.nil? or val.nil?
key = key.strip.to_sym
val.strip!
if @options[section].has_key?(key)
unless @options[section][key].is_a?(Array)
@options[section][key] = [@options[section][key]]
end
@options[section][key] << val
else
@options[section][key] = val
end
end
end
end
end
if __FILE__ == $0
require 'test/unit'
require 'stringio'
INI_FILE = <<-INI
[example.com]
url=http://localhost:3000
[cluster.example.com]
url=http://localhost:3000
url=http://localhost:3001
url=http://localhost:3002
INI
class TestIniFile < Test::Unit::TestCase
def setup
@ini_file = IniFile.new(StringIO.new(INI_FILE))
@hash = @ini_file.to_h
end
def test_parsing_sections_to_hash_keys
assert_equal [:"example.com", :"cluster.example.com"], @hash.keys
end
def test_parsing_sections_to_hash_values
assert_equal [
{:url=>"http://localhost:3000"},
{:url=>["http://localhost:3000", "http://localhost:3001", "http://localhost:3002"]}
], @hash.values
end
def test_parsing_multiple_values_to_array
assert_equal [
"http://localhost:3000",
"http://localhost:3001",
"http://localhost:3002"
], @hash[:"cluster.example.com"][:url]
end
end
end
class InventoryCard < CouchRest::Model::Base
include TenantModel
include ActiveModel::SerializerSupport
include TranslatedModel
include TranslatedParameterizedName
belongs_to :warehouse, :class_name => "Warehouse", :foreign_key => "warehouse_id"
belongs_to :supplier, :class_name => "Supplier", :foreign_key => "supplier_id"
belongs_to :vat_rate, :class_name => "VatRate", :foreign_key => "vat_rate_id"
collection_of :categories
property :sku, String
translates :name
translates :parameterized_name
translates :description
property :active, TrueClass, :default => true
#
# ... other property declarations ...
#
timestamps!
design do
view :by_tenant_id_and_locale_and_parameterized_name, {
:map => "
function(doc) {
if (doc['type'] == 'InventoryCard' && doc.name_translations && doc.active) {
for(locale in doc.name_translations) {
emit([
doc.tenant_id,
locale,
doc.parameterized_name_translations[locale]
], 1)
}
}
}
",
:reduce => "
function(keys, values, rereduce) {
return sum(values);
}
"
}
# ... other view declarations ...
end
alias_method :to_param, :parameterized_name
end
class Api::V1::InventoryCardsController < Api::V1::BaseController
before_action :find_resource, :only => %w{ show update destroy upload_image destroy_image }
# sets HTTP headers for current page, per page and count and also @per_page var
set_pagination_headers :inventory_cards, 10, :only => %w{ index }
def index
@inventory_cards = current_tenant.inventory_cards
.page(params[:page]).per(@per_page)
respond_with @inventory_cards.to_a
end
def show
respond_with @inventory_card
end
def create
@inventory_card = InventoryCard.create(
params[:inventory_card].merge(:user_id => current_user.id, :id => nil)
)
respond_with @inventory_card
end
def update
@inventory_card.update_attributes params[:inventory_card]
respond_with @inventory_card
end
def destroy
respond_with @inventory_card.destroy
end
def upload_image
@inventory_card.add_image(params[:image].tempfile)
respond_with @inventory_card.images
end
def destroy_image
@inventory_card.delete_image(params[:file_name])
respond_with @inventory_card.images
end
private
def find_resource
@inventory_card = InventoryCard.secure_find!(params[:id])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment