memberfier 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|