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 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: []