├── README.md
└── app
├── controllers
├── book_controller.rb
└── vehicle_listings_controller.rb
├── decorators
├── automatic_delegation.rb
├── lead_decorator.rb
└── offer_decorator.rb
├── facades
├── dashboard_facade.rb
└── documents_facade.rb
├── forms
└── investor_form.rb
├── helpers
├── lead_helper.rb
└── partners_helper.rb
├── models
├── investor.rb
├── investor_fat.rb
├── loan.rb
├── report.rb
└── vehicle_listing.rb
├── performers
├── investment_data_performer.rb
├── investment_performer.rb
├── investor_performer.rb
└── loan_data_performer.rb
├── policies
├── cart_policy.rb
├── post_policy.rb
└── store_front_policy.rb
├── presenters
├── dividend_year_dispatcher.rb
├── monthly_distribution_presenter.rb
├── quarterly_distribution_presenter.rb
└── yearly_distribution_presenter.rb
├── serializers
├── chat_attachment_serializer.rb
├── chat_message_serializer.rb
└── review_serializer.rb
├── services
├── calculate_currency.rb
└── connect_listings_to_seller.rb
└── workers
├── check_ad_worker.rb
├── move_to_top_worker.rb
└── partner_deactivate_worker.rb
/README.md:
--------------------------------------------------------------------------------
1 | # Rails Structure Sample
2 |
3 | 
4 |
5 | ## Description
6 |
7 | Ruby on Rails is one of our favourite frameworks for web applications development. We love it because of agile and interesting development process, high performance and, of course, ruby programming language.
8 | Below you can find an example of a project`s structure, and its main components
9 |
10 | ## File structure
11 |
12 | ```
13 | project_name
14 | ├── app
15 | | ├── assets => images, fonts, stylesheets, js
16 | | ├── controllers
17 | | ├── decorators
18 | | ├── helpers
19 | | ├── mailers
20 | | ├── models
21 | | ├── performers
22 | | ├── policies
23 | | ├── presenters
24 | | ├── serializers
25 | | ├── services
26 | | ├── uploaders
27 | | ├── views
28 | | └── workers => workers for running processes in the background
29 | ├── bin => contains script that starts, update, deploy or run your application.
30 | ├── config => configure your application's routes, database, and more
31 | ├── db => contains your current database schema and migrations
32 | ├── lib => extended modules for your application
33 | ├── log => app log files
34 | ├── public => The only folder seen by the world as-is. Contains static files and compiled assets.
35 | ├── spec => Unit tests, features, and other test apparatus.
36 | ├── tmp => Temporary files (like cache and pid files).
37 | └── vendor => third-party code
38 | ```
39 |
40 | ## Controllers
41 |
42 | Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output.
43 |
44 | There are a lot of opinions on what a controller should do. A common ground of the responsibilities a controller should have include the following:
45 |
46 | **Authentication and authorization** — checking whether the entity (oftentimes, a user) behind the request is who it says it is and whether it is allowed to access the resource or perform the action.
47 |
48 | **Data fetching** — it should call the logic for finding the right data based on the parameters that came with the request. In the perfect world, it should be a call to one method that does all the work. The controller should not do the heavy work, it should delegate it further.
49 |
50 | **Template rendering** — finally, it should return the right response by rendering the result with the proper format (HTML, JSON, etc.). Or, it should redirect to some other path or URL.
51 |
52 |
53 | ```ruby
54 | class BooksController < ApplicationController
55 |
56 | def show
57 | @book = Book.find(params[:id])
58 | end
59 |
60 | def create
61 | @book = Book.new(book_params)
62 |
63 | if @book.save
64 | redirect_to action: 'list'
65 | else
66 | @subjects = Subject.all
67 | render action: 'new'
68 | end
69 | end
70 |
71 | private
72 |
73 | def book_params
74 | params.require(:books).permit(:title, :price, :subject_id, :description)
75 | end
76 |
77 | end
78 | ```
79 |
80 | ```ruby
81 | class ListingsController < ApiController
82 |
83 | def index
84 | listings = Current.user.listings
85 |
86 | render json: ListingSerializer.new(listings).serializable_hash
87 | end
88 |
89 | def create
90 | authorize Listing
91 |
92 | command = Listings::Create.call(Current.user, params[:listing])
93 |
94 | if command.success?
95 | render json: ListingSerializer.new(command.result).serializable_hash
96 | else
97 | render json: command.errors
98 | end
99 | end
100 |
101 | def destroy
102 | listing = Current.user.listings.find(params[:id])
103 |
104 | authorize listing
105 |
106 | command = Listings::Delete.call(listing)
107 |
108 | if command.success?
109 | head :ok
110 | else
111 | render json: command.errors
112 | end
113 | end
114 |
115 | end
116 | ```
117 |
118 | [Examples](app/controllers)
119 |
120 | [Documentation](https://edgeguides.rubyonrails.org/action_controller_overview.html)
121 |
122 | ## Decorators
123 |
124 | Decorators adds an object-oriented layer of presentation logic to your Rails application
125 |
126 | ```ruby
127 | class ListingDecorator < Draper::Decorator
128 |
129 | delegate_all
130 |
131 | def creation_date
132 | object.created_at.strftime('%d/%m/%Y')
133 | end
134 |
135 | end
136 | ```
137 |
138 | [Examples](app/decorators)
139 |
140 | [Documentation](https://github.com/drapergem/draper)
141 |
142 | ## Helpers
143 |
144 | Helpers in Rails are used to extract complex logic out of the view so that you can organize your code better.
145 |
146 | ```ruby
147 | module PartnersHelper
148 |
149 | def partner_activation(partner)
150 | partner.active? ? t('partners.extend_activation') : t('partners.activation')
151 | end
152 |
153 | def partner_edit_page_title(redirect_url)
154 | redirect_url.present? ? I18n.t('partners.upgrade_my_profile') : I18n.t('partners.edit_my_profile')
155 | end
156 |
157 | end
158 | ```
159 |
160 | [Examples](app/helpers)
161 |
162 | [Documentation](https://api.rubyonrails.org/classes/ActionController/Helpers.html)
163 |
164 | ## Models
165 |
166 | Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database.
167 |
168 | ```ruby
169 | # == Schema Information
170 | #
171 | # Table name: colors
172 | #
173 | # id :integer not null, primary key
174 | # created_at :datetime not null
175 | # updated_at :datetime not null
176 | #
177 |
178 | class Color < ApplicationRecord
179 |
180 | # == Constants ============================================================
181 |
182 | # == Attributes ===========================================================
183 | attribute :name
184 |
185 | # == Extensions ===========================================================
186 | translates :name, fallbacks_for_empty_translations: true
187 | globalize_accessors
188 |
189 | # == Relationships ========================================================
190 | has_many :vehicle_listings, dependent: :nullify
191 |
192 | # == Validations ==========================================================
193 | validates(*Color.globalize_attribute_names, presence: true)
194 |
195 | # == Scopes ===============================================================
196 |
197 | # == Callbacks ============================================================
198 |
199 | # == Class Methods ========================================================
200 |
201 | # == Instance Methods =====================================================
202 |
203 | end
204 | ```
205 |
206 | [Examples](app/models)
207 |
208 | [Documentation](https://guides.rubyonrails.org/active_model_basics.html)
209 |
210 | [Ruby on Rails Model Patterns and Anti-patterns](https://blog.appsignal.com/2020/11/18/rails-model-patterns-and-anti-patterns.html)
211 |
212 | ## Form Objects
213 |
214 | Use [form object](https://medium.com/selleo/essential-rubyonrails-patterns-form-objects-b199aada6ec9) pattern to make your models more lightweight.
215 | Single responsibility principle helps us make the best design decisions about what the class should be responsible for. Your model is a database table, it represents a single entry in the code, so there is no reason to worry and user action.
216 |
217 | Form Objects are responsible for the presentation of the form in your application. Each form field is an attribute in a class and can be checked through validation, which will give us clean data and they will go further along the chain. This can be your model, defining a table in the database or, for example, a search form.
218 |
219 | Here are an examples of [fat](app/models/investor_fat.rb) and [refactored](app/models/investor.rb) models.
220 |
221 | Usage Form Object in controller:
222 |
223 | ```ruby
224 |
225 | class Admin::InvestorsController < CommonAdminController
226 |
227 | include Sorting
228 |
229 | load_resource :investor, except: %i[index new create]
230 |
231 | def index
232 | @investors = Investor.real.includes(:investor_group, :address)
233 | end
234 |
235 | def new
236 | @investor_form = InvestorForm.new
237 | end
238 |
239 | def edit
240 | @investor_form = InvestorForm.new(@investor)
241 | end
242 |
243 | def create
244 | params[:investor][:password] = generate_devise_password
245 | @investor_form = InvestorForm.new
246 |
247 | if @investor_form.submit(params)
248 | flash[:success] = 'Investor was successfully created.'
249 | redirect_to admin_investor_path(@investor_form.id)
250 | else
251 | render :new
252 | end
253 | end
254 |
255 | def update
256 | @investor_form = InvestorForm.new(@investor)
257 |
258 | if @investor_form.submit(params)
259 | flash[:success] = 'Investor was successfully updated.'
260 | redirect_to admin_investor_path(@investor)
261 | else
262 | render :edit
263 | end
264 | end
265 |
266 | ```
267 |
268 | [Examples](app/forms/investor_form.rb)
269 |
270 | [Documentation](https://thoughtbot.com/blog/activemodel-form-objects)
271 |
272 | ## Performers
273 |
274 | The performer pattern creates seperation for the methods in your model that are view related. The performers are modules and are included into the corresponding model.
275 |
276 | ```ruby
277 | module InvestmentDataPerformer
278 |
279 | def self.included(base)
280 | base.extend ClassMethods
281 | end
282 |
283 | module ClassMethods
284 |
285 | end
286 |
287 | def non_final_profit
288 | return nil unless investment.non_final_profit
289 | investment.non_final_profit * holding / 100
290 | end
291 |
292 | def final_distribution
293 | return unless investment.distribution.present?
294 | investment.distribution * holding / 100
295 | end
296 |
297 | end
298 | ```
299 |
300 | [Examples](app/performers)
301 |
302 | [Documentation](https://github.com/jwipeout/performer-pattern)
303 |
304 | ## Serializers
305 |
306 | In Ruby serialization does nothing else than just converting the given data structure into its valid JSON.
307 |
308 | ```ruby
309 | class AttachmentSerializer
310 |
311 | include JSONAPI::Serializer
312 |
313 | attributes :id, :name, :description
314 |
315 | attribute :url, &:attachment_url
316 |
317 | attribute :type do |obj|
318 | obj.attachment.mime_type
319 | end
320 |
321 | attribute :name do |obj|
322 | obj.attachment['filename']
323 | end
324 |
325 | end
326 | ```
327 |
328 | [Examples](app/serializers)
329 |
330 | [Documentation](https://github.com/jsonapi-serializer/jsonapi-serializer)
331 |
332 | ## Services
333 |
334 | Service Object can be a class or module in Ruby that performs an action. It can help take out logic from other areas of the MVC files.
335 |
336 | ```ruby
337 | class Subscribe < BaseService
338 |
339 | attr_reader :email
340 |
341 | def initialize(email)
342 | @email = email
343 | end
344 |
345 | def call
346 | list.members.create(body: { email_address: email, status: 'subscribed' })
347 | end
348 |
349 | private
350 |
351 | def list
352 | gibbon_request.lists(ENV['MAILCHIMP_LIST_ID'])
353 | end
354 |
355 | def gibbon_request
356 | Gibbon::Request.new(api_key: ENV['MAILCHIMP_API_KEY'])
357 | end
358 |
359 | end
360 | ```
361 |
362 | [Examples](app/services)
363 |
364 | [Documentation](https://github.com/nebulab/simple_command)
365 |
366 | ## Presenters
367 |
368 | Presenters give you an object oriented way to approach view helpers.
369 |
370 | ```ruby
371 | class QuarterlyDistributionPresenter < BaseDividendYearPresenter
372 |
373 | QUARTERS = %w[Q1 Q2 Q3 Q4].freeze
374 |
375 | def dividends_year
376 | QUARTERS.map do |quarter|
377 | value_decorator(quarter)
378 | end
379 | end
380 |
381 | def select_dividend_by(dividend_year)
382 | CalendarQuarter.from_date(dividend_year).quarter
383 | end
384 |
385 | end
386 | ```
387 |
388 | [Examples](app/presenters)
389 |
390 | [Documentation](http://nithinbekal.com/posts/rails-presenters/)
391 |
392 | ## Policies
393 |
394 | Pundit provides a set of helpers which guide in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scalable authorization system.
395 |
396 | ```ruby
397 | class ProductPolicy < ApplicationPolicy
398 |
399 | def create?
400 | user_active?
401 | end
402 |
403 | def show?
404 | user_active? && record.active?
405 | end
406 |
407 | def destroy?
408 | show?
409 | end
410 |
411 | end
412 |
413 | ```
414 |
415 | [Examples](app/policies)
416 |
417 | [Documentation](https://github.com/varvet/pundit)
418 |
419 | ## Facades
420 |
421 | Facades provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
422 | One of the responsibilities that is being thrown at the controller is to prepare the data so it can be presented to user. To remove the responsibility of preparing the data for the view we're using Facade pattern.
423 |
424 | ```ruby
425 | class Admin
426 |
427 | class DocumentsFacade
428 |
429 | attr_reader :loan, :investor
430 |
431 | def initialize(loan_id, investor_id)
432 | @loan = Loan.find(loan_id)
433 | @investor = Investor.find(investor_id)
434 | end
435 |
436 | def lender?
437 | investor.present?
438 | end
439 |
440 | def documents
441 | Document.where(investment_id: loan.id, investor_id: investor.id)
442 | end
443 |
444 | def lenders
445 | loan.lenders.order(:name).uniq.collect { |l| [l.name, l.id] }
446 | end
447 |
448 | end
449 |
450 | end
451 | ```
452 |
453 | Now we can reduce our controller down to:
454 |
455 | ```ruby
456 | class DocumentsController < LoansController
457 |
458 | def index
459 | add_breadcrumb 'Documents'
460 | @documents_data = Admin::DocumentsFacade.new(params[:loan_id], params[:investor_id])
461 | end
462 |
463 | end
464 | ```
465 |
466 | And inside our view we will call for our facade:
467 |
468 | ```slim
469 | = render ‘documents/form’, document: @document_data.new_document
470 | ```
471 |
472 | [Examples](app/facades)
473 |
474 | [Documentation](https://medium.com/kkempin/facade-design-pattern-in-ruby-on-rails-710aa88326f)
475 |
476 | ## Workers
477 |
478 | At Codica we use sidekiq as a full-featured background processing framework for Ruby. It aims to be simple to integrate with any modern Rails application and much higher performance than other existing solutions.
479 |
480 | ```ruby
481 | class PartnerAlertWorker
482 |
483 | include Sidekiq::Worker
484 |
485 | def perform(id)
486 | @partner = Partner.find(id)
487 | PartnerMailer.alert(@partner).deliver_now
488 | end
489 |
490 | end
491 | ```
492 |
493 | [Examples](app/workers)
494 |
495 | [Documentation](https://github.com/mperham/sidekiq/wiki)
496 |
497 | ## Spec
498 |
499 | Spec folder include the test suites, the application logic tests.
500 |
501 | ```ruby
502 | require 'rails_helper'
503 |
504 | RSpec.feature 'Static page', type: :feature do
505 | let(:listing) { create :listing }
506 |
507 | scenario 'unsuccess payment' do
508 | visit payment_info_error_path(listing: listing.id)
509 | expect(page).to have_content I18n.t('unsuccess_payment_header')
510 | end
511 |
512 | scenario 'success payment' do
513 | visit payment_info_success_path(promotion: 'on_homepage', price: '30')
514 | expect(page).to have_content I18n.t('success_payment_header')
515 | end
516 |
517 | end
518 | ```
519 |
520 | [Examples](https://github.com/codica2/rspec-samples)
521 |
522 | [Documentation](https://relishapp.com/rspec/)
523 |
524 | ## License
525 | rails-app-best-practice is Copyright © 2015-2019 Codica. It is released under the [MIT License](https://opensource.org/licenses/MIT).
526 |
527 | ## About Codica
528 |
529 | [](https://www.codica.com)
530 |
531 | We love open source software! See [our other projects](https://github.com/codica2) or [hire us](https://www.codica.com/) to design, develop, and grow your product.
532 |
--------------------------------------------------------------------------------
/app/controllers/book_controller.rb:
--------------------------------------------------------------------------------
1 | class BooksController < ApplicationController
2 |
3 | def show
4 | @book = Book.find(params[:id])
5 | end
6 |
7 | def new
8 | @book = Book.new
9 | @subjects = Subject.all
10 | end
11 |
12 | def create
13 | @book = Book.new(book_params)
14 |
15 | if @book.save
16 | redirect_to action: 'list'
17 | else
18 | @subjects = Subject.all
19 | render action: 'new'
20 | end
21 | end
22 |
23 | def edit
24 | @book = Book.find(params[:id])
25 | @subjects = Subject.all
26 | end
27 |
28 | def update
29 | @book = Book.find(params[:id])
30 |
31 | if @book.update_attributes(book_params)
32 | redirect_to action: 'show', id: @book
33 | else
34 | @subjects = Subject.all
35 | render action: 'edit'
36 | end
37 | end
38 |
39 | private
40 |
41 | def book_params
42 | params.require(:books).permit(:title, :price, :subject_id, :description)
43 | end
44 |
45 | end
46 |
--------------------------------------------------------------------------------
/app/controllers/vehicle_listings_controller.rb:
--------------------------------------------------------------------------------
1 | module Dashboards
2 |
3 | class VehicleListingsController < BaseController
4 |
5 | layout 'dashboard'
6 |
7 | def index
8 | authorize! :manage, VehicleListing
9 |
10 | add_breadcrumb I18n.t('shared.footer.home'), :root_path
11 | add_breadcrumb I18n.t('ads.ad.my_profile'), dashboard_path(locale: I18n.locale)
12 |
13 | @price_range = Calculate::PriceRange.call(VehicleListing.prices, @current_currency)
14 | @page = Page::Listing::Vehicle::Search.new(listings: current_seller.vehicle_listings, params: params)
15 |
16 | current_seller.visit!(:my_vehicles)
17 |
18 | respond_to do |format|
19 | format.html
20 | format.js
21 | end
22 | end
23 |
24 | private
25 |
26 | def listings_params
27 | return {} if params[:listing].blank?
28 |
29 | params.require(:listing).permit(:city_id, :body_id, :model_id, :car_type, :brand_id, :start_date)
30 | end
31 |
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/app/decorators/automatic_delegation.rb:
--------------------------------------------------------------------------------
1 | module AutomaticDelegation
2 |
3 | extend ActiveSupport::Concern
4 |
5 | # Delegates missing instance methods to the source object. Note: This will delegate `super`
6 | # method calls to `object` as well. Calling `super` will first try to call the method on
7 | # the parent decorator class. If no method exists on the parent class, it will then try
8 | # to call the method on the `object`.
9 | def method_missing(method, *args, &block)
10 | return super unless delegatable?(method)
11 |
12 | object.send(method, *args, &block)
13 | end
14 |
15 | # Checks if the decorator responds to an instance method, or is able to
16 | # proxy it to the source object.
17 | def respond_to_missing?(method, include_private = false)
18 | super || delegatable?(method)
19 | end
20 |
21 | # @private
22 | def delegatable?(method)
23 | return if private_methods(false).include?(method)
24 |
25 | object.respond_to?(method)
26 | end
27 |
28 | module ClassMethods
29 |
30 | # Proxies missing class methods to the source class.
31 | def method_missing(method, *args, &block)
32 | return super unless delegatable?(method)
33 |
34 | object_class.send(method, *args, &block)
35 | end
36 |
37 | # Checks if the decorator responds to a class method, or is able to proxy
38 | # it to the source class.
39 | def respond_to_missing?(method, include_private = false)
40 | super || delegatable?(method)
41 | end
42 |
43 | # @private
44 | def delegatable?(method)
45 | object_class? && object_class.respond_to?(method)
46 | end
47 |
48 | # @private
49 | # Avoids reloading the model class when ActiveSupport clears autoloaded
50 | # dependencies in development mode.
51 | def before_remove_const; end
52 |
53 | end
54 |
55 | included do
56 | private :delegatable?
57 | private_class_method :delegatable?
58 | end
59 |
60 | end
61 |
--------------------------------------------------------------------------------
/app/decorators/lead_decorator.rb:
--------------------------------------------------------------------------------
1 | class LeadDecorator < Draper::Decorator
2 |
3 | include ActionView::Helpers::NumberHelper
4 |
5 | delegate_all
6 |
7 | def offered_price
8 | number_to_currency(object.offered_price, unit: ExchangeRate.base_currency, precision: 0, format: price_format, delimiter: price_divider)
9 | end
10 |
11 | private
12 |
13 | def price_format
14 | I18n.default_locale == :fr ? '%n %u' : '%u %n'
15 | end
16 |
17 | def price_divider
18 | I18n.default_locale == :en ? ',' : ' '
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/app/decorators/offer_decorator.rb:
--------------------------------------------------------------------------------
1 | class OfferDecorator < Draper::Decorator
2 |
3 | include ActionView::Helpers::NumberHelper
4 |
5 | delegate_all
6 |
7 | def creation_date
8 | object.created_at.strftime('%d/%m/%Y')
9 | end
10 |
11 | def title
12 | object.target.decorate.title
13 | end
14 |
15 | def recipients_count
16 | recipients = object.leads.count
17 | result = object.with_sms ? "#{recipients} SMS" : nil
18 | [result, "#{recipients} EMAILS"].reject(&:blank?).join(' / ')
19 | end
20 |
21 | def price
22 | number_to_currency(object.price, unit: '$')
23 | end
24 |
25 | def emails_delivery_status
26 | "#{object.emails_delivered} / #{object.lead_offers.count}"
27 | end
28 |
29 | def sms_delivery_status
30 | return unless object.with_sms?
31 |
32 | "#{object.sms_delivered} / #{object.lead_offers.count}"
33 | end
34 |
35 | end
36 |
--------------------------------------------------------------------------------
/app/facades/dashboard_facade.rb:
--------------------------------------------------------------------------------
1 | class Admin
2 | class DashboardFacade
3 |
4 | def initialize; end
5 |
6 | def total_invest_amount(type)
7 | InvestmentData.send(type).joins(:amount_sources).sum(:'amount_sources.amount')
8 | end
9 |
10 | def total_loan_amount
11 | LoanData.joins(:amount_sources).sum(:'amount_sources.amount')
12 | end
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/facades/documents_facade.rb:
--------------------------------------------------------------------------------
1 | class Admin
2 | class DocumentsFacade
3 |
4 | attr_reader :loan, :investor
5 |
6 | def initialize(loan_id, investor_id)
7 | @loan = Loan.find(loan_id)
8 | @investor = Investor.find(investor_id) if investor_id.present?
9 | end
10 |
11 | def lender?
12 | @investor.present?
13 | end
14 |
15 | def new_document
16 | @loan.documents.build(investor_id: @investor.id)
17 | end
18 |
19 | def documents
20 | Document.where(investment_id: @loan.id, investor_id: @investor.id)
21 | end
22 |
23 | def lenders
24 | @loan.lenders.order(:name).uniq.collect { |l| [l.name, l.id] }
25 | end
26 |
27 | def placeholder
28 | lender? ? 'Please select lender' : 'Lenders'
29 | end
30 |
31 | def selected_lender
32 | @investor ? @investor.id : nil
33 | end
34 |
35 | end
36 |
37 | end
38 |
--------------------------------------------------------------------------------
/app/forms/investor_form.rb:
--------------------------------------------------------------------------------
1 | class InvestorForm
2 | include ActiveModel::Model
3 |
4 | validates :name, presence: { message: "Can't be blank" }
5 | validate :email_uniqueness, if: :email_validation
6 |
7 | validates :email,
8 | presence: { message: "Can't be blank" },
9 | format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i, message: 'Not a valid email address' }
10 |
11 | validates :phone_number,
12 | presence: { message: "Can't be blank" },
13 | format: { with: /\A\+?[\d]+[\d\-]+\z/, message: 'Invalid phone number' },
14 | length: { minimum: 10, maximum: 15, message: 'The amount of characters is between 10 and 15' }
15 |
16 | # attr_accessor :check, :wire_transfer
17 | attr_reader :params, :address
18 |
19 | delegate :check, :wire_transfer, :connected?, :persisted?, :inactive?,
20 | :investor_viewers, :viewer_investors, :investor_group, :address, :inactive_investor_contact, to: :investor
21 |
22 | delegate :address_attributes=, to: :investor, prefix: false
23 | delegate :inactive_investor_contact_attributes=, to: :investor, prefix: false
24 |
25 | def self.model_name
26 | ActiveModel::Name.new(self, nil, 'Investor')
27 | end
28 |
29 | def initialize(instance = nil)
30 | @investor = instance
31 | investor.build_address unless investor.address
32 | investor.build_inactive_investor_contact if investor && investor.inactive_investor_contact.nil?
33 |
34 | investor_params_array.each do |key|
35 | singleton_class.send :attr_accessor, key
36 | singleton_class.send :delegate, key, to: :investor
37 | end
38 | end
39 |
40 | def submit(params)
41 | @params = params
42 | investor.assign_attributes(investor_params)
43 |
44 | if valid?
45 | investor.save!
46 | true
47 | else
48 | false
49 | end
50 | end
51 |
52 | def investor
53 | @investor ||= Investor.new
54 | end
55 |
56 | private
57 |
58 | def investor_params
59 | params.require(:investor).permit(:id, :name, :phone_number, :email, :password, :occupation, :birthday, :red_mark,
60 | :investor_group_id, :accredited_investor, :itin_ssn, :box_url, :connected,
61 | :wire_transfer_fee, :avatar, :mail_validation, :inactive,
62 | address_attributes: %i[id attention institution address_1 address_2 address_3 city
63 | state postal_code country investor_id],
64 | inactive_investor_contact_attributes: %i[investor_id name email phone_number comments])
65 | end
66 |
67 | def investor_params_array
68 | [:id, :name, :phone_number, :email, :password, :occupation, :birthday, :red_mark,
69 | :investor_group_id, :accredited_investor, :itin_ssn, :box_url, :connected,
70 | :wire_transfer_fee, :avatar, :email_validation, :inactive]
71 | end
72 |
73 | end
74 |
--------------------------------------------------------------------------------
/app/helpers/lead_helper.rb:
--------------------------------------------------------------------------------
1 | module LeadHelper
2 |
3 | def lead_price(listing)
4 | current = CalculateCurrency.call(price: listing.price, currency: @current_currency)
5 | delimiter = I18n.default_locale == :en ? ',' : ' '
6 | price = number_with_delimiter(current[:price], delimiter: delimiter)
7 | if listing.price.present? && listing.price.positive?
8 | sanitize("#{current[:currency]} #{price}")
9 | else
10 | t('home.index.negotiable')
11 | end
12 | end
13 |
14 | def lead_whatsapp_link(phone_number)
15 | "https://api.whatsapp.com/send?phone=#{phone_number.phone}"
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/app/helpers/partners_helper.rb:
--------------------------------------------------------------------------------
1 | module PartnersHelper
2 |
3 | def partner_activation(partner)
4 | partner.active? ? t('partners.extend_activation') : t('partners.activation')
5 | end
6 |
7 | def partner_info?(partner)
8 | partner.address.present? || partner.website.present?
9 | end
10 |
11 | def partner_edit_page_title(redirect_url)
12 | redirect_url.present? ? I18n.t('partners.upgrade_my_profile') : I18n.t('partners.edit_my_profile')
13 | end
14 |
15 | def available_hours
16 | (0..23).map { |h| "#{h}:00" }
17 | end
18 |
19 | def logo_title(logo)
20 | logo.present? ? I18n.t('partners.form.change_logo') : I18n.t('partners.form.add_logo')
21 | end
22 |
23 | def get_premium_title(partner)
24 | partner.active? ? t('partners.extend_premium_account') : t('dashboard.menu.get_premium_account')
25 | end
26 |
27 | def working_hours(partner)
28 | work_html = ''
29 | partner.working_hours.each do |schedule|
30 | next unless schedule.completely_filled?
31 | work_html += "
#{schedule.start_day}-#{schedule.end_day} #{schedule.opening_hours}-#{schedule.closing_hours}"
32 | end
33 | work_html
34 | end
35 |
36 | end
37 |
--------------------------------------------------------------------------------
/app/models/investor.rb:
--------------------------------------------------------------------------------
1 | class Investor < ApplicationRecord
2 | # == Constants ============================================================
3 |
4 | # == Attributes ===========================================================
5 |
6 | # == Extensions ===========================================================
7 | enum payment_method: [:wire_transfer, :check, :no_data]
8 | enum investor_type: [:regular, :admin]
9 |
10 | include PgSearch
11 | include InvestorPerformer
12 |
13 | devise :database_authenticatable, :registerable, :recoverable, :rememberable,
14 | :trackable, :timeoutable, timeout_in: 15.minutes
15 |
16 | mount_uploader :avatar, AvatarUploader
17 |
18 | # == Relationships ========================================================
19 | has_one :wire_transfer, dependent: :destroy
20 | has_one :check, dependent: :destroy
21 | has_one :address, dependent: :destroy
22 | has_one :pie_chart, through: :investor_dashboard
23 | has_one :inactive_investor_contact, dependent: :destroy
24 |
25 | # his viewers, managers, main connected investor
26 | has_many :investor_viewers, dependent: :destroy
27 | # investors he manages, views, his connected investors
28 | has_many :viewer_investors, class_name: 'InvestorViewer', foreign_key: :viewer_id, dependent: :destroy
29 | has_many :investor_dashboards, dependent: :destroy
30 | has_many :documents, dependent: :destroy
31 | has_many :admin_distributions, dependent: :destroy
32 | has_many :equity_datas, dependent: :destroy
33 | has_many :note_datas, dependent: :destroy
34 | has_many :opportunities_reports, dependent: :destroy
35 | has_many :notes, through: :note_datas, source: :note
36 | has_many :equities, -> { distinct }, through: :equity_datas, source: :equity
37 | has_many :viewers, through: :investor_viewers, source: :viewer
38 | has_many :opportunities, through: :opportunities_reports, source: :opportunity
39 | has_many :related_investors, dependent: :destroy
40 | has_many :backwards_related_investors, class_name: 'RelatedInvestor',
41 | foreign_key: 'related_investor_id', dependent: :destroy
42 |
43 | belongs_to :investor_group, optional: true
44 |
45 | accepts_nested_attributes_for :address, :inactive_investor_contact
46 |
47 | # == Validations ==========================================================
48 | validates :name, :phone_number, presence: true
49 |
50 | validates :phone_number, format: { with: /\A\+?[\d]+[\d\-]+\z/ },
51 | length: { in: 10..15 }, unless: proc { |investor| investor.phone_number.blank? }
52 |
53 | validates_confirmation_of :password, message: "Passwords don't match"
54 | validates :password, format: { with: /\A(?=.*?\d.*?\d)(?=.*[a-zA-Z]).{8,15}\z/, message: 'Password does not satisfy the requirements' }, allow_nil: true
55 |
56 | # == Scopes ===============================================================
57 | scope :real, -> { where(viewer: false).order(:name) }
58 | scope :viewers, -> { where(viewer: true) }
59 |
60 | pg_search_scope :search, against: %i[name], using: {
61 | tsearch: { prefix: true }
62 | }
63 |
64 |
65 | # == Callbacks ============================================================
66 | after_initialize :default_values
67 | after_save :update_dashboard, unless: -> { changes[:sign_in_count] || changes[:investor_group_id] }
68 | after_update :update_managers, if: -> { changes[:investor_group_id] }
69 |
70 | before_destroy :update_prime_investor!, prepend: true, if: :connected?
71 | # == Class Methods ========================================================
72 |
73 | # == Instance Methods =====================================================
74 |
75 | def profile_updated_notification
76 | AdminMailer.investor_update_profile(self, 'Profile was updated').deliver_now
77 | end
78 |
79 | def prime_connected_investor
80 | investor_viewers.connected.first&.viewer
81 | end
82 |
83 | def reset_tutorial
84 | self.tutorial_progress['/investor'] = false
85 | self.save
86 | end
87 |
88 | def investor_dashboard
89 | investor_dashboards.dollar.first
90 | end
91 |
92 | private
93 |
94 | def email_regexp
95 | /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
96 | end
97 |
98 | end
99 |
--------------------------------------------------------------------------------
/app/models/investor_fat.rb:
--------------------------------------------------------------------------------
1 | class Investor < ActiveRecord::Base
2 |
3 | devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :timeoutable, timeout_in: 15.minutes
4 |
5 | after_initialize :default_values
6 |
7 | mount_uploader :avatar, AvatarUploader
8 |
9 | has_one :wire_transfer, dependent: :destroy
10 | has_one :check, dependent: :destroy
11 | has_one :address, dependent: :destroy
12 | has_many :documents, dependent: :destroy
13 | has_many :admin_distributions, dependent: :destroy
14 | has_many :investor_viewers, dependent: :destroy
15 | has_many :investment_datas, dependent: :destroy
16 | has_many :loan_datas, dependent: :destroy
17 | has_many :opportunities_reports, dependent: :destroy
18 |
19 | has_many :loans, through: :loan_datas, source: :loan
20 | has_many :investments, through: :investment_datas, source: :investment
21 | has_many :viewers, through: :investor_viewers, source: :viewer
22 | has_many :opportunities, through: :opportunities_reports, source: :opportunity
23 |
24 | has_many :viewer_investors, class_name: 'InvestorViewer', foreign_key: :viewer_id, dependent: :destroy
25 | has_many :notifications, as: :human
26 |
27 | belongs_to :investor
28 | belongs_to :investor_group
29 |
30 | has_many :related_investors, dependent: :destroy
31 | has_many :backwards_related_investors, class_name: 'RelatedInvestor',
32 | foreign_key: 'related_investor_id',
33 | dependent: :destroy
34 |
35 | validates :name, presence: { message: "Can't be blank"}
36 | validates :email, presence: { message: "Can't be blank"}, uniqueness: { message: "Has already been taken"},
37 | email_format: true, if: :email_validation
38 | validates :phone_number, presence: { message: "Can't be blank"}, format: { with: /\A\+?[\d]+[\d\-]+\z/, message: 'Invalid phone number'},
39 | length: { minimum: 10, maximum: 15, message: 'The amount of characters is between 10 and 15' }
40 |
41 | validate :email_valid
42 | validate :birthday_valid
43 |
44 | enum payment_method: [:wire_transfer, :check, :no_data]
45 |
46 | accepts_nested_attributes_for :address
47 |
48 | accepts_nested_attributes_for :related_investors, :backwards_related_investors, reject_if: proc { |a| a['related_investor_id'].blank? }
49 |
50 | scope :real, -> { where(viewer: false) }
51 | scope :distributions, -> (year, quarter, direction) { real.real.joins(:admin_distributions).where('admin_distributions.year = ? AND admin_distributions.quarter = ?', year, quarter).order ("admin_distributions.payed #{direction}, name ASC") }
52 | scope :viewers, -> { where(viewer: true) }
53 |
54 | after_update :update_managers
55 |
56 | paginates_per 20
57 |
58 | def my_manager?(investor)
59 | self.investor_group.try(:manager_ids).try(:include?, investor.id)
60 | end
61 |
62 | def managers
63 | investor_viewers.where(viewer_type: 2).map { |iv| iv.investor }.uniq
64 | end
65 |
66 | def viewed_investors
67 | ids = viewer_investors.map(&:investor_id)
68 | ids << id unless self.viewer?
69 | Investor.where(id: ids)
70 | end
71 |
72 | def regular_viewers
73 | self.investor_viewers.where(viewer_type: 0).map(&:viewer)
74 | end
75 |
76 | def country
77 | address.try(:country)
78 | end
79 |
80 | def is_connected?(investor)
81 | email == investor.email && id != investor.id && investor.connected?
82 | end
83 |
84 | def uniq_email?
85 | email_validation
86 | end
87 |
88 | def has_connected_investor?
89 | Investor.where(email: email).where(connected: true).any? ? true : false
90 | end
91 |
92 | def birthday_valid
93 | if birthday.present? && (birthday <= 125.years.ago || birthday > Time.now)
94 | errors.add(:birthday, ' is invalid')
95 | end
96 | end
97 |
98 | def regular_investors
99 | InvestorViewer.where(viewer_id: id, viewer_type: 0)
100 | end
101 |
102 | def sub_investors
103 | InvestorViewer.where(viewer_id: id, viewer_type: 1)
104 | end
105 |
106 | def managed_investors
107 | InvestorViewer.where(viewer_id: id, viewer_type: 2)
108 | end
109 |
110 | def managed_group_name
111 | InvestorGroup.select { |ig| ig.manager_ids.include?(id) }.first.name
112 | end
113 |
114 | def sub_investor?
115 | Investor.select { |i| i.email == email && i.created_at < created_at }.present?
116 | end
117 |
118 | def total(value, type, action = :sum)
119 | investment_type = type == :development ? 0 : 1
120 | investment_datas.joins(:investment).where(['investments.investment_type = ?', investment_type])
121 | .send(action, "investment_data.#{value}")
122 | end
123 |
124 | def total_final_distribution(type)
125 | result = 0
126 | investments.includes(:investment_datas).send(type).each do |investment|
127 | investment_data = investment.investment_datas.detect { |inv_data| inv_data.investor_id == id }
128 | final_distribution = investment_data.final_distribution_after_fee
129 | result += final_distribution if final_distribution
130 | end
131 | result
132 | end
133 |
134 | def default_values
135 | self.payment_method ||= :no_data
136 | self.wire_transfer_fee ||= 20
137 | end
138 |
139 | def raw_payment_method
140 | Investor.payment_methods[self.payment_method]
141 | end
142 |
143 | def different_investments?
144 | self.investments.map(&:investment_type).uniq.count > 1
145 | end
146 |
147 | def box
148 | if box_url.present?
149 | box_url.include?('http') ? box_url : 'http://' + box_url
150 | else
151 | nil
152 | end
153 | end
154 |
155 | def total_distribution(investment)
156 | investment_data = investment_datas.find { |investment_data| investment_data.investment_id == investment.id }
157 | if investment.development?
158 | if investment.distribution
159 | investment_data.invest_amount + investment_data.holding.round(2) * investment.distribution / 100
160 | else
161 | 0
162 | end
163 | elsif investment.multi_family?
164 | investment.distribution_years.flat_map(&:distributions).map do |distribution|
165 | distribution.distribution_after_holding(investment_data)
166 | end.reject(&:nil?).inject(&:+)
167 | elsif investment.loan?
168 | loan_datas.find { |loan_data| loan_data.investment_id == investment.id }.loan_dividends.map(&:amount).inject(&:+)
169 | end
170 | end
171 |
172 | end
173 |
--------------------------------------------------------------------------------
/app/models/loan.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: loans
4 | #
5 | # id :integer not null, primary key
6 | # name :string
7 | # email :string
8 | # car_type :integer
9 | # amount_of_money :string
10 | # monthly_income :string
11 | # currency :string
12 | # city_id :integer
13 | # created_at :datetime not null
14 | # updated_at :datetime not null
15 | #
16 |
17 | class Loan < ApplicationRecord
18 |
19 | # == Constants ============================================================
20 |
21 | # == Attributes ===========================================================
22 |
23 | # == Extensions ===========================================================
24 | enum car_type: %i[new_car second_hand import]
25 |
26 | # == Relationships ========================================================
27 | belongs_to :city
28 | has_one :phone_number, as: :phoneable, dependent: :destroy, inverse_of: :phoneable
29 |
30 | accepts_nested_attributes_for :phone_number
31 |
32 | # == Validations ==========================================================
33 | validates :name, :car_type, :currency, :city_id, presence: true
34 | validates :email, presence: true, format: { with: Devise.email_regexp }
35 | validates :amount_of_money, :monthly_income, presence: true,
36 | numericality: { only_integer: true, greater_than: 0 },
37 | length: { maximum: 10 }
38 |
39 | # == Scopes ===============================================================
40 |
41 | # == Callbacks ============================================================
42 |
43 | # == Class Methods ========================================================
44 |
45 | # == Instance Methods =====================================================
46 |
47 | end
48 |
--------------------------------------------------------------------------------
/app/models/report.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: reports
4 | #
5 | # id :integer not null, primary key
6 | # subject :integer not null
7 | # comment :string
8 | # listing_id :integer
9 | # email :string
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | #
13 |
14 | class Report < ApplicationRecord
15 |
16 | # == Constants ============================================================
17 |
18 | # == Attributes ===========================================================
19 |
20 | # == Extensions ===========================================================
21 | enum subject: %i[sold scam wrong_number]
22 |
23 | # == Relationships ========================================================
24 | belongs_to :listing
25 |
26 | # == Validations ==========================================================
27 | validates :subject, presence: true
28 |
29 | # == Scopes ===============================================================
30 |
31 | # == Callbacks ============================================================
32 |
33 | # == Class Methods ========================================================
34 |
35 | # == Instance Methods =====================================================
36 |
37 | end
38 |
--------------------------------------------------------------------------------
/app/models/vehicle_listing.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: listings
4 | #
5 | # id :integer not null, primary key
6 | # new :boolean
7 | # car_type :integer
8 | # condition :integer
9 | # place :string
10 | # gear_type :integer
11 | # model_id :integer
12 | # fuel_id :integer
13 | # body_id :integer
14 | # price :integer
15 | # mileage :integer
16 | # certified :boolean
17 | # description :text
18 | # created_at :datetime not null
19 | # updated_at :datetime not null
20 | # city_id :integer
21 | # validated :boolean default(FALSE)
22 | # wheel :integer
23 | # climatisation :boolean
24 | # slug :string
25 | # color_id :integer
26 | # sold :boolean default(FALSE), not null
27 | # year :integer
28 | # engine_capacity :string
29 | # on_homepage_ended_at :datetime
30 | # fixed_on_top_ended_at :datetime
31 | # bumped_on_top_ended_at :datetime
32 | # position :integer
33 | # last_reacted :date
34 | # token :string
35 | # validated_at :date
36 | # seller_type :string
37 | # seller_id :integer
38 | # type :string
39 | # spare_part_category_id :integer
40 | # impressions_count :integer default(0)
41 | #
42 |
43 | class VehicleListing < Listing
44 |
45 | # == Constants ============================================================
46 |
47 | # == Attributes ===========================================================
48 | attr_accessor :brand_id
49 |
50 | # == Extensions ===========================================================
51 |
52 | friendly_id :url, use: %i[slugged finders]
53 |
54 | enum car_type: {
55 | car: 1,
56 | moto: 2,
57 | truck: 3
58 | }
59 |
60 | enum gear_type: {
61 | manual: 1,
62 | automatic: 2
63 | }
64 |
65 | enum wheel: {
66 | right: 1,
67 | left: 2
68 | }
69 |
70 | # == Relationships ========================================================
71 | belongs_to :model
72 | belongs_to :fuel
73 | belongs_to :color
74 | belongs_to :body_type, foreign_key: :body_id
75 |
76 | has_one :brand, through: :model
77 |
78 | # == Validations ==========================================================
79 | validates :price, inclusion: { in: proc { Calculate::DefaultPriceRange.call } }
80 | validates :year, inclusion: { in: 1900..Time.zone.today.year }, allow_blank: true
81 |
82 | validates :engine_capacity, :mileage, numericality: { greater_than: 0 }, allow_blank: true
83 |
84 | validates :mileage, length: { maximum: 7 }
85 | validates :description, length: { maximum: 1000 }
86 |
87 | validates :car_type, :color_id, :gear_type, :fuel_id, :body_id, :model_id, presence: true
88 |
89 | # == Scopes ===============================================================
90 | scope :color, ->(color_ids) { where(color: color_ids) }
91 | scope :wheel, ->(wheel_ids) { where(wheel: wheel_ids) }
92 | scope :model_id, ->(model_ids) { where(model_id: model_ids) }
93 | scope :body_type, ->(body_type_id) { where(body_id: body_type_id) }
94 | scope :body_id, ->(body_type_id) { where(body_id: body_type_id) }
95 | scope :car_type, ->(car_type_ids) { where(car_type: car_type_ids) }
96 | scope :fuel_id, ->(fuel_type_ids) { where(fuel_id: fuel_type_ids) }
97 | scope :maxmileage, ->(mileage) { where("mileage <= ? OR mileage = '0' OR mileage IS NULL", mileage) }
98 | scope :minmileage, ->(mileage) { where("mileage >= ? OR mileage = '0' OR mileage IS NULL", mileage) }
99 | scope :gear_type, ->(gear_type_ids) { where(gear_type: gear_type_ids) }
100 | scope :type, ->(car_type) { where(car_type: VehicleListing.car_types[car_type]) }
101 | scope :make, ->(name) { joins(:model).where('models.name = ?', name) }
102 | scope :brand, ->(name) { joins(:brand).where('brands.name = ?', name) }
103 | scope :brand_id, ->(brand_ids) { joins(:brand).where(brands: { id: brand_ids }) }
104 | scope :body, ->(name) { joins(body_type: :translations).where('body_type_translations.name = ?', name) }
105 |
106 | # == Callbacks ============================================================
107 | has_secure_token :token
108 |
109 | # == Class Methods ========================================================
110 | def self.prices
111 | validated.pluck(:price, :discount_price).map { |p| p.last || p.first }.compact
112 | end
113 |
114 | def self.max_price(currency)
115 | CalculateCurrency.call(price: prices.max, currency: currency)[:price]
116 | end
117 |
118 | def self.promoted_on_top(car_type, limit = ADS_ON_TOP)
119 | listings = validated.available.car_type(car_type)
120 | listings.on_promotion('fixed_on_top')
121 | .or(listings.on_promotion('on_homepage')).order('RANDOM()').limit(limit)
122 | end
123 |
124 | # == Instance Methods =====================================================
125 | # Slug generation rule
126 |
127 | def url
128 | I18n.t('common.ad_url', locale: I18n.default_locale, model_brand_name: brand&.name, model_name: model&.name,
129 | city_region_name: region&.name, city_name: city&.name, id: id)
130 | end
131 |
132 | private
133 |
134 | def clear_unused_moto_fields
135 | return unless moto?
136 |
137 | self.climatisation = nil
138 | self.wheel = nil
139 | end
140 |
141 | end
142 |
--------------------------------------------------------------------------------
/app/performers/investment_data_performer.rb:
--------------------------------------------------------------------------------
1 | module InvestmentDataPerformer
2 | def self.included(base)
3 | base.extend ClassMethods
4 | end
5 |
6 | module ClassMethods
7 |
8 | end
9 |
10 | def total_distribution
11 | investment.rental_properties? ? total_quarterly_distribution : total_distribution_dev
12 | end
13 |
14 | def profit_less_reserves_for_distribution
15 | return unless investment.profit_less_reserves_for_distribution
16 |
17 | investment.profit_less_reserves_for_distribution * holding / 100
18 | end
19 |
20 | def non_final_profit
21 | return nil unless investment.non_final_profit
22 |
23 | investment.non_final_profit * holding / 100
24 | end
25 |
26 | def final_distribution
27 | investment.distribution ? investment.distribution * holding / 100 : nil
28 | end
29 |
30 | def final_distribution_after_fee
31 | investment.distribution * holding / 100 * (100 - dragonfly_fee) / 100 if investment.distribution && dragonfly_fee
32 | end
33 |
34 |
35 | def last_distribution
36 | investment.distribution_years.try(:last).try(:distributions).try(:last).try(:amount)
37 | end
38 |
39 | def total_quarterly_distribution
40 | ((investment.total_distribution || 0) * holding / 100) || 0
41 | end
42 |
43 | def sale_distribution
44 | (distribution_from_sale || (investment.net_available_for_distribution || 0) * holding / 100) || 0
45 | end
46 |
47 | end
48 |
--------------------------------------------------------------------------------
/app/performers/investment_performer.rb:
--------------------------------------------------------------------------------
1 | module InvestmentPerformer
2 |
3 | def self.included(base)
4 | base.extend ClassMethods
5 | end
6 |
7 | module ClassMethods
8 | def sfrp
9 | Investment.single_family_rental_properties
10 | end
11 | end
12 |
13 | def total_distribution
14 | distribution_years.joins(:distributions).sum('distributions.amount')
15 | end
16 |
17 | def progresses_with_order
18 | progresses.order(development? ? 'progresses.updated DESC' : 'progresses.year DESC, progresses.quarter DESC')
19 | end
20 |
21 | def sfrp?
22 | single_family_rental_properties?
23 | end
24 |
25 | def rental_properties?
26 | multi_family? || sfrp?
27 | end
28 |
29 | def total_distribution_for_year(year)
30 | distribution_years.where(['year = ?', year]).joins(:distributions).sum('distributions.amount')
31 | end
32 |
33 | def next_image_order
34 | last_image = images.order(order: :asc).last
35 | last_image ? last_image.order + 1 : 0
36 | end
37 |
38 | def reconstruct_order
39 | images.order(order: :asc).each_with_index do |image, index|
40 | image.order = index unless image.order == index
41 | image.save
42 | end
43 | end
44 |
45 | def table_color
46 | completed? ? 'green' : 'blue'
47 | end
48 |
49 | def progress_quarters
50 | output = []
51 | progresses.each do |progress|
52 | quarter = CalendarQuarter.new(progress.raw_quarter + 1, progress.year)
53 | output << quarter unless output.include? quarter
54 | end
55 | output.sort.reverse
56 | end
57 |
58 | def full_name
59 | complex_name.present? ? name.to_s + ' - ' + complex_name.to_s : name
60 | end
61 |
62 | def current_stage?(stage)
63 | stage.stage.include?(timeline)
64 | end
65 |
66 | def current_stage
67 | stages.find_by(stage: Investment.timelines[timeline])
68 | end
69 |
70 | end
71 |
--------------------------------------------------------------------------------
/app/performers/investor_performer.rb:
--------------------------------------------------------------------------------
1 | module InvestorPerformer
2 |
3 | def self.included(base)
4 | base.extend ClassMethods
5 | end
6 |
7 | module ClassMethods
8 |
9 | end
10 |
11 | def sub_investor?
12 | Investor.select { |i| i.email == email && i.created_at < created_at }.present?
13 | end
14 |
15 | def default_values
16 | self.payment_method ||= :no_data
17 | self.wire_transfer_fee ||= 20
18 | end
19 |
20 | def raw_payment_method
21 | Investor.payment_methods[payment_method]
22 | end
23 |
24 | def different_investments?
25 | investments.map(&:investment_type).uniq.count >= 1
26 | end
27 |
28 | def box
29 | return nil if box_url.blank?
30 |
31 | box_url.include?('http') ? box_url : 'http://' + box_url
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/app/performers/loan_data_performer.rb:
--------------------------------------------------------------------------------
1 | module LoanDataPerformer
2 | def self.included(base)
3 | base.extend ClassMethods
4 | end
5 |
6 | module ClassMethods
7 |
8 | end
9 |
10 | def end_date
11 | ([end_of_loan] + loan_date_extensions.pluck(:maturity_date_after_extension).compact).last
12 | end
13 |
14 | def nearest_end_date
15 | if loan_date_extensions.any?
16 | lde = loan_date_extensions.pluck(:maturity_date_after_extension).unshift(end_of_loan)
17 | lde.each { |date| return date if date > Date.today }
18 | lde[-1]
19 | else
20 | end_of_loan
21 | end
22 | end
23 |
24 | def dividends
25 | dividends = loan_dividends.select { |ld| ld.submited }.map(&:amount).inject { |sum, x| sum.to_f + x.to_f }
26 | dividends.round if dividends.present?
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/app/policies/cart_policy.rb:
--------------------------------------------------------------------------------
1 | class CartPolicy < ApplicationPolicy
2 |
3 | def show?
4 | user_active?
5 | end
6 |
7 | def create?
8 | user_active?
9 | end
10 |
11 | def update?
12 | user_active? && record.waiting_for_order_details?
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/app/policies/post_policy.rb:
--------------------------------------------------------------------------------
1 | class PostPolicy < ApplicationPolicy
2 |
3 | def create?
4 | user_active?
5 | end
6 |
7 | def destroy?
8 | user_active? && current_user.posts.include?(record)
9 | end
10 |
11 | def like?
12 | user_active? && Like.find_by(post: record, user: current_user).blank?
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/app/policies/store_front_policy.rb:
--------------------------------------------------------------------------------
1 | class StorePolicy < ApplicationPolicy
2 |
3 | def show?
4 | record.operational?
5 | end
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/app/presenters/dividend_year_dispatcher.rb:
--------------------------------------------------------------------------------
1 | class DividendYear
2 | class DividendYearDispatcher
3 |
4 | def initialize(dividend_year:, default_presenter: YearlyDistributionPresenter)
5 | @dividend_year = dividend_year
6 | @default_presenter = default_presenter
7 | end
8 |
9 | def results
10 | presenter.dividends_year
11 | end
12 |
13 | def presenter
14 | presenter_class(@dividend_year.distribution).new(@dividend_year)
15 | end
16 |
17 | def presenter_class(distribution)
18 | ('DividendYear::' + "#{distribution.gsub(/[ +]/,'_')}_distribution_presenter".camelize).safe_constantize || @default_presenter
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/presenters/monthly_distribution_presenter.rb:
--------------------------------------------------------------------------------
1 | class DividendYear
2 | class MonthlyDistributionPresenter < DividendYear::BaseDividendYearPresenter
3 | MONTHS = %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec].freeze
4 |
5 | def dividends_year
6 | MONTHS.map do |month|
7 | value_decorator(select_dividend_by_month(@dividend_year, month).try(:amount))
8 | end
9 | end
10 |
11 | def select_dividend_by_month(dividend_year, month)
12 | dividend_year.loan_dividends.select(&:submited).find { |ld| ld.submitted_date.strftime('%b') == month }
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/presenters/quarterly_distribution_presenter.rb:
--------------------------------------------------------------------------------
1 | class DividendYear
2 | class QuarterlyDistributionPresenter < DividendYear::BaseDividendYearPresenter
3 | QUARTERS = %w[Q1 Q2 Q3 Q4].freeze
4 |
5 | def dividends_year
6 | QUARTERS.map do |quarter|
7 | value_decorator(select_dividend_by_quarter(@dividend_year, quarter).try(:amount))
8 | end
9 | end
10 |
11 | def select_dividend_by_quarter(dividend_year, quarter)
12 | dividend_year.loan_dividends.select(&:submited).find do|ld|
13 | quarter[1] == CalendarQuarter.from_date(ld.submitted_date).quarter.to_s
14 | end
15 | end
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/app/presenters/yearly_distribution_presenter.rb:
--------------------------------------------------------------------------------
1 | class DividendYear
2 | class YearlyDistributionPresenter < DividendYear::BaseDividendYearPresenter
3 | def dividends_year
4 | [value_decorator(@dividend_year.loan_dividends.sample.try(:amount))]
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/serializers/chat_attachment_serializer.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: chat_attachments
4 | #
5 | # id :bigint not null, primary key
6 | # attachment_data :text
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # chat_message_id :bigint
10 | #
11 | # Indexes
12 | #
13 | # index_chat_attachments_on_chat_message_id (chat_message_id)
14 | #
15 | class ChatAttachmentSerializer
16 |
17 | include JSONAPI::Serializer
18 |
19 | attributes :id
20 |
21 | attribute :url, &:attachment_url
22 |
23 | attribute :type do |obj|
24 | obj.attachment.mime_type
25 | end
26 |
27 | attribute :name do |obj|
28 | obj.attachment['filename']
29 | end
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/app/serializers/chat_message_serializer.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: chat_messages
4 | #
5 | # id :bigint not null, primary key
6 | # messageble_type :string
7 | # text :text
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # chat_id :bigint
11 | # messageble_id :bigint
12 | #
13 | # Indexes
14 | #
15 | # index_chat_messages_on_chat_id (chat_id)
16 | # index_chat_messages_on_messageble (messageble_type,messageble_id)
17 | #
18 | class ChatMessageSerializer
19 |
20 | include JSONAPI::Serializer
21 |
22 | attributes :id, :messageble_type, :messageble_id, :sender_full_name, :text, :created_at
23 |
24 | has_many :attachments, serializer: ChatAttachmentSerializer
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/app/serializers/review_serializer.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: reviews
4 | #
5 | # id :bigint not null, primary key
6 | # comment :text
7 | # rating :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # user_id :bigint
11 | #
12 | # Indexes
13 | #
14 | # index_reviews_on_user_id (user_id)
15 | #
16 | class ReviewSerializer
17 |
18 | include JSONAPI::Serializer
19 |
20 | attributes :id, :comment, :rating, :created_at
21 |
22 | belongs_to :user
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/app/services/calculate_currency.rb:
--------------------------------------------------------------------------------
1 | class CalculateCurrency < BaseService
2 |
3 | attr_reader :price, :currency, :rewert
4 |
5 | def initialize(options = {})
6 | @price = options[:price].to_i
7 | @currency = options[:currency]
8 | @rewert = options[:rewert] || nil
9 | end
10 |
11 | def call
12 | rate = get_rate(ExchangeRate.base_currency, Settings.currencies[currency])
13 | money = rate.present? ? get_price(rate) : price
14 | { currency: currency, price: money }
15 | end
16 |
17 | private
18 |
19 | def get_price(rate)
20 | rewert.present? ? (price / rate).floor : (rate * price).floor
21 | end
22 |
23 | def get_rate(from, to)
24 | Money.default_bank.get_rate(from, to)
25 | end
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/app/services/connect_listings_to_seller.rb:
--------------------------------------------------------------------------------
1 | class ConnectListingsToSeller < BaseService
2 |
3 | attr_reader :resource, :listings_ids
4 |
5 | def initialize(resource, listings_ids)
6 | @resource = resource
7 | @listings_ids = listings_ids
8 | end
9 |
10 | def call
11 | return if !resource.persisted? || listings_ids.nil?
12 | connect_listings_to_seller
13 | end
14 |
15 | private
16 |
17 | def connect_listings_to_seller
18 | Listing.where(id: listings_ids).each { |listing| listing.update(seller: resource) if listing.seller.blank? }
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/app/workers/check_ad_worker.rb:
--------------------------------------------------------------------------------
1 | class CheckAdWorker
2 |
3 | include Sidekiq::Worker
4 | sidekiq_options queue: ENV['CARRIERWAVE_BACKGROUNDER_QUEUE'].to_sym
5 |
6 | def perform(ad_id)
7 | @ad = Listing.find(ad_id)
8 | return if @ad.blank?
9 | mark_as_sold if not_reacted?
10 | return if @ad.sold?
11 | send_email
12 | CheckAdWorker.perform_in(DAYS_TO_CHECK_AD.days, @ad.id)
13 | end
14 |
15 | private
16 |
17 | def mark_as_sold
18 | @ad.update_attribute('sold', true) # rubocop:disable SkipsModelValidations
19 | end
20 |
21 | def not_reacted?
22 | @ad.last_reacted < Time.zone.today - DAYS_TO_MARK_AS_SOLD
23 | end
24 |
25 | def send_email
26 | CheckAdMailer.check_email(@ad).deliver_now
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/app/workers/move_to_top_worker.rb:
--------------------------------------------------------------------------------
1 | class MoveToTopWorker
2 |
3 | include Sidekiq::Worker
4 | sidekiq_options queue: ENV.fetch('CARRIERWAVE_BACKGROUNDER_QUEUE').to_sym
5 |
6 | def perform(prom_id, prom_class)
7 | promotion = prom_class.constantize.find(prom_id)
8 | return if promotion.bumped_on_top_ended_at < Time.zone.today
9 |
10 | promotion.bump_to_top
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/app/workers/partner_deactivate_worker.rb:
--------------------------------------------------------------------------------
1 | class PartnerDeactivateWorker
2 |
3 | include Sidekiq::Worker
4 | sidekiq_options queue: ENV['CARRIERWAVE_BACKGROUNDER_QUEUE'].to_sym
5 |
6 | def perform(id)
7 | @partner = Partner.find(id)
8 | deactivate_partner if @partner.active_to < Time.zone.today
9 | end
10 |
11 | private
12 |
13 | def deactivate_partner
14 | @partner.update(active: false, active_to: nil)
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------