|
Hi Railer - Thanks for visiting Fun On Rails !
I am writing this blog to share my ideas, experience, difficulties while working with ruby and rails. Blog mainly contains information regarding ruby, rails, gems, plugins, radiant, vi-editor, linux commands and many more fun...
Recent Posts
by sandipransing
CSV (comma separated values) files are frequently used to import/export data.
In rails 3, FasterCSV comes as default and below is the way to upload csv files inside rails applications. The code below will also show you how to generate csv in memory, parse on csv data, skip header, iterate over records, save records inside db, export upload error file and many more.
First, View to upload file
= form_tag upload_url, :multipart => true do
%label{:for => "file"} File to Upload
= file_field_tag "file"
= submit_tag
Assume upload_url maps to import action of customers controller
Controller code
class CustomersController < ApplicationController
[...]
def import
if request.post? && params[:file].present?
infile = params[:file].read
n, errs = 0, []
CSV.parse(infile) do |row|
n += 1
# SKIP: header i.e. first row OR blank row
next if n == 1 or row.join.blank?
# build_from_csv method will map customer attributes &
# build new customer record
customer = Customer.build_from_csv(row)
# Save upon valid
# otherwise collect error records to export
if customer.valid?
customer.save
else
errs << row
end
end
# Export Error file for later upload upon correction
if errs.any?
errFile ="errors_#{Date.today.strftime('%d%b%y')}.csv"
errs.insert(0, Customer.csv_header)
errCSV = FasterCSV.generate do |csv|
errs.each {|row| csv << row}
end
send_data errCSV,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename=#{errFile}.csv"
else
flash[:notice] = I18n.t('customer.import.success')
redirect_to import_url #GET
end
end
end
[...]
end
Customer model
class Customer < ActiveRecord::Base
scope :active, where(:active => true)
scope :latest, order('created_at desc')
def self.csv_header
"First Name,Last Name,Email,Phone,Mobile, Address, FAX, City".split(',')
end
def self.csv_new(row)
# find existing customer from email or create new
cust = find_or_initialize_by_email(row[2])
cust.attributes ={:first_name => row[0],
:last_name => row[1],
:email => row[3],
:phone => row[4],
:mobile => row[5],
:address => row[6],
:fax => row[7],
:city => row[8]}
return cust
end
end
Export customer records in CSV format
Below code loads customer records from database then generate csv_data inside memory and
exports data to browser using send_data method.
Note: As we are not writing on file system hence code can easily work heroku.
def export
# CRITERIA : to select customer records
#=> Customer.active.latest.limit(100)
custs = Customer.limit(10)
filename ="customers_#{Date.today.strftime('%d%b%y')}"
csv_data = FasterCSV.generate do |csv|
csv << Customer.csv_header
custs.each do |c|
csv << c.to_csv
end
end
send_data csv_data,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename=#{filename}.csv"
end
Read More…
by sandipransing
Previous post explains on mongoid document array field and rails form implementation
Below example shows rails form integration of array field of embedded mongoid document
consider scenario, student embeds one family who has many assets
class Student
include Mongoid::Document
field :name
field :phone
embeds_one :family
validates_associated :family
accepts_nested_attributes_for :family
end
class Family
include Mongoid::Document
ASSETS = ['flat', 'car', 'business', 'bunglow', 'cash']
field :members, type: Integer
field :assets, type: Array
field :religon
embedded_in :student
end
Brief controller code
class StudentsController < ApplicationController
def new
@student = Student.new
@student.family ||= @student.build_family
end
def create
@student = Student.new(params[:student])
@student.family.assets.reject!(&:blank?)
if @student.save
[...]
else
render :action => :new
end
end
end
view form will look like-
= form_for(@student) do |s|
= s.text_field :name
= s.text_field :phone
- s.fields_for :family do |f|
= f.text_field :members
= f.text_field :religion
- Family::ASSETS.each do |asset|
/Here f.object_name #=> student[family]
= f.check_box :assets, :name => "#{f.object_name}[assets][]", asset
Read More…
by sandipransing
mongoid document supports array as field. array field in mongoid document is a ruby array but its quite complex to manage array field in rails forms.
After lot of google and reading comments from stack-overflow at last i felt helpless. Finally after doing research on rails form helper object(form_for, fields_for) am pleased to get it working as expected :)
In below example, product can have multiple categories
class Product
CATEGORIES = %w(Apparel Media Software Sports Agri Education)
include Mongoid::Document
field :name, :type => String
field :categories, :type => Array
end
Here is form code
= form_for(@product) do |f|
= f.text_field :name
- Product::CATEGORIES.each do |category|
= f.check_box :categories, :name => "product[categories][]", category
Here is products controller code
class ProductsController < ApplicationController
before_filter :load_product, :only => [:new, :create]
[...]
# We don't need new action to be defined
def create
@product.attributes = params[:product]
# Here we need to reject blank categories
@product.categories.reject!(&:blank?)
if @product.save
flash[:notice] = I18n.t('product.create.success')
redirect_to(:action => :index)
else
render :action => :new
end
end
[...]
private
def load_product
@product = Product.new
end
end
Read More…
by sandipransing
twitter-bootstrap is pluggable css suit provided by twitter.
To know more about how to get started on it click here
Below post will help you out in getting started bootstrap css with rails app.
One need to add below files to helpers directory. MainForm can be used as base version of form builder and can be overriden for its subsequent use inside other custom form builders.
1. MainForm
# app/helpers/main_form.rb
class MainForm < ActionView::Helpers::FormBuilder # NestedForm::Builder
CSS = {
:label => 'label-control',
:hint => 'hint',
:hint_ptr => 'hint-pointer',
:error => 'help-inline',
:field_error => 'error',
:main_class => 'clearfix'
}
FIELDS = %w(radio_button check_box text_field text_area password_field select file_field collection_select email_field date_select)
def main_class(error=nil)
return CSS[:main_class] unless error
[CSS[:main_class], CSS[:field_error]].join(' ')
end
def required(name)
object.class.validators_on(name).map(&:class).include?(ActiveModel::Validations::PresenceValidator) rescue nil
end
def cancel(options={})
link = options.fetch(:return, "/")
@template.content_tag(:a, "Cancel", :href => link, :class => "btn_form button np_cancel_btn #{options[:class]}")
end
def submit(value="Save", options={})
options[:class] = "send_form_btn #{options[:class]}"
super
end
def label_class
{:class => CSS[:label]}
end
def label_tag(attribute, arg)
# Incase its a mandatory field, the '*' is added to the field.
txt = arg[:label] && arg[:label].to_s || attribute.to_s.titleize
txt<< '*' if(arg[:required] || required(attribute)) && arg[:required] != false
label(attribute, txt, label_class)
end
def error_tag(method_name, attribute)
errs = field_error(method_name, attribute)
@template.content_tag(:span, errs.first, :class => CSS[:error]) if errs.present?
end
def field_error(method_name, attribute)
return if @object && @object.errors.blank?
return @object.errors[attribute] if method_name != 'file_field'
@object.errors["#{attribute.to_s}_file_name"] | @object.errors["#{attribute.to_s}_file_size"] | @object.errors["#{attribute.to_s}_content_type"]
end
def hint_tag(txt)
hintPtr = @template.content_tag(:span, '', :class => CSS[:hint_ptr])
hintT = @template.content_tag(:span, txt + hintPtr, {:class => CSS[:hint]}, false)
end
def spinner_tag
@template.image_tag('spinner.gif', :class => :spinner,:id => :spinner)
end
end
ZeroForm is custom form builder which is inherited from main_form and its going to be actually used inside forms. Feel free to make custom form related changes inside this
ZeroForm
cat app/helpers/zero_form.rb
class ZeroForm < MainForm
# Overridden label_class here as we dont need class to be applied
def label_class
{}
end
def self.create_tagged_field(method_name)
define_method(method_name) do |attribute, *args|
arg = args.last && args.last.is_a?(Hash) && args.last || {}
# Bypass form-builder and do your own custom stuff!
return super(attribute, *args) if arg[:skip] && args.last.delete(:skip)
errT = error_tag(method_name, attribute)
labelT = label_tag(attribute, arg)
mainT = super(attribute, *args)
baseT = @template.content_tag(:div, mainT + errT)
hintT = hint_tag(arg[:hint]) if arg[:hint]
spinnerT = spinner_tag if arg[:spinner]
allT = labelT + baseT + spinnerT + hintT
@template.content_tag(:div, allT, :class => main_class(errT))
end
end
FIELDS.each do |name|
create_tagged_field(name)
end
end
In order to use Nested Forms you need to extend MainForm with NestedForm Builder
Integrate NestedForm with FormBuilder
class MainForm < NestedForm::Builder
end
View Form
= form_for @address ||= Address.new, :builder => ZeroForm do |f|
= f.text_field :street_address
= f.text_area :detail_address, :rows => 2
= f.text_field :city
= f.select :state, %w(US IN AUS UK UKRAINE)
= f.submit 'Save & Continue', :class => 'btn primary'
= link_to 'Skip »', '#'
To know more on twitter-bootstrap pagination in rails click here
Read More…
by sandipransing
How to get collection of models inside your application. Certainly there are many ways to do it.
Lets have a look at different ways starting from worst -
Get table names inside database and then iterating over to get model name
@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
#=> ["AdhearsionAudit", "AudioLog", "AuditDetail","TinyPrint", "TinyVideo", "UnknownCall", "UserAudit", "User"]
Select those with associated class
@models.delete_if{|m| m.constantize rescue true}
Load models dir
@models = Dir['app/models/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize.name }
Select ActiveRecord::Base extended class only
@models.reject!{|m| m.constantize.superclass != ActiveRecord::Base }
Get Active Record subclasses
# make sure relevant models are loaded otherwise
# require them prior
# Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
class A < ActiveRecord::Base
end
class B < A
end
ActiveRecord::Base.send(:subclasses).collect(&:name)
#=> [...., A]
How to get Inherited models too
class A < ActiveRecord::Base
end
class B < A
end
ActiveRecord::Base.descendants.collect(&:name)
#=> [...., A, B]
Below is more elegant solution provide by Vincent-robert over stack overflow which recursively looks for subsequent descendent's of class and gives you list from all over application
class Class
def extend?(klass)
not superclass.nil? and ( superclass == klass or superclass.extend? klass )
end
end
def models
Module.constants.select do |constant_name|
constant = eval constant_name
if not constant.nil? and constant.is_a? Class and constant.extend? ActiveRecord::Base
constant
end
end
end
Read More…
by sandipransing
Stripe is simple website payment solution and its very easy to easy setup
It currently supports only in US and seems to be very popular compared to other payment gateways because of its api & pricing
Stripe API provides -
1. charge (regular payments)
2. subscription (recurring payments)
3. managing customers (via stripe_customer_token)
What you need to do ?
Create a stripe account by providing email address and password. There after go to the manage account page to obtain stripe public & api keys.
Rails Integration
# Gemfile
gem stripe
# config/initializers/stripe.rb
Stripe.api_key = "rGaNWsIG3Gy6zvXB8wv4rEcizJp6XjF5"
STRIPE_PUBLIC_KEY = "vk_BcSyS2qPWdT5SdrwkQg0vTSyhZgqN"
# app/views/layouts/application.html.haml
= javascript_include_tag 'https://js.stripe.com/v1/'
= tag :meta, :name => 'stripe-key', :content => STRIPE_PUBLIC_KEY
Payment Form
# app/views/payments/new.html.haml
#stripe_error
%noscript JavaScript is not enabled and is required for this form. First enable it in your web browser settings.
= form_for @payment ||= Payment.new, :html => {:id => :payForm} do |p|
= p.hidden_field :stripe_card_token
.field
= p.text_field :amount
.credit_card_form
%h3.title
Enter Credit Card
- if @payment.stripe_card_token.present?
Credit card has been provided.
- else
.field
= label_tag :card_number, "Credit Card Number"
= text_field_tag :card_number, nil, name: nil
.field
= label_tag :card_code, "Security Code (CVV)"
= text_field_tag :card_code, nil, name: nil
.field
= label_tag :card_month, "Expiry Date"
= select_month nil, {add_month_numbers: true}, {name: nil, id: "card_month"}
= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+15}, {name: nil, id: "card_year"}
Javascript Code
# app/views/payments/new.js
var payment;
jQuery(function() {
Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'));
return payment.setupForm();
});
payment = {
setupForm: function() {
$('.head').click(function() {
$(this).css('disabled', true);
if($('#payment_stripe_card_token').val()){
$('#payForm').submit();
}
else{
payment.processCard();
}
});
},
processCard: function() {
var card;
card = {
number: $('#card_number').val(),
cvc: $('#card_code').val(),
expMonth: $('#card_month').val(),
expYear: $('#card_year').val()
};
return Stripe.createToken(card, payment.handleStripeResponse);
},
handleStripeResponse: function(status, response) {
if (status === 200) {
$('#payment_stripe_card_token').val(response.id)
$('#stripe_error').remove();
$('#payForm').submit();
} else {
$('#stripe_error').addClass('error').text(response.error.message);
$('.head').css('disabled', false);
}
}
};
Generate & Migrate Payment Model
rails g model payment status:string amount:float email:string transaction_number:string
rake db:migrate
Payment Model
# app/models/payment.rb
class Payment < ActiveRecord::Base
PROCESSING, FAILED, SUCCESS = 1, 2, 3
attr_accessible :stripe_card_token
validates :amount, :stripe_card_token, :presence => true, :numericality => { :greater_than => 0 }
def purchase
self.status = PROCESSING
customer = Stripe::Customer.create(description:email, card: stripe_card_token)
# OPTIONAL: save customer token for further reference
stripe_customer_token = customer.id
# Charge
charge = Stripe::Charge.create(
:amount => amount * 100, # $15.00 this time
:currency => "usd",
:customer => stripe_customer_token
)
if charge.paid
self.transaction_num = charge.id
self.status = SUCCESS
else
self.status = FAILED
end
return self
rescue Exception => e
errors.add :base, "There was a problem with your credit card."
self.status = FAILED
return self
end
end
Payments Controller
# app/controllers/payments_controller.rb
class PaymentsController < ApplicationController
def create
@payment = Payment.new(params[:payment])
if @payment.valid? && @payment.purchase
flash[:notice] = 'Thanks for Purchase!'
redirect_to root_url
else
render :action => :new
end
end
end
Read More…
by sandipransing
rails-uri module provide us with url manipulation methods
Parse string url
url = URI.parse('http://funonrails.com/search/label/rails3')
url.host #=> "http://funonrails.com"
url.port #=> 80
URL with Basic Authentication
url = URI.parse('http://sandip:2121@funonrails.com/search/label/rails3')
url.user #=> "sandip"
url.password #=> "2121"
Extracting urls form string paragraph
URI.extract('http://funonrails.com is rails blog authored by http://sandipransing.github.com contact mailto://sandip@funonrails.com')
#=> ["http://funonrails.com", "http://sandipransing.github.com", "mailto://sandip@funonrails.com"]
Split & Join URI
URI.split('http://sandip:2121@funonrails.com/search/label/rails3')
#=> ["http", "sandip:2121", "funonrails.com", nil, nil, "/search/label/rails3", nil, nil, nil]
<=> [Scheme, Userinfo, Host, Port, Registry, Path, Opaque, Query, Fragment]
URI.join('http://funonrails.com','search/label/rails3')
#=> #
Escape & Unescape alias encode/decode URI
URI.escape('http://funonrails.com/search/?label=\\rails\3')
URI.encode('http://funonrails.com/search/?label=\\rails\3')
#=> "http://funonrails.com/search/?label=%5Crails%5C3"
URI.unescape("http://funonrails.com/search/?label=%5Crails%5C3")
URI.decode("http://funonrails.com/search/?label=%5Crails%5C3")
#=> "http://funonrails.com/search/?label=\\rails\\3"
Match urls using regular expressions
"http://funonrails.com/search/label/rails3".sub(URI.regexp(['search'])) do |*matchs|
p $&
end
#=> "http://funonrails.com/search/label/rails3"
Getting requested url inside rails
request.request_uri
request.env['REQUEST_URI']
Getting previous page url inside rails
request.referrer
Read More…
by sandipransing
Paypal standard website payment service allows online payment transactions for websites.
Before implementing payments inside rails app needs to have following things in place-
1. Register Paypal sandbox account
2. Paypal Merchant account api credentials i.e. login, password, signature, application_id
3. Paypal Buyer account creds to test payments
Bundle Install
# Gemfile
gem 'activemerchant
Gateway config
# config/gateway.yml
development: &development
mode: test
login: rana_1317365002_biz_api1.gmail.com
password: '1311235050'
signature: ACxcVrB3mFChvPIe8aDWQlLhAPN46oPBQCj7rJWPza6CDZmBURg.
application_id: APP-76y884485P519543T
production:
<<: *development
test:
<<: *development
Generate & Migrate Payment Model
rails g model payment status:string amount:float transaction_number:string
rake db:migrate
Payment Model
# app/models/payment.rb
class Payment < ActiveRecord::Base
PROCESSING, FAILED, SUCCESS = 1, 2, 3
validates :amount, :presence => true, :numericality => { :greater_than => 0 }
def self.conf
@@gateway_conf ||= YAML.load_file(Rails.root.join('config/gateway.yml').to_s)[Rails.env]
end
## Paypal
def setup_purchase(options)
gateway.setup_purchase(amount * 100, options)
end
def redirect_url_for(token)
gateway.redirect_url_for(token)
end
def purchase(options={})
self.status = PROCESSING
#:ip => request.remote_ip,
#:payer_id => params[:payer_id],
#:token => params[:token]
response = gateway.purchase(amt, options)
if response.success?
self.transaction_num = response.params['transaction_id']
self.status = SUCCESS
else
self.status = FAILED
end
return self
rescue Exception => e
self.status = FAILED
return self
end
private
def gateway
ActiveMerchant::Billing::Base.mode = auth['mode'].to_sym
ActiveMerchant::Billing::PaypalExpressGateway.new(
:login => auth['login'],
:password => auth['password'],
:signature => auth['signature'])
end
def auth
self.class.conf
end
end
Billing routes
## Callback URL
match '/billing/paypal/:id/confirm', :to => 'billing#paypal', :as => :confirm_paypal
## Request URL
match '/billing/paypal/:id', :to => 'billing#checkout', :as => :billing
match '/billing/thank_you/:id', :to => 'billing#checkout', :as => :billing_thank_you
Billing Controller
# app/controllers/billing_controller1.rb
class BillingController < ApplicationController
before_filter :get_order, :only => [:checkout, :paypal, :thank_you]
# ASSUMPTION
# order is valid i.e. amount is entered
def checkout
response = @order.setup_purchase(:return_url => confirm_paypal_url(@order), :cancel_return_url => root_url)
redirect_to @order.redirect_url_for(response.token)
end
## CALL BACK
def paypal
@order = @order.purchase(:token => params[:token], :payer_id => params[:PayerID], :ip => request.remote_ip)
@order.save
redirect_to thank_you_billing_url(@order)
end
private
def get_order
@order = Payment.find_by_id(params[:id])
@order && @order.valid? || invalid_url
end
end
Views
# app/views/billing/thank_you.html.haml
- if @order.success?
%p The transaction is successfully completed
- else
%p The transaction failed
Read More…
by sandipransing
Authorize Net SIM gateway transaction skips merchant side creditcard details form and directs transaction to be take place on gateway server.
# Gemfile
gem 'authorize-net'
Register for authorize net sandbox account click herePayment gateway credentials
# config/gateway.yml
development: &development
mode: test
login: 9gdLh6T
key: 67fu45xw6VP92LX1
production:
<<: *development
test:
<<: *development
Generate & Migrate Payment Model
rails g model payment status:string amount:float transaction_number:string
rake db:migrate
SIM gateway methods extracted and added to payment model
# app/models/payment.rb
class Payment < ActiveRecord::Base
PROCESSING, FAILED, SUCCESS = 1, 2, 3
validates :amount, :presence => true, :numericality => { :greater_than => 0 }
def self.conf
@@gateway_conf ||= YAML.load_file(Rails.root.join('config/gateway.yml').to_s)[Rails.env]
end
def success?
self.status == SUCCESS
end
## Authorize :: SIM
def setup_transaction(options ={})
options.merge!(:link_method => AuthorizeNet::SIM::HostedReceiptPage::LinkMethod::POST)
t = AuthorizeNet::SIM::Transaction.new(
auth['login'], auth['key'], amount,
:hosted_payment_form => true,
:test => auth['mode']
)
t.set_hosted_payment_receipt(AuthorizeNet::SIM::HostedReceiptPage.new(options))
return t
end
def auth
self.class.conf
end
end
Payment routes
## Callback URL
match '/billing/:id/confirm', :to => 'billing#authorize', :as => :confirm_billing
## Request URL
match '/billing/:id', :to => 'billing#checkout', :as => :billing
match '/billing/:id/thank_you', :to => 'billing#thank_you', :as => :thank_you_billing
Billing controller
# app/controllers/billing_controller.rb
class BillingController < ApplicationController
helper :authorize_net
before_filter :get_order, :only => [:checkout, :authorize, :thank_you]
def checkout
# ASSUMPTION order is valid means amount is entered
@transaction = @order.setup_transaction(
{:link_text => 'Continue',
:link_url => confirm_billing_url(@order)})
end
## CALL BACK
def authorize
resp = AuthorizeNet::SIM::Response.new(params)
if resp.approved?
@order.status = Payment::SUCCESS
@order.transaction_num = resp.transaction_id
else
@order.status = Payment::FAILED
end
@order.save(:validate => false)
redirect_to thank_you_billing_url(@order)
end
private
def auth
Payment.conf
end
def get_order
@order = Payment.find_by_id(params[:id])
@order && @order.valid? || invalid_url
end
end
Views Forms
# app/views/billing/checkout.html.haml
= form_for :sim_transaction, :url => AuthorizeNet::SIM::Transaction::Gateway::TEST, :html => {:id => :authForm} do |f|
= sim_fields(@transaction)
:javascript
$(document).ready(function(){
$('#authForm').submit();
})
# app/views/billing/thank_you.html.haml
- if @order.success?
%p The transaction is successfully completed
- else
%p The transaction failed
Read More…
by sandipransing
Customizing default rails form builder to adopt for labels, input fields, errors, hints, etc. in order to build forms just in minutes
# app/helpers/app_form_builder.rb
class AppFormBuilder < ActionView::Helpers::FormBuilder
HELPERS = %w[check_box text_field text_area password_field select date_select datetime_select file_field collection_select state_select label calendar_date_select]
def self.create_tagged_field(method_name)
define_method(method_name) do |name, *args|
errs = object.errors.on(name.to_sym) if object && object.errors
# initialize some local variables
if args.last.is_a?(Hash)
label = args.last.delete(:label)
suffix = args.last.delete(:suffix)
klass = args.last.delete(:class)
req = args.last.delete(:required)
end
label = 'none' if method_name == 'hidden_field'
label ||= name.to_s.titleize
label = nil if label == 'none'
klass = klass ? [klass] : []
# Custom class if it exists
if method_name =~ /text_field|check_box|select/
klass << method_name
end
klass << 'f' #A default selector
klass << 'error' if errs.present?
klass = klass.join(' ')
# Required Field Notations
if req == 'all' || (req == 'new' && object.new_record?)
label << @template.content_tag(:span, :*, :class => :req)
end
suffix = @template.content_tag(:label, suffix) if suffix.present?
label = @template.content_tag(:label, label) if label.present?
errs = @template.content_tag(:span, errs.to_s, :class => :message) if errs.present?
reverse = true if method_name == 'check_box'
if reverse
content = "#{super} #{suffix} #{label} #{errs}"
else
content = "#{label} #{super} #{suffix} #{errs}"
end
@template.content_tag(:div, content, :class => klass)
end
end
HELPERS.each do |name|
create_tagged_field(name)
end
end
Read More…
|