memberfier 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +11 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/application.js +15 -0
- data/app/assets/javascripts/potentialmember_imports.js.coffee +3 -0
- data/app/assets/javascripts/potentialmembers.js.coffee +3 -0
- data/app/assets/stylesheets/application.css +13 -0
- data/app/assets/stylesheets/layout.css.scss +64 -0
- data/app/assets/stylesheets/potentialmember_imports.css.scss +1 -0
- data/app/assets/stylesheets/potentialmembers.css.scss +9 -0
- data/app/backends/memberfier/mongo_store.rb +43 -0
- data/app/backends/memberfier/redis_store.rb +25 -0
- data/app/controllers/memberfier/potentialmember_imports_controller.rb +14 -0
- data/app/controllers/memberfier/potentialmembers_controller.rb +12 -0
- data/app/mailers/memberfier/potentialmember_mailer.rb +13 -0
- data/app/models/potentialmember.rb +30 -0
- data/app/models/potentialmember_import.rb +56 -0
- data/app/views/layouts/memberfier.html.erb +440 -0
- data/app/views/memberfier/potentialmember_imports/new.html.haml +25 -0
- data/app/views/memberfier/potentialmembers/index.html.haml +16 -0
- data/app/views/memberfier/potentialmembers/index.xls.erb +25 -0
- data/config/routes.rb +16 -0
- data/lib/memberfier.rb +55 -0
- data/lib/memberfier/engine.rb +11 -0
- data/lib/memberfier/potentialmember_importer.rb +9 -0
- data/lib/memberfier/version.rb +3 -0
- data/lib/tasks/memberfier_tasks.rake +4 -0
- metadata +71 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MmYyOWE0ZmViNTA2YTUxZmFhYWI5MmE2M2ZkNTE4NjZhODA2M2E0Nw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZGVkNjcxMzcxODM1YzExYTAzM2I2MWNkNTk5NTQwMzllMDZkYjM5YQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
M2ZjNjUwNjg5ODJlNjUzZGQ4ODMyMmNiZDQxOTg5ODNiZGNkZTBkYmI2MGM3
|
10
|
+
NDA1ZjlkZTc1NWRkODRmNjQ3ZTNmZjZkOTQ5MTE0Y2JkZmEzMDRjOGU3Mjc4
|
11
|
+
NTljNTNjMWU0MGNjZTc4ZWU2ZDg3NTUzMjFmYjI1ZWM3NDRhNjM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NzRlMDk0Mzk3NzU0MWM0NGE3YzE0OTdhMWNmMzU0M2Q3NzIxN2I1YmNjODE2
|
14
|
+
MTU5MjM3MzZkYTQ1YmFhMjlmNDViYjJkMmZlZTNhNWExMDc1NWZhMjM2MGJj
|
15
|
+
MDNmMTZkZDAwNWQ3MzcyNjM0NjFmNmZmYzkzMDI4YWY4YjliZjc=
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
= Memberfier
|
2
|
+
|
3
|
+
Memberfier imports details of potential members of a site and emails them recurringly until until they become members.
|
4
|
+
Idea here is to bypass spam filters and send people various emails until they register to your site.
|
5
|
+
Say you have a site that makes service providers give service to people. You need service providers first, right?
|
6
|
+
So google for your service providers and get their emails, phone numbers, address and names of them from their websites or
|
7
|
+
bulk get from yellow pages. Put them to any spreadsheet and import them to memberfier.
|
8
|
+
Memberfier first sends an email to those service providers and next day checks their membership status to find out if some of
|
9
|
+
them has registered. Then in a week it sends another mass email to unregistered providers and so on.
|
10
|
+
The project uses mongoid and redis-resque on backend and send grid to send emails.
|
11
|
+
Work in progress...
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Memberfier'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,64 @@
|
|
1
|
+
html, body {
|
2
|
+
background-color: #4B7399;
|
3
|
+
font-family: Verdana, Helvetica, Arial;
|
4
|
+
font-size: 14px;
|
5
|
+
}
|
6
|
+
|
7
|
+
a {
|
8
|
+
color: #0000FF;
|
9
|
+
img { border: none; }
|
10
|
+
}
|
11
|
+
|
12
|
+
.clear {
|
13
|
+
clear: both;
|
14
|
+
height: 0;
|
15
|
+
overflow: hidden;
|
16
|
+
}
|
17
|
+
|
18
|
+
#container {
|
19
|
+
width: 80%;
|
20
|
+
margin: 0 auto;
|
21
|
+
background-color: #FFF;
|
22
|
+
padding: 20px 40px;
|
23
|
+
border: solid 1px black;
|
24
|
+
margin-top: 20px;
|
25
|
+
}
|
26
|
+
|
27
|
+
#flash_notice {
|
28
|
+
color: #00B205;
|
29
|
+
}
|
30
|
+
|
31
|
+
#flash_alert {
|
32
|
+
color: #D00;
|
33
|
+
}
|
34
|
+
|
35
|
+
.error_messages, #error_explanation {
|
36
|
+
width: 400px;
|
37
|
+
border: 2px solid #CF0000;
|
38
|
+
padding: 0px;
|
39
|
+
padding-bottom: 12px;
|
40
|
+
margin-bottom: 20px;
|
41
|
+
background-color: #f0f0f0;
|
42
|
+
font-size: 12px;
|
43
|
+
|
44
|
+
h2 {
|
45
|
+
text-align: left;
|
46
|
+
font-weight: bold;
|
47
|
+
padding: 5px 10px;
|
48
|
+
font-size: 12px;
|
49
|
+
margin: 0;
|
50
|
+
background-color: #c00;
|
51
|
+
color: #fff;
|
52
|
+
}
|
53
|
+
|
54
|
+
p { margin: 8px 10px; }
|
55
|
+
ul { margin-bottom: 0; }
|
56
|
+
}
|
57
|
+
|
58
|
+
.field_with_errors {
|
59
|
+
display: inline;
|
60
|
+
}
|
61
|
+
|
62
|
+
form .field, form .actions {
|
63
|
+
margin: 12px 0;
|
64
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Memberfier
|
2
|
+
class MongoStore
|
3
|
+
def initialize(collection)
|
4
|
+
@collection = collection
|
5
|
+
end
|
6
|
+
|
7
|
+
def keys
|
8
|
+
@collection.distinct :_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def []=(key, value)
|
12
|
+
value = nil if value.blank?
|
13
|
+
collection.update({:_id => key},
|
14
|
+
{'$set' => {:value => ActiveSupport::JSON.encode(value)}},
|
15
|
+
{:upsert => true, :safe => true})
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
if document = collection.find_one(:_id => key)
|
20
|
+
document["value"]
|
21
|
+
else
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroy_entry(key)
|
27
|
+
@collection.remove({:_id => key})
|
28
|
+
end
|
29
|
+
|
30
|
+
def searchable?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear_database
|
35
|
+
collection.drop
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def collection; @collection; end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Memberfier
|
2
|
+
class RedisStore
|
3
|
+
def initialize(redis)
|
4
|
+
@redis = redis
|
5
|
+
end
|
6
|
+
|
7
|
+
def keys
|
8
|
+
@redis.keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def []=(key, value)
|
12
|
+
value = nil if value.blank?
|
13
|
+
@redis[key] = ActiveSupport::JSON.encode(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
@redis[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear_database
|
21
|
+
@redis.keys.clone.each {|key| @redis.del key }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class PotentialmemberImportsController < ApplicationController
|
2
|
+
def new
|
3
|
+
@potentialmember_import = PotentialmemberImport.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def create
|
7
|
+
@potentialmember_import = PotentialmemberImport.new(params[:potentialmember_import])
|
8
|
+
if @potentialmember_import.save
|
9
|
+
redirect_to root_url, notice: "Imported potentialmembers successfully."
|
10
|
+
else
|
11
|
+
render :new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Memberfier
|
2
|
+
class PotentialmembersController < ApplicationController
|
3
|
+
def index
|
4
|
+
@potentialmembers = Potentialmember.order(:name)
|
5
|
+
respond_to do |format|
|
6
|
+
format.html
|
7
|
+
format.csv { send_data @potentialmembers.to_csv }
|
8
|
+
format.xls
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class ProviderMailer < ActionMailer::Base
|
2
|
+
include Resque::Mailer
|
3
|
+
default from: APP_CONFIG.mailer.from
|
4
|
+
|
5
|
+
def registration_email(provider)
|
6
|
+
@provider = provider
|
7
|
+
|
8
|
+
@provider_url = "#{APP_CONFIG.base_url}/hizmetsgl/#{provider['_id']}"
|
9
|
+
@howto_url = "#{APP_CONFIG.base_url}/hizmetverenler"
|
10
|
+
mail(to: provider["business_email"], subject: t('provider_mailer.registration_email.subject'))
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'iconv'
|
3
|
+
class Potentialmember
|
4
|
+
include Mongoid::Document
|
5
|
+
include Mongoid::Potentialmember
|
6
|
+
|
7
|
+
|
8
|
+
field :name
|
9
|
+
field :email
|
10
|
+
field :phone
|
11
|
+
field :company
|
12
|
+
field :website
|
13
|
+
field :address
|
14
|
+
field :work_types
|
15
|
+
|
16
|
+
validates_presence_of :name
|
17
|
+
validates_presence_of :email
|
18
|
+
validates_presence_of :phone
|
19
|
+
validates_presence_of :company
|
20
|
+
|
21
|
+
def self.to_csv(options = {})
|
22
|
+
CSV.generate(options) do |csv|
|
23
|
+
csv << column_names
|
24
|
+
all.each do |potentialmember|
|
25
|
+
csv << potentialmember.attributes.values_at(*column_names)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class PotentialmemberImport
|
2
|
+
# switch to ActiveModel::Model in Rails 4
|
3
|
+
extend ActiveModel::Model
|
4
|
+
#extend ActiveModel::Naming
|
5
|
+
include ActiveModel::Conversion
|
6
|
+
include ActiveModel::Validations
|
7
|
+
|
8
|
+
attr_accessor :file
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
attributes.each { |name, value| send("#{name}=", value) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def persisted?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def save
|
19
|
+
if imported_potentialmembers.map(&:valid?).all?
|
20
|
+
imported_potentialmembers.each(&:save!)
|
21
|
+
true
|
22
|
+
else
|
23
|
+
imported_potentialmembers.each_with_index do |potentialmember, index|
|
24
|
+
potentialmember.errors.full_messages.each do |message|
|
25
|
+
errors.add :base, "Row #{index+2}: #{message}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def imported_potentialmembers
|
33
|
+
@imported_potentialmembers ||= load_imported_potentialmembers
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_imported_potentialmembers
|
37
|
+
spreadsheet = open_spreadsheet
|
38
|
+
header = spreadsheet.row(1)
|
39
|
+
(2..spreadsheet.last_row).map do |i|
|
40
|
+
row = Hash[[header, spreadsheet.row(i)].transpose]
|
41
|
+
potentialmember = Potentialmember.find_by_id(row["id"]) || Potentialmember.new
|
42
|
+
potentialmember.attributes = row.to_hash.slice(*Potentialmember.accessible_attributes)
|
43
|
+
potentialmember
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def open_spreadsheet
|
48
|
+
case File.extname(file.original_filename)
|
49
|
+
when ".csv" then Csv.new(file.path, nil, :ignore)
|
50
|
+
when ".xls" then Excel.new(file.path, nil, :ignore)
|
51
|
+
when ".xlsx" then Excelx.new(file.path, nil, :ignore)
|
52
|
+
else raise "Unknown file type: #{file.original_filename}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,440 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/html4/loose.dtd">
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<title>Memberfier</title>
|
6
|
+
<%= csrf_meta_tag %>
|
7
|
+
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
|
8
|
+
<script type="text/javascript" src="http://www.jacklmoore.com/autosize/jquery.autosize.js"></script>
|
9
|
+
<script type="text/javascript">
|
10
|
+
/**
|
11
|
+
* Unobtrusive scripting adapter for jQuery
|
12
|
+
*
|
13
|
+
* Requires jQuery 1.4.3 or later.
|
14
|
+
* https://github.com/rails/jquery-ujs
|
15
|
+
*/
|
16
|
+
|
17
|
+
(function($) {
|
18
|
+
// Make sure that every Ajax request sends the CSRF token
|
19
|
+
function CSRFProtection(fn) {
|
20
|
+
var token = $('meta[name="csrf-token"]').attr('content');
|
21
|
+
if (token) fn(function(xhr) { xhr.setRequestHeader('X-CSRF-Token', token) });
|
22
|
+
}
|
23
|
+
if ($().jquery == '1.5') { // gruesome hack
|
24
|
+
var factory = $.ajaxSettings.xhr;
|
25
|
+
$.ajaxSettings.xhr = function() {
|
26
|
+
var xhr = factory();
|
27
|
+
CSRFProtection(function(setHeader) {
|
28
|
+
var open = xhr.open;
|
29
|
+
xhr.open = function() { open.apply(this, arguments); setHeader(this) };
|
30
|
+
});
|
31
|
+
return xhr;
|
32
|
+
};
|
33
|
+
}
|
34
|
+
else $(document).ajaxSend(function(e, xhr) {
|
35
|
+
CSRFProtection(function(setHeader) { setHeader(xhr) });
|
36
|
+
});
|
37
|
+
|
38
|
+
// Triggers an event on an element and returns the event result
|
39
|
+
function fire(obj, name, data) {
|
40
|
+
var event = new $.Event(name);
|
41
|
+
obj.trigger(event, data);
|
42
|
+
return event.result !== false;
|
43
|
+
}
|
44
|
+
|
45
|
+
// Submits "remote" forms and links with ajax
|
46
|
+
function handleRemote(element) {
|
47
|
+
var method, url, data,
|
48
|
+
dataType = element.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType);
|
49
|
+
|
50
|
+
if (element.is('form')) {
|
51
|
+
method = element.attr('method');
|
52
|
+
url = element.attr('action');
|
53
|
+
data = element.serializeArray();
|
54
|
+
// memoized value from clicked submit button
|
55
|
+
var button = element.data('ujs:submit-button');
|
56
|
+
if (button) {
|
57
|
+
data.push(button);
|
58
|
+
element.data('ujs:submit-button', null);
|
59
|
+
}
|
60
|
+
} else {
|
61
|
+
method = element.attr('data-method');
|
62
|
+
url = element.attr('href');
|
63
|
+
data = null;
|
64
|
+
}
|
65
|
+
|
66
|
+
$.ajax({
|
67
|
+
url: url, type: method || 'GET', data: data, dataType: dataType,
|
68
|
+
// stopping the "ajax:beforeSend" event will cancel the ajax request
|
69
|
+
beforeSend: function(xhr, settings) {
|
70
|
+
if (settings.dataType === undefined) {
|
71
|
+
xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
|
72
|
+
}
|
73
|
+
return fire(element, 'ajax:beforeSend', [xhr, settings]);
|
74
|
+
},
|
75
|
+
success: function(data, status, xhr) {
|
76
|
+
element.trigger('ajax:success', [data, status, xhr]);
|
77
|
+
},
|
78
|
+
complete: function(xhr, status) {
|
79
|
+
element.trigger('ajax:complete', [xhr, status]);
|
80
|
+
},
|
81
|
+
error: function(xhr, status, error) {
|
82
|
+
element.trigger('ajax:error', [xhr, status, error]);
|
83
|
+
}
|
84
|
+
});
|
85
|
+
}
|
86
|
+
|
87
|
+
// Handles "data-method" on links such as:
|
88
|
+
// <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
|
89
|
+
function handleMethod(link) {
|
90
|
+
var href = link.attr('href'),
|
91
|
+
method = link.attr('data-method'),
|
92
|
+
csrf_token = $('meta[name=csrf-token]').attr('content'),
|
93
|
+
csrf_param = $('meta[name=csrf-param]').attr('content'),
|
94
|
+
form = $('<form method="post" action="' + href + '"></form>'),
|
95
|
+
metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
|
96
|
+
|
97
|
+
if (csrf_param !== undefined && csrf_token !== undefined) {
|
98
|
+
metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
|
99
|
+
}
|
100
|
+
|
101
|
+
form.hide().append(metadata_input).appendTo('body');
|
102
|
+
form.submit();
|
103
|
+
}
|
104
|
+
|
105
|
+
function disableFormElements(form) {
|
106
|
+
form.find('input[data-disable-with]').each(function() {
|
107
|
+
var input = $(this);
|
108
|
+
input.data('ujs:enable-with', input.val())
|
109
|
+
.val(input.attr('data-disable-with'))
|
110
|
+
.attr('disabled', 'disabled');
|
111
|
+
});
|
112
|
+
}
|
113
|
+
|
114
|
+
function enableFormElements(form) {
|
115
|
+
form.find('input[data-disable-with]').each(function() {
|
116
|
+
var input = $(this);
|
117
|
+
input.val(input.data('ujs:enable-with')).removeAttr('disabled');
|
118
|
+
});
|
119
|
+
}
|
120
|
+
|
121
|
+
function allowAction(element) {
|
122
|
+
var message = element.attr('data-confirm');
|
123
|
+
return !message || (fire(element, 'confirm') && confirm(message));
|
124
|
+
}
|
125
|
+
|
126
|
+
function requiredValuesMissing(form) {
|
127
|
+
var missing = false;
|
128
|
+
form.find('input[name][required]').each(function() {
|
129
|
+
if (!$(this).val()) missing = true;
|
130
|
+
});
|
131
|
+
return missing;
|
132
|
+
}
|
133
|
+
|
134
|
+
$('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) {
|
135
|
+
var link = $(this);
|
136
|
+
if (!allowAction(link)) return false;
|
137
|
+
|
138
|
+
if (link.attr('data-remote') != undefined) {
|
139
|
+
handleRemote(link);
|
140
|
+
return false;
|
141
|
+
} else if (link.attr('data-method')) {
|
142
|
+
handleMethod(link);
|
143
|
+
return false;
|
144
|
+
}
|
145
|
+
});
|
146
|
+
|
147
|
+
$('form').live('submit.rails', function(e) {
|
148
|
+
var form = $(this), remote = form.attr('data-remote') != undefined;
|
149
|
+
if (!allowAction(form)) return false;
|
150
|
+
|
151
|
+
// skip other logic when required values are missing
|
152
|
+
if (requiredValuesMissing(form)) return !remote;
|
153
|
+
|
154
|
+
if (remote) {
|
155
|
+
handleRemote(form);
|
156
|
+
return false;
|
157
|
+
} else {
|
158
|
+
// slight timeout so that the submit button gets properly serialized
|
159
|
+
setTimeout(function(){ disableFormElements(form) }, 13);
|
160
|
+
}
|
161
|
+
});
|
162
|
+
|
163
|
+
$('form input[type=submit], form button[type=submit], form button:not([type])').live('click.rails', function() {
|
164
|
+
var button = $(this);
|
165
|
+
if (!allowAction(button)) return false;
|
166
|
+
// register the pressed submit button
|
167
|
+
var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null;
|
168
|
+
button.closest('form').data('ujs:submit-button', data);
|
169
|
+
});
|
170
|
+
|
171
|
+
$('form').live('ajax:beforeSend.rails', function(event) {
|
172
|
+
if (this == event.target) disableFormElements($(this));
|
173
|
+
});
|
174
|
+
|
175
|
+
$('form').live('ajax:complete.rails', function(event) {
|
176
|
+
if (this == event.target) enableFormElements($(this));
|
177
|
+
});
|
178
|
+
$(document).ready(function() {
|
179
|
+
$('textarea').autosize();
|
180
|
+
});
|
181
|
+
})( jQuery );
|
182
|
+
|
183
|
+
</script>
|
184
|
+
<style type='text/css'>
|
185
|
+
/*
|
186
|
+
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
|
187
|
+
Code licensed under the BSD License:
|
188
|
+
http://developer.yahoo.com/yui/license.html
|
189
|
+
version: 3.3.0
|
190
|
+
build: 3167
|
191
|
+
*/
|
192
|
+
/*
|
193
|
+
TODO will need to remove settings on HTML since we can't namespace it.
|
194
|
+
TODO with the prefix, should I group by selector or property for weight savings?
|
195
|
+
*/
|
196
|
+
html{
|
197
|
+
color:#000;
|
198
|
+
background:#FFF;
|
199
|
+
}
|
200
|
+
/*
|
201
|
+
TODO remove settings on BODY since we can't namespace it.
|
202
|
+
*/
|
203
|
+
/*
|
204
|
+
TODO test putting a class on HEAD.
|
205
|
+
- Fails on FF.
|
206
|
+
*/
|
207
|
+
body,
|
208
|
+
div,
|
209
|
+
dl,
|
210
|
+
dt,
|
211
|
+
dd,
|
212
|
+
ul,
|
213
|
+
ol,
|
214
|
+
li,
|
215
|
+
h1,
|
216
|
+
h2,
|
217
|
+
h3,
|
218
|
+
h4,
|
219
|
+
h5,
|
220
|
+
h6,
|
221
|
+
pre,
|
222
|
+
code,
|
223
|
+
form,
|
224
|
+
fieldset,
|
225
|
+
legend,
|
226
|
+
input,
|
227
|
+
textarea,
|
228
|
+
p,
|
229
|
+
blockquote,
|
230
|
+
th,
|
231
|
+
td {
|
232
|
+
margin:0;
|
233
|
+
padding:0;
|
234
|
+
}
|
235
|
+
table {
|
236
|
+
border-collapse:collapse;
|
237
|
+
border-spacing:0;
|
238
|
+
}
|
239
|
+
fieldset,
|
240
|
+
img {
|
241
|
+
border:0;
|
242
|
+
}
|
243
|
+
/*
|
244
|
+
TODO think about hanlding inheritence differently, maybe letting IE6 fail a bit...
|
245
|
+
*/
|
246
|
+
address,
|
247
|
+
caption,
|
248
|
+
cite,
|
249
|
+
code,
|
250
|
+
dfn,
|
251
|
+
em,
|
252
|
+
strong,
|
253
|
+
th,
|
254
|
+
var {
|
255
|
+
font-style:normal;
|
256
|
+
font-weight:normal;
|
257
|
+
}
|
258
|
+
/*
|
259
|
+
TODO Figure out where this list-style rule is best set. Hedger has a request to investigate.
|
260
|
+
*/
|
261
|
+
li {
|
262
|
+
list-style:none;
|
263
|
+
}
|
264
|
+
|
265
|
+
caption,
|
266
|
+
th {
|
267
|
+
text-align:left;
|
268
|
+
}
|
269
|
+
h1,
|
270
|
+
h2,
|
271
|
+
h3,
|
272
|
+
h4,
|
273
|
+
h5,
|
274
|
+
h6 {
|
275
|
+
font-size:100%;
|
276
|
+
font-weight:normal;
|
277
|
+
}
|
278
|
+
q:before,
|
279
|
+
q:after {
|
280
|
+
content:'';
|
281
|
+
}
|
282
|
+
abbr,
|
283
|
+
acronym {
|
284
|
+
border:0;
|
285
|
+
font-variant:normal;
|
286
|
+
}
|
287
|
+
/* to preserve line-height and selector appearance */
|
288
|
+
sup {
|
289
|
+
vertical-align:text-top;
|
290
|
+
}
|
291
|
+
sub {
|
292
|
+
vertical-align:text-bottom;
|
293
|
+
}
|
294
|
+
input,
|
295
|
+
textarea,
|
296
|
+
select {
|
297
|
+
font-family:inherit;
|
298
|
+
font-size:inherit;
|
299
|
+
font-weight:inherit;
|
300
|
+
}
|
301
|
+
/*to enable resizing for IE*/
|
302
|
+
input,
|
303
|
+
textarea,
|
304
|
+
select {
|
305
|
+
*font-size:100%;
|
306
|
+
}
|
307
|
+
/*because legend doesn't inherit in IE */
|
308
|
+
legend {
|
309
|
+
color:#000;
|
310
|
+
}
|
311
|
+
|
312
|
+
|
313
|
+
body {
|
314
|
+
font-family: Veranda, helvetica, sans-serif;
|
315
|
+
background-color: #4682B4;
|
316
|
+
}
|
317
|
+
|
318
|
+
#potentialmembers {
|
319
|
+
margin: auto;
|
320
|
+
width: 940px;
|
321
|
+
padding: 10px;
|
322
|
+
background-color: white;
|
323
|
+
border: 1px solid #666;
|
324
|
+
}
|
325
|
+
|
326
|
+
h1 {
|
327
|
+
font-size: 26px;
|
328
|
+
font-weight: bold;
|
329
|
+
color: #444;
|
330
|
+
}
|
331
|
+
|
332
|
+
h2 {
|
333
|
+
font-size: 15px;
|
334
|
+
font-weight: bold;
|
335
|
+
}
|
336
|
+
|
337
|
+
span.locale {
|
338
|
+
display: inline-block;
|
339
|
+
width: 50px;
|
340
|
+
}
|
341
|
+
|
342
|
+
form.translate {
|
343
|
+
margin: 5px 0;
|
344
|
+
}
|
345
|
+
|
346
|
+
form.translate input[type='text'] {
|
347
|
+
width: 720px;
|
348
|
+
}
|
349
|
+
|
350
|
+
ul, ul li {
|
351
|
+
display: block;
|
352
|
+
list-style-type: none;
|
353
|
+
margin: 0;
|
354
|
+
padding: 0;
|
355
|
+
}
|
356
|
+
|
357
|
+
ul li { display: inline }
|
358
|
+
|
359
|
+
ul { text-align: right; position: relative; top: -37px; }
|
360
|
+
|
361
|
+
body { padding-top: 20px; padding-bottom: 20px}
|
362
|
+
|
363
|
+
#header {
|
364
|
+
margin-bottom: 10px;
|
365
|
+
}
|
366
|
+
|
367
|
+
#groups {
|
368
|
+
font-size: 13px;
|
369
|
+
font-weight: bold;
|
370
|
+
}
|
371
|
+
|
372
|
+
#footer {
|
373
|
+
width: 960px;
|
374
|
+
margin: auto;
|
375
|
+
}
|
376
|
+
|
377
|
+
#shameless {
|
378
|
+
float: right;
|
379
|
+
color: white;
|
380
|
+
margin-top: 21px;
|
381
|
+
font-size: 10px;
|
382
|
+
}
|
383
|
+
|
384
|
+
.translation {
|
385
|
+
border-top: 1px dashed #999;
|
386
|
+
padding: 10px;
|
387
|
+
}
|
388
|
+
|
389
|
+
.translation a {
|
390
|
+
color: #666;
|
391
|
+
}
|
392
|
+
|
393
|
+
.translation a:hover {
|
394
|
+
color: #000;
|
395
|
+
}
|
396
|
+
|
397
|
+
.button {
|
398
|
+
font-weight: normal;
|
399
|
+
display: inline-block;
|
400
|
+
cursor: pointer;
|
401
|
+
padding: 0px 8px;
|
402
|
+
line-height: 20px;
|
403
|
+
height: 20px;
|
404
|
+
background: #eee;
|
405
|
+
border: 1px solid #999;
|
406
|
+
text-decoration: none;
|
407
|
+
color: #000;
|
408
|
+
font-size: 12px;
|
409
|
+
border-radius: 2px;
|
410
|
+
}
|
411
|
+
|
412
|
+
.button.small {
|
413
|
+
padding: 0px 4px;
|
414
|
+
line-height: 16px;
|
415
|
+
height: 16px;
|
416
|
+
font-size: 10px;
|
417
|
+
}
|
418
|
+
|
419
|
+
.button:hover {
|
420
|
+
background: #ddd;
|
421
|
+
border-color: #888;
|
422
|
+
}
|
423
|
+
|
424
|
+
input.button {
|
425
|
+
height: 22px;
|
426
|
+
}
|
427
|
+
|
428
|
+
.button.warning {
|
429
|
+
color: red;
|
430
|
+
}
|
431
|
+
textarea {
|
432
|
+
width: 700px;
|
433
|
+
height: 1.2em;
|
434
|
+
}
|
435
|
+
</style>
|
436
|
+
</head>
|
437
|
+
<body>
|
438
|
+
<%= yield %>
|
439
|
+
</body>
|
440
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
%h1 Potentialmember Import
|
2
|
+
%p A CSV or Excel file can be used to import records. The first row should be the column name. The following columns are allowed.
|
3
|
+
%ul
|
4
|
+
- Potentialmember.columns.each do |column|
|
5
|
+
- if column.name.in? ["id", *Potentialmember.accessible_attributes]
|
6
|
+
%li
|
7
|
+
%strong= column.name
|
8
|
+
\-
|
9
|
+
\#{column.type.to_s.titleize} type
|
10
|
+
%p
|
11
|
+
If an
|
12
|
+
%strong id
|
13
|
+
is supplied it will update the matching record instead of creating a new one.
|
14
|
+
= form_for @potentialmember_import do |f|
|
15
|
+
- if @potentialmember_import.errors.any?
|
16
|
+
#error_explanation
|
17
|
+
%h2
|
18
|
+
= pluralize(@potentialmember_import.errors.count, "error")
|
19
|
+
prohibited this import from completing:
|
20
|
+
%ul
|
21
|
+
- @potentialmember_import.errors.full_messages.each do |msg|
|
22
|
+
%li= msg
|
23
|
+
.field
|
24
|
+
= f.file_field :file
|
25
|
+
.buttons= f.submit "Import"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
%h1 Potentialmembers
|
2
|
+
%p
|
3
|
+
Download:
|
4
|
+
\#{link_to "CSV", potentialmembers_path(format: "csv")} |
|
5
|
+
\#{link_to "Excel", potentialmembers_path(format: "xls")}
|
6
|
+
%table#potentialmembers
|
7
|
+
%tr
|
8
|
+
%th Name
|
9
|
+
%th Company
|
10
|
+
%th Phone
|
11
|
+
- @potentialmembers.each do |potentialmember|
|
12
|
+
%tr
|
13
|
+
%td= potentialmember.name
|
14
|
+
%td= potentialmember.company
|
15
|
+
%td= potentialmember.phone
|
16
|
+
%p= link_to "Import potentialmembers", new_potentialmember_import_path
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
3
|
+
xmlns:o="urn:schemas-microsoft-com:office:office"
|
4
|
+
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
5
|
+
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
|
6
|
+
xmlns:html="http://www.w3.org/TR/REC-html40">
|
7
|
+
<Worksheet ss:Name="Sheet1">
|
8
|
+
<Table>
|
9
|
+
<Row>
|
10
|
+
<Cell><Data ss:Type="String">ID</Data></Cell>
|
11
|
+
<Cell><Data ss:Type="String">Name</Data></Cell>
|
12
|
+
<Cell><Data ss:Type="String">Company</Data></Cell>
|
13
|
+
<Cell><Data ss:Type="String">Phone</Data></Cell>
|
14
|
+
</Row>
|
15
|
+
<% @potentialmembers.each do |potentialmember| %>
|
16
|
+
<Row>
|
17
|
+
<Cell><Data ss:Type="Number"><%= potentialmember.id %></Data></Cell>
|
18
|
+
<Cell><Data ss:Type="String"><%= potentialmember.name %></Data></Cell>
|
19
|
+
<Cell><Data ss:Type="String"><%= potentialmember.company %></Data></Cell>
|
20
|
+
<Cell><Data ss:Type="Number"><%= potentialmember.phone %></Data></Cell>
|
21
|
+
</Row>
|
22
|
+
<% end %>
|
23
|
+
</Table>
|
24
|
+
</Worksheet>
|
25
|
+
</Workbook>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# When Rails >= 3.1
|
2
|
+
if defined?(Memberfier::Engine)
|
3
|
+
Memberfier::Engine.routes.draw do
|
4
|
+
resources :potentialmembers
|
5
|
+
resources :potentialmember_imports
|
6
|
+
root to: 'potentialmembers#index'
|
7
|
+
get "/memberfier" =>"memberfier/potentialmembers#index"
|
8
|
+
end
|
9
|
+
else
|
10
|
+
Rails.application.routes.draw do
|
11
|
+
resources :potentialmembers, :to => "Memberfier::Potentialmembers"
|
12
|
+
resources :potentialmember_imports, :to => "Memberfier::PotentialmemberImports"
|
13
|
+
root to: 'potentialmembers#index'
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/memberfier.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'memberfier/engine' if defined?(Rails) && Rails::VERSION::STRING.to_f >= 3.1
|
2
|
+
|
3
|
+
module Memberfier
|
4
|
+
class << self
|
5
|
+
attr_accessor :auth_handler, :current_store, :framework_keys
|
6
|
+
attr_reader :simple_backend
|
7
|
+
attr_writer :layout_name
|
8
|
+
end
|
9
|
+
|
10
|
+
@framework_keys = ["date.formats.default", "date.formats.short", "date.formats.long",
|
11
|
+
"time.formats.default", "time.formats.short", "time.formats.long", "time.am", "time.pm",
|
12
|
+
"support.array.words_connector", "support.array.two_words_connector", "support.array.last_word_connector",
|
13
|
+
"errors.format", "errors.messages.inclusion", "errors.messages.exclusion", "errors.messages.invalid",
|
14
|
+
"errors.messages.confirmation", "errors.messages.accepted", "errors.messages.empty",
|
15
|
+
"errors.messages.blank", "errors.messages.too_long", "errors.messages.too_short", "errors.messages.wrong_length",
|
16
|
+
"errors.messages.not_a_number", "errors.messages.not_an_integer", "errors.messages.greater_than",
|
17
|
+
"errors.messages.greater_than_or_equal_to", "errors.messages.equal_to", "errors.messages.less_than",
|
18
|
+
"errors.messages.less_than_or_equal_to", "errors.messages.odd", "errors.messages.even", "errors.required", "errors.blank",
|
19
|
+
"number.format.separator", "number.format.delimiter", "number.currency.format.format", "number.currency.format.unit",
|
20
|
+
"number.currency.format.separator", "number.currency.format.delimiter", "number.percentage.format.delimiter",
|
21
|
+
"number.precision.format.delimiter", "number.human.format.delimiter", "number.human.storage_units.format",
|
22
|
+
"number.human.storage_units.units.byte.one", "number.human.storage_units.units.byte.other",
|
23
|
+
"number.human.storage_units.units.kb", "number.human.storage_units.units.mb", "number.human.storage_units.units.gb",
|
24
|
+
"number.human.storage_units.units.tb", "number.human.decimal_units.format", "number.human.decimal_units.units.unit",
|
25
|
+
"number.human.decimal_units.units.thousand", "number.human.decimal_units.units.million",
|
26
|
+
"number.human.decimal_units.units.billion", "number.human.decimal_units.units.trillion",
|
27
|
+
"number.human.decimal_units.units.quadrillion", "datetime.distance_in_words.half_a_minute",
|
28
|
+
"datetime.distance_in_words.less_than_x_seconds.one", "datetime.distance_in_words.less_than_x_seconds.other",
|
29
|
+
"datetime.distance_in_words.x_seconds.one", "datetime.distance_in_words.x_seconds.other",
|
30
|
+
"datetime.distance_in_words.less_than_x_minutes.one", "datetime.distance_in_words.less_than_x_minutes.other",
|
31
|
+
"datetime.distance_in_words.x_minutes.one", "datetime.distance_in_words.x_minutes.other",
|
32
|
+
"datetime.distance_in_words.about_x_hours.one", "datetime.distance_in_words.about_x_hours.other",
|
33
|
+
"datetime.distance_in_words.x_days.one", "datetime.distance_in_words.x_days.other",
|
34
|
+
"datetime.distance_in_words.about_x_months.one", "datetime.distance_in_words.about_x_months.other",
|
35
|
+
"datetime.distance_in_words.x_months.one", "datetime.distance_in_words.x_months.other",
|
36
|
+
"datetime.distance_in_words.about_x_years.one", "datetime.distance_in_words.about_x_years.other",
|
37
|
+
"datetime.distance_in_words.over_x_years.one", "datetime.distance_in_words.over_x_years.other",
|
38
|
+
"datetime.distance_in_words.almost_x_years.one", "datetime.distance_in_words.almost_x_years.other",
|
39
|
+
"datetime.prompts.year", "datetime.prompts.month", "datetime.prompts.day", "datetime.prompts.hour",
|
40
|
+
"datetime.prompts.minute", "datetime.prompts.second", "helpers.select.prompt", "helpers.submit.create",
|
41
|
+
"helpers.submit.update", "helpers.submit.submit"]
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
def self.locales
|
46
|
+
@simple_backend.available_locales
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def self.layout_name
|
51
|
+
@layout_name || "memberfier"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Memberfier
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
|
4
|
+
isolate_namespace Memberfier
|
5
|
+
config.generators do |g|
|
6
|
+
g.orm :mongoid
|
7
|
+
g.test_framework :rspec, :views => false, :fixture => true
|
8
|
+
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class PotentialmemberImporter < ActiveImporter::Base
|
2
|
+
imports Potentialmember
|
3
|
+
column 'First name', :first_name
|
4
|
+
column 'Last name', :last_name
|
5
|
+
column 'Company name ', :company_name
|
6
|
+
column 'Company address ', :company_address
|
7
|
+
column 'Company phone ', :company_phone
|
8
|
+
column 'Company work', :company_work_tags
|
9
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: memberfier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Caner Cakmak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Memberfier imports spreadsheet files of potential members of a site and
|
14
|
+
emails them recurringly until they do some action in the site
|
15
|
+
email: canercak@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- MIT-LICENSE
|
21
|
+
- README.rdoc
|
22
|
+
- Rakefile
|
23
|
+
- app/assets/javascripts/application.js
|
24
|
+
- app/assets/javascripts/potentialmember_imports.js.coffee
|
25
|
+
- app/assets/javascripts/potentialmembers.js.coffee
|
26
|
+
- app/assets/stylesheets/application.css
|
27
|
+
- app/assets/stylesheets/layout.css.scss
|
28
|
+
- app/assets/stylesheets/potentialmember_imports.css.scss
|
29
|
+
- app/assets/stylesheets/potentialmembers.css.scss
|
30
|
+
- app/backends/memberfier/mongo_store.rb
|
31
|
+
- app/backends/memberfier/redis_store.rb
|
32
|
+
- app/controllers/memberfier/potentialmember_imports_controller.rb
|
33
|
+
- app/controllers/memberfier/potentialmembers_controller.rb
|
34
|
+
- app/mailers/memberfier/potentialmember_mailer.rb
|
35
|
+
- app/models/potentialmember.rb
|
36
|
+
- app/models/potentialmember_import.rb
|
37
|
+
- app/views/layouts/memberfier.html.erb
|
38
|
+
- app/views/memberfier/potentialmember_imports/new.html.haml
|
39
|
+
- app/views/memberfier/potentialmembers/index.html.haml
|
40
|
+
- app/views/memberfier/potentialmembers/index.xls.erb
|
41
|
+
- config/routes.rb
|
42
|
+
- lib/memberfier.rb
|
43
|
+
- lib/memberfier/engine.rb
|
44
|
+
- lib/memberfier/potentialmember_importer.rb
|
45
|
+
- lib/memberfier/version.rb
|
46
|
+
- lib/tasks/memberfier_tasks.rake
|
47
|
+
homepage: https://github.com/canercak/memberfier
|
48
|
+
licenses:
|
49
|
+
- MIT
|
50
|
+
metadata: {}
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 2.2.2
|
68
|
+
signing_key:
|
69
|
+
specification_version: 4
|
70
|
+
summary: Memberfier is an action emailer that runs with mongoi and resque scheduler.
|
71
|
+
test_files: []
|