memberfier 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ # Place all the behaviors and hooks related to the matching controller here.
2
+ # All this logic will automatically be available in application.js.
3
+ # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
@@ -0,0 +1,3 @@
1
+ # Place all the behaviors and hooks related to the matching controller here.
2
+ # All this logic will automatically be available in application.js.
3
+ # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
@@ -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,9 @@
1
+
2
+ table td, table th {
3
+ padding-right: 20px;
4
+ padding-bottom: 5px;
5
+ text-align: left;
6
+ &:last-child {
7
+ text-align: right;
8
+ }
9
+ }
@@ -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
@@ -0,0 +1,3 @@
1
+ module Memberfier
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :memberfier do
3
+ # # Task goes here
4
+ # 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: []