├── spec
├── internal
│ ├── log
│ │ └── .gitignore
│ ├── public
│ │ └── favicon.ico
│ ├── Rakefile
│ ├── config
│ │ ├── routes.rb
│ │ ├── database.yml.travis
│ │ └── initializers
│ │ │ └── devise.rb
│ └── db
│ │ └── schema.rb
├── support
│ ├── request_helpers.rb
│ └── controller_macros.rb
├── factories
│ ├── users.rb
│ ├── fields.rb
│ └── models.rb
├── models
│ └── kms
│ │ ├── model_spec.rb
│ │ ├── field_spec.rb
│ │ └── has_many_field_spec.rb
├── controllers
│ └── kms
│ │ ├── models
│ │ ├── fields_controller_spec.rb
│ │ └── models_controller_spec.rb
│ │ └── public
│ │ └── entries_controller_spec.rb
└── spec_helper.rb
├── app
├── assets
│ ├── images
│ │ └── kms_models
│ │ │ └── .keep
│ ├── javascripts
│ │ ├── templates
│ │ │ ├── fields
│ │ │ │ ├── checkbox_field.html.slim
│ │ │ │ ├── string_field.html.slim
│ │ │ │ ├── text_field.html.slim
│ │ │ │ ├── date_field.html.slim
│ │ │ │ ├── belongs_to_field.html.slim
│ │ │ │ ├── has_many_field.html.slim
│ │ │ │ └── file_field.html.slim
│ │ │ ├── entries
│ │ │ │ ├── new.html.slim
│ │ │ │ ├── form.html.slim
│ │ │ │ ├── edit.html.slim
│ │ │ │ └── index.html.slim
│ │ │ ├── models
│ │ │ │ ├── new.html.slim
│ │ │ │ ├── edit.html.slim
│ │ │ │ ├── index.html.slim
│ │ │ │ ├── form.html.slim
│ │ │ │ └── fields.html.slim
│ │ │ └── help
│ │ │ │ ├── models_variables.html.slim
│ │ │ │ └── models_endpoints.html.slim
│ │ └── kms_models
│ │ │ ├── application.js
│ │ │ └── application
│ │ │ ├── controllers
│ │ │ ├── models_controller.coffee.erb
│ │ │ ├── fields_controller.coffee.erb
│ │ │ └── entries_controller.coffee.erb
│ │ │ └── routes.coffee.erb
│ └── stylesheets
│ │ └── kms_models
│ │ └── application.css
├── models
│ └── kms
│ │ ├── date_field.rb
│ │ ├── text_field.rb
│ │ ├── string_field.rb
│ │ ├── checkbox_field.rb
│ │ ├── belongs_to_field.rb
│ │ ├── file_field.rb
│ │ ├── field.rb
│ │ ├── models_wrapper.rb
│ │ ├── model.rb
│ │ ├── page_decorator.rb
│ │ ├── has_many_field.rb
│ │ └── entry.rb
├── helpers
│ └── kms_models
│ │ └── application_helper.rb
├── serializers
│ └── kms
│ │ ├── simple_model_serializer.rb
│ │ ├── field_serializer.rb
│ │ ├── model_serializer.rb
│ │ └── entry_serializer.rb
├── views
│ └── layouts
│ │ └── kms_models
│ │ └── application.html.erb
├── controllers
│ └── kms
│ │ ├── models
│ │ ├── fields_controller.rb
│ │ ├── entries_controller.rb
│ │ └── models_controller.rb
│ │ └── public
│ │ └── entries_controller.rb
└── uploaders
│ └── entry_file_uploader.rb
├── lib
├── kms
│ └── models
│ │ ├── version.rb
│ │ └── engine.rb
├── kms_models.rb
├── tasks
│ └── kms_models_tasks.rake
├── drops
│ └── kms
│ │ ├── models_wrapper_drop.rb
│ │ └── entry_drop.rb
└── generators
│ └── kms_models
│ └── install
│ └── install_generator.rb
├── config
├── initializers
│ ├── externals.rb
│ ├── help.rb
│ ├── ability.rb
│ └── resources.rb
├── routes.rb
└── locales
│ ├── en.yml
│ └── ru.yml
├── CONTRIBUTING.md
├── config.ru
├── db
└── migrate
│ ├── 20150820080436_add_label_field_to_kms_models.rb
│ ├── 20150821201250_fix_models_column_name.rb
│ ├── 20150901115303_add_class_name_to_kms_fields.rb
│ ├── 20180122135245_add_description_to_kms_models.rb
│ ├── 20170802085121_add_position_to_kms_fields.rb
│ ├── 20150910081440_add_position_to_kms_entries.rb
│ ├── 20150820132142_add_slug_to_kms_entries.rb
│ ├── 20170209125819_add_allow_creation_using_form_to_models.rb
│ ├── 20170802063046_change_values_column_to_jsonb.rb
│ ├── 20150413143711_create_kms_entries.rb
│ ├── 20150409124420_create_kms_models.rb
│ └── 20150409125056_create_kms_fields.rb
├── .gitignore
├── .travis.yml
├── bin
└── rails
├── Gemfile
├── Rakefile
├── kms_models.gemspec
├── MIT-LICENSE
├── README.md
└── CHANGELOG.md
/spec/internal/log/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
--------------------------------------------------------------------------------
/spec/internal/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/kms_models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/internal/Rakefile:
--------------------------------------------------------------------------------
1 | require 'combustion'
2 | Combustion::Application.load_tasks
3 |
--------------------------------------------------------------------------------
/app/models/kms/date_field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class DateField < Field
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/kms/text_field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class TextField < Field
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/kms/string_field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class StringField < Field
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/kms/models/version.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | module Models
3 | VERSION = '1.1.0'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/kms_models.rb:
--------------------------------------------------------------------------------
1 | require "kms/models/engine"
2 |
3 | module Kms
4 | module Models
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/helpers/kms_models/application_helper.rb:
--------------------------------------------------------------------------------
1 | module KmsModels
2 | module ApplicationHelper
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/config/initializers/externals.rb:
--------------------------------------------------------------------------------
1 | Kms::ExternalsRegistry.register(:models) {|_,_| Kms::ModelsWrapper.new.to_drop }
2 |
--------------------------------------------------------------------------------
/spec/internal/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount Kms::Models::Engine => '/kms'
3 | end
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This is really easy:
2 |
3 | - [x] Fork it
4 | - [x] Code it
5 | - [x] Submit Pull Request
6 | - [x] Get it merged
7 |
--------------------------------------------------------------------------------
/lib/tasks/kms_models_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :kms_models do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/config/initializers/help.rb:
--------------------------------------------------------------------------------
1 | Kms::HelpService.register_templates Kms::Models::Engine, 'help/models_variables.html', 'help/models_endpoints.html'
2 |
--------------------------------------------------------------------------------
/spec/support/request_helpers.rb:
--------------------------------------------------------------------------------
1 | module Requests
2 | module JsonHelpers
3 | def json
4 | JSON.parse(response.body)
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler'
3 |
4 | Bundler.require :default, :development
5 |
6 | Combustion.initialize! :all
7 | run Combustion::Application
8 |
--------------------------------------------------------------------------------
/app/serializers/kms/simple_model_serializer.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class SimpleModelSerializer < ActiveModel::Serializer
3 | attributes :id, :label_field
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/models/kms/checkbox_field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class CheckboxField < Field
3 | def get_value(entry)
4 | entry.values[liquor_name] == 'true'
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/internal/config/database.yml.travis:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: postgresql
3 | database: kms_test
4 | username: postgres
5 | password:
6 | encoding: utf-8
7 | host: localhost
8 |
--------------------------------------------------------------------------------
/app/serializers/kms/field_serializer.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class FieldSerializer < ActiveModel::Serializer
3 | attributes :id, :name, :liquor_name, :type, :class_name
4 | end
5 |
6 | end
7 |
--------------------------------------------------------------------------------
/config/initializers/ability.rb:
--------------------------------------------------------------------------------
1 | if Kms::Model.table_exists?
2 | Kms::Model.all.each do |model|
3 | Kms::AbilityService.register do
4 | can :manage, model
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20150820080436_add_label_field_to_kms_models.rb:
--------------------------------------------------------------------------------
1 | class AddLabelFieldToKmsModels < ActiveRecord::Migration[4.2]
2 | def change
3 | add_column :kms_models, :label_field, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150821201250_fix_models_column_name.rb:
--------------------------------------------------------------------------------
1 | class FixModelsColumnName < ActiveRecord::Migration[4.2]
2 | def change
3 | rename_column :kms_models, :model_name, :kms_model_name
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150901115303_add_class_name_to_kms_fields.rb:
--------------------------------------------------------------------------------
1 | class AddClassNameToKmsFields < ActiveRecord::Migration[4.2]
2 | def change
3 | add_column :kms_fields, :class_name, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20180122135245_add_description_to_kms_models.rb:
--------------------------------------------------------------------------------
1 | class AddDescriptionToKmsModels < ActiveRecord::Migration[5.1]
2 | def change
3 | add_column :kms_models, :description, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/factories/users.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :user, class: Kms::User do
3 | email "admin@example.com"
4 | password "password"
5 | password_confirmation "password"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/drops/kms/models_wrapper_drop.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class ModelsWrapperDrop < Liquor::Drop
3 | def self.register_model(collection_name)
4 | has_many collection_name.to_sym
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/fields/checkbox_field.html.slim:
--------------------------------------------------------------------------------
1 | .checkbox
2 | label
3 | input type="checkbox" ng-model="entry.values[field.liquor_name]" ng-attr-id="{{field.liquor_name}}"
4 | | {{ field.name }}
5 |
--------------------------------------------------------------------------------
/db/migrate/20170802085121_add_position_to_kms_fields.rb:
--------------------------------------------------------------------------------
1 | class AddPositionToKmsFields < ActiveRecord::Migration[5.1]
2 | def change
3 | add_column :kms_fields, :position, :integer, default: 0, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150910081440_add_position_to_kms_entries.rb:
--------------------------------------------------------------------------------
1 | class AddPositionToKmsEntries < ActiveRecord::Migration[4.2]
2 | def change
3 | add_column :kms_entries, :position, :integer, default: 0, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | spec/internal/config/database.yml
5 | spec/internal/log/*.log
6 | spec/internal/tmp/
7 | spec/internal/.sass-cache
8 | *.swp
9 | *.swo
10 | *~
11 | .byebug_history
12 | Gemfile.lock
13 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/fields/string_field.html.slim:
--------------------------------------------------------------------------------
1 | label for="{{ field.liquor_name }}"
2 | | {{ field.name }}
3 | input.form-control type="text" ng-model="entry.values[field.liquor_name]" ng-attr-id="{{field.liquor_name}}"
4 |
--------------------------------------------------------------------------------
/db/migrate/20150820132142_add_slug_to_kms_entries.rb:
--------------------------------------------------------------------------------
1 | class AddSlugToKmsEntries < ActiveRecord::Migration[4.2]
2 | def change
3 | add_column :kms_entries, :slug, :string
4 | add_index :kms_entries, :slug, unique: true
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/config/initializers/resources.rb:
--------------------------------------------------------------------------------
1 | Kms::ResourceService.register(:models, Kms::Model, "fa-tasks")
2 | if Kms::Model.table_exists?
3 | Kms::Model.all.each do |model|
4 | Kms::ResourceService.register(:models, model, "fa-tasks")
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/entries/new.html.slim:
--------------------------------------------------------------------------------
1 | .row
2 | .col-lg-12
3 | form role="form" ng-submit="create()" novalidate=""
4 | ng-include src="'entries/form.html'"
5 | button.btn.btn-default type="submit" = I18n.t(:add_entry)
6 |
--------------------------------------------------------------------------------
/db/migrate/20170209125819_add_allow_creation_using_form_to_models.rb:
--------------------------------------------------------------------------------
1 | class AddAllowCreationUsingFormToModels < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :kms_models, :allow_creation_using_form, :boolean, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20170802063046_change_values_column_to_jsonb.rb:
--------------------------------------------------------------------------------
1 | class ChangeValuesColumnToJsonb < ActiveRecord::Migration[5.1]
2 | def change
3 | execute "ALTER TABLE kms_entries ALTER COLUMN values SET DATA TYPE jsonb USING values::jsonb"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/fields/text_field.html.slim:
--------------------------------------------------------------------------------
1 | label for="{{ field.liquor_name }}"
2 | | {{ field.name }}
3 | textarea.form-control ng-model="entry.values[field.liquor_name]" rows="15" ckeditor='editorOptions' ng-attr-id="{{field.liquor_name}}"
4 |
--------------------------------------------------------------------------------
/spec/models/kms/model_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Kms
4 | describe Model, type: :model do
5 | it { should have_many(:fields) }
6 | it { should have_many(:entries) }
7 | it { should have_db_column(:allow_creation_using_form) }
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/models/new.html.slim:
--------------------------------------------------------------------------------
1 | .row
2 | .col-lg-12
3 | form role="form" ng-submit="create()" novalidate=""
4 | ng-include src="'models/form.html'"
5 | ng-include src="'models/fields.html'"
6 | button.btn.btn-default type="submit" = I18n.t(:add_model)
7 |
--------------------------------------------------------------------------------
/db/migrate/20150413143711_create_kms_entries.rb:
--------------------------------------------------------------------------------
1 | class CreateKmsEntries < ActiveRecord::Migration[4.2]
2 | def change
3 | create_table :kms_entries do |t|
4 | t.belongs_to :model, index: true
5 | t.json :values
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/entries/form.html.slim:
--------------------------------------------------------------------------------
1 | .form-group ng-repeat="field in model.fields_attributes"
2 | ng-include src="'fields/' + getFieldTemplateName(field) + '.html'"
3 | /input.form-control type="text" ng-model="entry.values[field.liquor_name]" ng-attr-id="{{field.liquor_name}}"
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/models/edit.html.slim:
--------------------------------------------------------------------------------
1 | .row
2 | .col-lg-12
3 | form role="form" ng-submit="update()" novalidate=""
4 | ng-include src="'models/form.html'"
5 | ng-include src="'models/fields.html'"
6 | button.btn.btn-default type="submit" = I18n.t(:update_model)
7 |
--------------------------------------------------------------------------------
/db/migrate/20150409124420_create_kms_models.rb:
--------------------------------------------------------------------------------
1 | class CreateKmsModels < ActiveRecord::Migration[4.2]
2 | def change
3 | create_table :kms_models do |t|
4 | t.string :model_name
5 | t.string :collection_name
6 |
7 | t.timestamps null: false
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/models/kms/belongs_to_field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class BelongsToField < Field
3 | def get_value(entry)
4 | entry_id = entry.values[liquor_name]
5 | association_record = Kms::Entry.find_by(id: entry_id)
6 | Liquor::DropDelegation.wrap_element(association_record)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.3.0
4 | services:
5 | - postgresql
6 | addons:
7 | postgresql: "9.4"
8 | script:
9 | - bundle exec rspec
10 | before_script:
11 | - psql -c 'create database kms_test;' -U postgres
12 | - cp spec/internal/config/database.yml.travis spec/internal/config/database.yml
13 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/help/models_variables.html.slim:
--------------------------------------------------------------------------------
1 | h4 = I18n.t("liquor_help.variables_title")
2 | p
3 | ul
4 | li
5 | var models
6 | p
7 | span = I18n.t('liquor_help.variables.models.main_description')
8 | p
9 | code
10 | | {% for service in: models.services do: %}
11 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/fields/date_field.html.slim:
--------------------------------------------------------------------------------
1 | label for="{{ field.liquor_name }}"
2 | | {{ field.name }}
3 | input.form-control datepicker-popup="" is-open="field.datepickerOpened" show-button-bar="false" ng-click="field.datepickerOpened = true" type="text" ng-model="entry.values[field.liquor_name]" ng-attr-id="{{field.liquor_name}}"
4 |
--------------------------------------------------------------------------------
/app/views/layouts/kms_models/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | KmsModels
5 | <%= stylesheet_link_tag "kms_models/application", media: "all" %>
6 | <%= javascript_include_tag "kms_models/application" %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/db/migrate/20150409125056_create_kms_fields.rb:
--------------------------------------------------------------------------------
1 | class CreateKmsFields < ActiveRecord::Migration[4.2]
2 | def change
3 | create_table :kms_fields do |t|
4 | t.string :name
5 | t.string :liquor_name
6 | t.string :type
7 | t.boolean :required
8 | t.belongs_to :model
9 |
10 | t.timestamps null: false
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/models/kms/file_field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class FileField < Field
3 | def get_value(entry)
4 | value = entry.values[liquor_name]
5 | uploader = EntryFileUploader.new(OpenStruct.new(model: entry, field_name: liquor_name))
6 | uploader.retrieve_from_store! value
7 | uploader.file.exists? ? uploader.url : nil
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/support/controller_macros.rb:
--------------------------------------------------------------------------------
1 | module ControllerMacros
2 |
3 | def login_user
4 | before(:each) do
5 | @request.env["devise.mapping"] = Devise.mappings[:kms_user]
6 | user = FactoryGirl.create(:user)
7 | # user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
8 | sign_in user
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/factories/fields.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :field, class: 'Kms::Field' do
3 | name 'Name'
4 | liquor_name 'name'
5 | type Kms::StringField.name
6 | factory :has_many_field do
7 | type Kms::HasManyField.name
8 | name 'Comments'
9 | liquor_name 'comments'
10 | class_name { FactoryGirl.create(:associated_model).id }
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/serializers/kms/model_serializer.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class ModelSerializer < ActiveModel::Serializer
3 | attributes :id, :kms_model_name, :collection_name, :description, :label_field, :allow_creation_using_form, :fields_attributes
4 |
5 | has_many :fields_attributes, serializer: Kms::FieldSerializer
6 |
7 | def fields_attributes
8 | object.fields.order(:position)
9 | end
10 |
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/entries/edit.html.slim:
--------------------------------------------------------------------------------
1 | .row
2 | .col-lg-12
3 | form role="form" ng-submit="update()" novalidate=""
4 | ng-include src="'entries/form.html'"
5 | .form-group
6 | label for="slug"
7 | = Kms::Entry.human_attribute_name(:slug)
8 | input.form-control type="text" ng-model="entry.slug" id="entry[slug]"
9 | button.btn.btn-default type="submit" = I18n.t(:update_entry)
10 |
--------------------------------------------------------------------------------
/app/models/kms/field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class Field < ActiveRecord::Base
3 | belongs_to :model
4 | scope :file_fields, -> { where(type: Kms::FileField.name) }
5 | scope :checkbox_fields, -> { where(type: Kms::CheckboxField.name) }
6 | scope :date_fields, -> { where(type: Kms::DateField.name) }
7 |
8 | def get_value(entry)
9 | # OVERRIDE in subclasses if needed
10 | entry.values[liquor_name]
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/models/kms/models_wrapper.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class ModelsWrapper
3 | include Liquor::Dropable
4 |
5 | def method_missing(name, *args, &block)
6 | model = Kms::Model.find_by(collection_name: name.to_s)
7 | model ? model.entries.order('position') : super
8 | end
9 |
10 | def respond_to_missing?(method_name, include_private = false)
11 | Kms::Model.where(collection_name: method_name.to_s).exists? || super
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Kms::Models::Engine.routes.draw do
2 | constraints(format: "json") do
3 | resources :models, format: true do
4 | resources :entries, format: true do
5 | member do
6 | post '' => 'entries#update'
7 | end
8 | end
9 | resources :fields, only: :update, format: true
10 | end
11 | end
12 | end
13 | Rails.application.routes.draw do
14 | post '/entries/:collection_name' => 'kms/public/entries#create'
15 | end
16 |
--------------------------------------------------------------------------------
/spec/models/kms/field_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Kms
4 | describe Field, type: :model do
5 | it { should belong_to(:model) }
6 | describe '#get_value' do
7 | it 'returns value stored in entry' do
8 | model = FactoryGirl.create(:model_with_string_field)
9 | field = model.fields.first
10 | entry = model.entries.create(values: {name: 'Test'})
11 | expect(field.get_value(entry)).to be_eql('Test')
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3 |
4 | ENGINE_ROOT = File.expand_path('../..', __FILE__)
5 | ENGINE_PATH = File.expand_path('../../lib/kms_models/engine', __FILE__)
6 |
7 | # Set up gems listed in the Gemfile.
8 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
10 |
11 | require 'rails/all'
12 | require 'rails/engine/commands'
13 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/fields/belongs_to_field.html.slim:
--------------------------------------------------------------------------------
1 | label for="{{ field.liquor_name }}"
2 | | {{ field.name }}
3 | ui-select ng-model="entry.values[field.liquor_name]" theme="bootstrap"
4 | ui-select-match placeholder=I18n.t(:belongs_to_field_placeholder)
5 | | {{ $select.selected.values[$select.selected.model.label_field] || $select.selected.id }}
6 | ui-select-choices repeat="relatedEntry.id as relatedEntry in {{ field.liquor_name }}"
7 | div ng-bind-html="relatedEntry.values[relatedEntry.model.label_field] | highlight: $select.search"
8 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/fields/has_many_field.html.slim:
--------------------------------------------------------------------------------
1 | label for="{{ field.liquor_name }}"
2 | | {{ field.name }}
3 | ui-select multiple="" ng-model="entry.values[field.liquor_name]" theme="bootstrap" on-select="addObject($item, field)" on-remove='removeObject($item, field)'
4 | ui-select-match placeholder=I18n.t(:has_many_field_placeholder) class="ui-select-match"
5 | | {{ $item.values[$item.model.label_field] || $item.id }}
6 | ui-select-choices repeat="childEntry.id as childEntry in {{ field.liquor_name }}"
7 | div ng-bind-html="childEntry.values[childEntry.model.label_field] | highlight: $select.search"
8 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Declare your gem's dependencies in kms_models.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 |
8 | # Declare any dependencies that are still in development here instead of in
9 | # your gemspec. These might include edge Rails or gems from your path or
10 | # Git. Remember to move these dependencies to your gemspec before releasing
11 | # your gem to rubygems.org.
12 | gem 'kms', github: 'webgradus/kms'
13 |
14 | # To use a debugger
15 | gem 'byebug', group: [:development, :test]
16 |
--------------------------------------------------------------------------------
/app/models/kms/model.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class Model < ActiveRecord::Base
3 | has_many :fields, class_name: 'Kms::Field', dependent: :destroy
4 | has_many :entries, class_name: 'Kms::Entry', dependent: :destroy
5 | accepts_nested_attributes_for :fields, allow_destroy: true
6 |
7 | validates :kms_model_name, :collection_name, presence: true
8 |
9 | def name
10 | id.to_s
11 | end
12 |
13 | # Hacking hack
14 | def model_name
15 | value = kms_model_name
16 | value.instance_eval do
17 | def human(options={})
18 | self
19 | end
20 | end
21 | value
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/models/kms/page_decorator.rb:
--------------------------------------------------------------------------------
1 | Kms::Page.class_eval do
2 | # fetch item by slug
3 | def fetch_item(slug)
4 | return nil unless templatable?
5 | templatable_type.constantize.find_by_slug!(slug)
6 | rescue NameError
7 | model = Kms::Model.find(templatable_type.to_i)
8 | model.entries.find_by_slug(slug)
9 | # Kms::Entry.find_by_slug(slug)
10 | end
11 |
12 | # fetch items for templatable page
13 | def fetch_items
14 | templatable_type.constantize.all
15 | rescue NameError
16 | # in templatable_type we store id of Kms::Model object in this case
17 | model = Kms::Model.find(templatable_type.to_i)
18 | model.entries
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/generators/kms_models/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | require 'rails/generators'
2 | module KmsModels
3 | class InstallGenerator < Rails::Generators::Base
4 |
5 | source_root File.expand_path('../../../../../', __FILE__)
6 |
7 | def insert_engine_routes
8 | route %(
9 | mount Kms::Models::Engine => '/kms'
10 | )
11 | end
12 |
13 | def insert_javascript
14 | append_file "app/assets/javascripts/application.js", "//= require kms_models/application\n"
15 | end
16 |
17 | def insert_stylesheet
18 | gsub_file "app/assets/stylesheets/application.css", '*/', "*= require kms_models/application\n*/"
19 | end
20 |
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/app/controllers/kms/models/fields_controller.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | module Models
3 | class FieldsController < ApplicationController
4 | wrap_parameters :field, include: [:position]
5 |
6 | def update
7 | model = Model.find(params[:model_id])
8 | @field = model.fields.find(params[:id])
9 | if @field.update(field_params)
10 | head :no_content
11 | else
12 | render json: @field.to_json(methods: :errors), status: :unprocessable_entity
13 | end
14 | end
15 |
16 | protected
17 |
18 | def field_params
19 | params.require(:field).permit(:position)
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/models/kms/has_many_field.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class HasManyField < Field
3 | def get_value(entry)
4 | entry_ids = entry.values[liquor_name]
5 | association_records = Kms::Entry.where(id: entry_ids)
6 | if entry_ids.present?
7 | # this one allows ORDER BY the IN value list like this example:
8 | # SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
9 | # ORDER BY id=1 DESC, id=3 DESC, id=2 DESC, id=4 DESC
10 | order_sql = entry_ids.map {|entry_id| "id=#{entry_id} DESC"}
11 | association_records = association_records.order(order_sql.join(','))
12 | end
13 | Liquor::DropDelegation.wrap_scope(association_records)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/fields/file_field.html.slim:
--------------------------------------------------------------------------------
1 | label for="{{ field.liquor_name }}"
2 | | {{ field.name }}
3 | div flow-init="{singleFile: true, headers: setHeaders, fileParameterName: 'entry.values[{{field.liquor_name}}]'}" flow-files-submitted="entry.values.{{field.liquor_name}} = $flow.files[0].file" flow-file-added="!!{png:1,gif:1,jpg:1,jpeg:1,doc:1,xls:1,xlsx:1,pdf:1,docx:1,mp4:1,webm:1}[$file.getExtension()]" flow-file-success="$file.msg = $message"
4 | input type="file" flow-btn="" ng-model="entry.values[field.liquor_name]"
5 | div class="thumbnail" ng-show="!$flow.files.length"
6 | img ng-src="{{entry.values[field.liquor_name].url}}"
7 | div class="thumbnail" ng-show="$flow.files.length"
8 | img flow-img="$flow.files[0]"
9 |
--------------------------------------------------------------------------------
/spec/internal/db/schema.rb:
--------------------------------------------------------------------------------
1 | ActiveRecord::Schema.define do
2 | create_table "kms_users", force: :cascade do |t|
3 | t.string "email", default: "", null: false
4 | t.string "encrypted_password", default: "", null: false
5 | t.string "reset_password_token"
6 | t.datetime "reset_password_sent_at"
7 | t.datetime "remember_created_at"
8 | t.datetime "created_at"
9 | t.datetime "updated_at"
10 | t.string "role"
11 | t.boolean "alert", default: false, null: false
12 | t.index ["email"], name: "index_kms_users_on_email", unique: true, using: :btree
13 | t.index ["reset_password_token"], name: "index_kms_users_on_reset_password_token", unique: true, using: :btree
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/models/kms/has_many_field_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Kms
4 | describe HasManyField, type: :model do
5 | describe '#get_value' do
6 | it 'returns drop scope containing association records' do
7 | model = FactoryGirl.create(:model_with_has_many_field)
8 | field = model.fields.first
9 | associated_model = Kms::Model.find(field.class_name)
10 | association_entry = associated_model.entries.create(values: {name: 'Test'})
11 | entry = model.entries.create(values: { comments: [association_entry.id] })
12 | expect(field.get_value(entry)).to be_instance_of(Kms::EntryDrop::Scope)
13 | expect(field.get_value(entry).source).to include(association_entry)
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/factories/models.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :model, class: 'Kms::Model' do
3 | kms_model_name 'Posts'
4 | collection_name 'posts'
5 | label_field 'name'
6 | factory :model_with_string_field do
7 | after(:create) do |model, evaluator|
8 | create_list(:field, 1, model: model)
9 | end
10 | factory :model_allowing_creation do
11 | allow_creation_using_form true
12 | end
13 |
14 | factory :associated_model do
15 | kms_model_name 'Comments'
16 | collection_name 'comments'
17 | end
18 | end
19 | factory :model_with_has_many_field do
20 | after(:create) do |model, evaluator|
21 | create_list(:has_many_field, 1, model: model)
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/app/assets/javascripts/kms_models/application.js:
--------------------------------------------------------------------------------
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 any plugin's vendor/assets/javascripts directory 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 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require "kms_models/application/routes"
14 | //= require_tree "../templates"
15 | //= require_tree "./application/controllers"
16 |
--------------------------------------------------------------------------------
/app/controllers/kms/public/entries_controller.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | module Public
3 | class EntriesController < ActionController::Base
4 | protect_from_forgery with: :exception
5 | before_action :find_model
6 |
7 | def create
8 | entry = @model.entries.new(values: entry_params)
9 | unless @model.allow_creation_using_form? && entry.save
10 | render json: {errors: entry.errors}.to_json, status: :unprocessable_entity
11 | end
12 | end
13 |
14 | protected
15 |
16 | def find_model
17 | @model = Model.find_by!(collection_name: params[:collection_name])
18 | end
19 |
20 | def entry_params
21 | params.require(:entry).permit(@model.fields.pluck(:liquor_name))
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/kms/models/engine.rb:
--------------------------------------------------------------------------------
1 | require "friendly_id"
2 | module Kms
3 | module Models
4 | class Engine < ::Rails::Engine
5 | engine_name 'kms_models'
6 | isolate_namespace Kms::Models
7 | config.eager_load_paths += Dir["#{config.root}/lib/**/"]
8 | config.to_prepare do
9 | Dir.glob(File.join(File.dirname(__FILE__), "../../../app/**/*_decorator*.rb")) do |c|
10 | require_dependency(c)
11 | end
12 | end
13 |
14 | initializer "kms_models.register_models_collections" do |app|
15 | app.config.after_initialize do
16 | Kms::Model.pluck(:collection_name).each do |collection_name|
17 | Kms::ModelsWrapperDrop.register_model collection_name
18 | end if Kms::Model.table_exists?
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
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 = 'KmsModels'
12 | rdoc.options << '--line-numbers'
13 | rdoc.rdoc_files.include('README.rdoc')
14 | rdoc.rdoc_files.include('lib/**/*.rb')
15 | end
16 |
17 | APP_RAKEFILE = File.expand_path("../spec/internal/Rakefile", __FILE__)
18 | load 'rails/tasks/engine.rake'
19 |
20 |
21 | load 'rails/tasks/statistics.rake'
22 |
23 |
24 |
25 | Bundler::GemHelper.install_tasks
26 |
27 | require 'rake/testtask'
28 |
29 | Rake::TestTask.new(:test) do |t|
30 | t.libs << 'lib'
31 | t.libs << 'test'
32 | t.pattern = 'test/**/*_test.rb'
33 | t.verbose = false
34 | end
35 |
36 |
37 | task default: :test
38 |
--------------------------------------------------------------------------------
/spec/controllers/kms/models/fields_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Kms
4 | module Models
5 | describe FieldsController, type: :controller do
6 | routes { Kms::Models::Engine.routes }
7 |
8 | login_user
9 | describe '#update' do
10 | it "responds with 204 status" do
11 | model = FactoryGirl.create(:model_with_string_field)
12 | put :update, params: { model_id: model.id, id: model.fields.first.id, field: { position: 1 } }, format: :json
13 | expect(response).to be_success
14 | end
15 | it "updates field's values" do
16 | model = FactoryGirl.create(:model_with_string_field)
17 | first_field = model.fields.first # position 0 here
18 | put :update, params: { model_id: model.id, id: first_field.id, field: { position: 1 } }, format: :json
19 | expect(first_field.reload.position).to be_eql(1)
20 | end
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 |
4 | require 'combustion'
5 | require 'shoulda/matchers'
6 |
7 | Combustion.initialize! :all
8 |
9 | require 'rspec/rails'
10 | require 'factory_girl_rails'
11 | require 'devise'
12 |
13 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
14 |
15 | RSpec.configure do |config|
16 | config.include Devise::Test::ControllerHelpers, type: :controller
17 | config.extend ControllerMacros, type: :controller
18 | config.include Requests::JsonHelpers, type: :controller
19 | config.color = true
20 | config.mock_with :rspec
21 | config.use_transactional_fixtures = true
22 | config.infer_base_class_for_anonymous_controllers = false
23 | end
24 | Shoulda::Matchers.configure do |config|
25 | config.integrate do |with|
26 | # Choose a test framework:
27 | with.test_framework :rspec
28 |
29 | # Or, choose the following (which implies all of the above):
30 | with.library :rails
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/kms_models/application.css:
--------------------------------------------------------------------------------
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 any plugin's vendor/assets/stylesheets directory 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 bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 | .as-sortable-dragging td {
17 | padding: 0 10px;
18 | }
19 | .widget.model-iteration-code-snippet {
20 | margin-top: 23px;
21 | }
22 | .widget.model-iteration-code-snippet .widget-icon {
23 | background: #ff5274;
24 | }
25 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/help/models_endpoints.html.slim:
--------------------------------------------------------------------------------
1 | h4 = I18n.t("liquor_help.endpoints_title")
2 | p
3 | ul
4 | li
5 | var POST /entries/:collection_name
6 | p
7 | span = I18n.t('liquor_help.endpoints.entries.post.main_description')
8 | p
9 | code ng-non-bindable=''
10 | | <form action="/entries/:collection_name" method="post">
11 | br
12 | | <input type="hidden" name='authenticity_token' value='{{ request.form_authenticity_token }}'>
13 | br
14 | | <input type="text" name="entry[name]">
15 | br
16 | | <input type="submit" value="Send">
17 | br
18 | | </form>
19 | p
20 | table.table
21 | tr
22 | th #{ I18n.t('liquor_help.parameter') } /entries/:collection_name
23 | th = I18n.t('liquor_help.description')
24 | tr
25 | td entry[:field_liquor_name]
26 | td = I18n.t('liquor_help.endpoints.entries.post.parameters.entry')
27 |
--------------------------------------------------------------------------------
/kms_models.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "kms/models/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "kms_models"
9 | s.version = Kms::Models::VERSION
10 | s.authors = ["Igor Petrov"]
11 | s.email = ["garik.piton@gmail.com"]
12 | s.homepage = "https://github.com/webgradus/kms_models"
13 | s.summary = "Extension for KMS"
14 | s.description = "KMS Models allows to define custom models on-the-fly."
15 | s.license = "MIT"
16 |
17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
18 | s.test_files = Dir["test/**/*"]
19 |
20 | s.add_dependency 'friendly_id', '~> 5.0.0'
21 | s.add_dependency 'kms', ">= 1.0.0"
22 |
23 | s.add_development_dependency 'combustion', '~> 0.5'
24 | s.add_development_dependency 'factory_girl_rails', '~> 4.8'
25 | s.add_development_dependency 'rspec-rails', '~> 3.5', '>= 3.5.0'
26 | s.add_development_dependency 'shoulda-matchers', '~> 3.1'
27 | end
28 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Igor Petrov
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.
21 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/models/index.html.slim:
--------------------------------------------------------------------------------
1 | .row ng-show="models.length"
2 | .col-lg-12
3 | .widget
4 | .widget-header
5 | i.fa.fa-tasks
6 | = Kms::Model.model_name.human(count: 1.1)
7 | a.btn.btn-sm.btn-primary.pull-right ui-sref="models.new"
8 | = I18n.t("add_model")
9 | .widget-body.no-padding
10 | .table-responsive
11 | table.table
12 | tbody
13 | tr ng-repeat="model in models"
14 | td style="width: 80%"
15 | a ui-sref="models.edit({id: model.id})"
16 | | {{ model.kms_model_name }}
17 | td
18 | .btn-group.pull-right
19 | a.btn.btn-sm.btn-danger ng-click="destroy(model)" ng-show="currentUser.admin"
20 | i.fa.fa-times
21 | .row ng-show="!models.length"
22 | .center-block
23 | .jumbotron.vertical-center.text-center
24 | .container
25 | h1
26 | span.fa-stack.fa-lg
27 | i.fa.fa-circle.fa-stack-2x
28 | i.fa.fa-tasks.fa-stack-1x
29 | p = I18n.t(:models_description)
30 | p
31 | a.btn.btn-primary.btn-lg ui-sref="models.new" role="button" = I18n.t(:create_first_model)
32 |
--------------------------------------------------------------------------------
/app/serializers/kms/entry_serializer.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class EntrySerializer < ActiveModel::Serializer
3 | attributes :id, :values, :position, :slug
4 |
5 | has_one :model, serializer: Kms::SimpleModelSerializer
6 |
7 | # OPTIMIZE
8 | def values
9 | values_with_urls = object.values.dup
10 | # prepare file fields
11 | object.model.fields.file_fields.each do |file_field|
12 | uploader = EntryFileUploader.new(OpenStruct.new(model: object, field_name: file_field.liquor_name))
13 | uploader.retrieve_from_store! values_with_urls[file_field.liquor_name]
14 | values_with_urls[file_field.liquor_name] = { url: uploader.file.exists? ? uploader.url : nil }
15 | end
16 | # prepare checkbox fields - cause PostgreSQL json stored as strings
17 | object.model.fields.checkbox_fields.each do |checkbox_field|
18 | values_with_urls[checkbox_field.liquor_name] = values_with_urls[checkbox_field.liquor_name] == 'true'
19 | end
20 | # prepare checkbox fields - cause PostgreSQL json stored as strings
21 | object.model.fields.date_fields.each do |date_field|
22 | values_with_urls[date_field.liquor_name] = Date.parse(values_with_urls[date_field.liquor_name])
23 | end
24 | values_with_urls
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/controllers/kms/public/entries_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Kms
4 | module Public
5 | describe EntriesController, type: :controller do
6 | describe '#create' do
7 | let(:model) { FactoryGirl.create(:model) }
8 | context 'when no model with provided collection name' do
9 | it 'returns 404' do
10 | expect { post :create, params: { collection_name: 'oops' }, format: :json }.to raise_exception(ActiveRecord::RecordNotFound)
11 | end
12 | end
13 | context 'when creation using forms allowed' do
14 | let(:model) { FactoryGirl.create(:model_allowing_creation) }
15 | it 'returns 204 status' do
16 | post :create, params: { collection_name: model.collection_name, entry: { name: 'Test' } }, format: :json
17 | expect(response).to have_http_status(204)
18 | expect(Kms::Entry.last.values.symbolize_keys).to include({ name: 'Test' })
19 | end
20 | end
21 | context 'when creation using forms not allowed' do
22 | it 'returns 422 status' do
23 | post :create, params: { collection_name: model.collection_name, entry: { name: 'Test' } }, format: :json
24 | expect(response.unprocessable?).to be true
25 | end
26 | end
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## KMS Models
2 |
3 | [](https://travis-ci.org/apiqcms/kms_models)
4 | [](https://codeclimate.com/github/apiqcms/kms_models)
5 |
6 | This extension adds "Models" section in [KMS](https://github.com/apiqcms/kms) and allows to define custom models on-the-fly. Supported fields for definition in Model: String, Text, Checkbox, File, HasMany, BelongsTo. Note that this extension requires at least PostgreSQL 9.2 because of JSON column type.
7 |
8 | ## Installation
9 |
10 | 1. Add to Gemfile
11 |
12 | gem "kms_models"
13 | # or for edge version:
14 | gem "kms_models", github: "webgradus/kms_models"
15 |
16 | 2. Run generator:
17 |
18 | rails g kms_models:install
19 |
20 | 3. Copy migrations:
21 |
22 | rails kms_models:install:migrations
23 |
24 | 4. Migrate:
25 |
26 | bundle exec rails db:migrate
27 |
28 | 5. Recompile assets:
29 |
30 | bundle exec rails assets:precompile
31 |
32 | 6. Restart KMS instance
33 |
34 | ## Getting started
35 | Please watch this video to start using KMS Models:
36 |
37 | [](https://youtu.be/_INzPDZimsA "Getting started with KMS Models extension")
38 |
39 | ## Contributing
40 |
41 | Please follow [CONTRIBUTING.md](CONTRIBUTING.md).
42 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/entries/index.html.slim:
--------------------------------------------------------------------------------
1 | .row ng-show="entries.length"
2 | .col-lg-12
3 | .widget
4 | .widget-header
5 | i.fa.fa-list
6 | | {{ model.description || model.kms_model_name }}
7 | a.btn.btn-sm.btn-primary.pull-right ui-sref="models.entries.new({modelId: model.id})"
8 | = I18n.t("add_entry")
9 | .widget-body.no-padding
10 | .table-responsive
11 | table.table
12 | tbody as-sortable="entriesSortableOptions" ng-model="entries"
13 | tr ng-repeat="entry in entries" as-sortable-item=""
14 | td style="width: 80%"
15 | i.fa.fa-bars as-sortable-item-handle=""
16 | a ui-sref="models.entries.edit({modelId: model.id, id: entry.id})"
17 | | {{ entry.values[model.label_field] || entry.id }}
18 | td
19 | .btn-group.pull-right
20 | a.btn.btn-sm.btn-danger ng-click="destroy(entry)"
21 | i.fa.fa-times
22 | .row ng-show="!entries.length"
23 | .center-block
24 | .jumbotron.vertical-center.text-center
25 | .container
26 | h1
27 | span.fa-stack.fa-lg
28 | i.fa.fa-circle.fa-stack-2x
29 | i.fa.fa-list.fa-stack-1x
30 | p = I18n.t(:entries_description)
31 | p
32 | a.btn.btn-primary.btn-lg ui-sref="models.entries.new({modelId: model.id})" role="button" = I18n.t(:create_first_entry)
33 |
--------------------------------------------------------------------------------
/app/controllers/kms/models/entries_controller.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | module Models
3 | class EntriesController < ApplicationController
4 | def index
5 | model = Model.find(params[:model_id])
6 | @entries = model.entries.order('position')
7 | render json: @entries, root: false
8 | end
9 |
10 | def show
11 | model = Model.find(params[:model_id])
12 | @entry = model.entries.find(params[:id])
13 | render json: @entry, root: false
14 | end
15 |
16 | def create
17 | model = Model.find(params[:model_id])
18 | @entry = model.entries.new(entry_params)
19 | if @entry.save
20 | render json: @entry, root: false
21 | else
22 | render json: { errors: @entry.errors.full_messages }.to_json, status: :unprocessable_entity
23 | end
24 | end
25 |
26 | def update
27 | model = Model.find(params[:model_id])
28 | @entry = model.entries.find(params[:id])
29 | if @entry.update_attributes(entry_params)
30 | render json: @entry, root: false
31 | else
32 | render json: { errors: @entry.errors.full_messages }.to_json, status: :unprocessable_entity
33 | end
34 | end
35 |
36 | def destroy
37 | @entry = Entry.find(params[:id])
38 | @entry.destroy
39 | render json: @entry, root: false
40 | end
41 |
42 | protected
43 |
44 | def entry_params
45 | params.require(:entry).permit!
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/app/controllers/kms/models/models_controller.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | module Models
3 | class ModelsController < ApplicationController
4 | wrap_parameters :model, include: [:kms_model_name, :collection_name, :description, :label_field, :fields_attributes, :allow_creation_using_form]
5 |
6 | def index
7 | render json: Model.all, root: false
8 | end
9 |
10 | def show
11 | @model = Model.find(params[:id])
12 | render json: @model, root: false
13 | end
14 |
15 | def create
16 | @model = Model.new(model_params)
17 | if @model.save
18 | Kms::ResourceService.register(:models, @model, "fa-tasks")
19 | Kms::ModelsWrapperDrop.register_model @model.collection_name
20 | else
21 | render json: { errors: @model.errors.full_messages }.to_json, status: :unprocessable_entity
22 | end
23 | end
24 |
25 | def update
26 | @model = Model.find(params[:id])
27 | unless @model.update_attributes(model_params)
28 | render json: { errors: @model.errors.full_messages }.to_json, status: :unprocessable_entity
29 | end
30 | end
31 |
32 | def destroy
33 | @model = Model.find(params[:id])
34 | @model.destroy
35 | Kms::ResourceService.unregister(:models, @model)
36 | end
37 |
38 | protected
39 |
40 | def model_params
41 | params.require(:model).permit(:kms_model_name, :collection_name, :description, :label_field, :allow_creation_using_form, fields_attributes: [:id, :name, :liquor_name, :type, :class_name, :_destroy])
42 | end
43 |
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/app/uploaders/entry_file_uploader.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | class EntryFileUploader < CarrierWave::Uploader::Base
4 |
5 | # Include RMagick or MiniMagick support:
6 | # include CarrierWave::RMagick
7 | # include CarrierWave::MiniMagick
8 |
9 | # Choose what kind of storage to use for this uploader:
10 | storage (ENV['KMS_ASSETS_STORAGE'] && ENV['KMS_ASSETS_STORAGE'].to_sym) || :file
11 |
12 | # Override the directory where uploaded files will be stored.
13 | # This is a sensible default for uploaders that are meant to be mounted:
14 | def store_dir
15 | "uploads/#{model.model.class.to_s.underscore}/#{model.model.id}/#{model.field_name}"
16 | end
17 |
18 | # Provide a default URL as a default if there hasn't been a file uploaded:
19 | # def default_url
20 | # # For Rails 3.1+ asset pipeline compatibility:
21 | # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
22 | #
23 | # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
24 | # end
25 |
26 | # Process files as they are uploaded:
27 | # process :scale => [200, 300]
28 | #
29 | # def scale(width, height)
30 | # # do something
31 | # end
32 |
33 | # Create different versions of your uploaded files:
34 | # version :thumb do
35 | # process :resize_to_fit => [50, 50]
36 | # end
37 |
38 | # Add a white list of extensions which are allowed to be uploaded.
39 | # For images you might use something like this:
40 | # def extension_white_list
41 | # %w(jpg jpeg gif png)
42 | # end
43 |
44 | # Override the filename of the uploaded files:
45 | # Avoid using model.id or version_name here, see uploader/store.rb for details.
46 | # def filename
47 | # "something.jpg" if original_filename
48 | # end
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/app/assets/javascripts/kms_models/application/controllers/models_controller.coffee.erb:
--------------------------------------------------------------------------------
1 | ModelsController = ($scope, $state, Restangular, $stateParams, Alertify, ErrorsService, TransliterationService) ->
2 | $scope.store = Restangular.all('models')
3 |
4 | Restangular.all('users').customGET('kms_user').then (current_user) ->
5 | $scope.currentUser = current_user
6 | $scope.currentUser.admin = $scope.currentUser.role == 'admin'
7 |
8 | $scope.store.getList().then (models)->
9 | $scope.models = models
10 |
11 | if $stateParams.id
12 | $scope.store.get($stateParams.id).then (model)->
13 | $scope.model = model
14 | else
15 | $scope.model = {fields_attributes: []}
16 |
17 | $scope.$watchCollection 'model.fields_attributes', (newFields, oldFields) ->
18 | if newFields and newFields.length > 0 and oldFields and oldFields.length == 0
19 | $scope.model.label_field =newFields[0].liquor_name
20 |
21 | $scope.$watch 'model.kms_model_name', (newValue, oldValue) ->
22 | if newValue? and !$scope.model.id
23 | $scope.model.collection_name = _.snakeCase TransliterationService.translit(newValue, 5).replace(/`/g, '')
24 |
25 | $scope.create = ->
26 | $scope.store.post($scope.model).then ->
27 | # for adding to Menu - better to render resources via js
28 | window.location.reload()
29 | #$state.go('models')
30 | ,(response)->
31 | Alertify.error(ErrorsService.prepareErrorsString(response.data.errors))
32 |
33 | $scope.update = ->
34 | $scope.model.put().then ->
35 | $state.go('models')
36 | ,(response)->
37 | Alertify.error(ErrorsService.prepareErrorsString(response.data.errors))
38 |
39 | $scope.destroy = (model)->
40 | if confirm('<%= I18n.t(:are_you_sure) %>')
41 | model.remove().then ->
42 | window.location.reload()
43 |
44 |
45 | angular.module('KMS')
46 | .controller('ModelsController', ['$scope', '$state', 'Restangular', '$stateParams', 'Alertify', 'ErrorsService', 'TransliterationService', ModelsController])
47 |
--------------------------------------------------------------------------------
/app/models/kms/entry.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class Entry < ActiveRecord::Base
3 | include Liquor::Dropable
4 | extend ::FriendlyId
5 | include CompileTemplates
6 |
7 | friendly_id :slug_candidates, use: :slugged
8 | belongs_to :model
9 | after_save :store_files, if: :cache_names_present?
10 |
11 | validates :slug, uniqueness: { scope: :model_id }
12 |
13 | attr_reader :cache_names
14 |
15 | def slug_candidates
16 | [values[model.label_field]]
17 | end
18 |
19 | def permalink
20 | templatable_page = Kms::Page.where(templatable_type: model.name).first
21 | return nil unless templatable_page
22 | Pathname.new(templatable_page.parent.fullpath).join(slug.to_s).to_s
23 | end
24 |
25 | def values
26 | read_attribute(:values) || {}
27 | end
28 |
29 | def values=(new_values)
30 | files_params = new_values.select { |_, v| v.is_a?(ActionDispatch::Http::UploadedFile) || v.is_a?(File) }
31 | @cache_names ||= {}
32 | files_params.each do |k, v|
33 | uploader = EntryFileUploader.new(OpenStruct.new(model: self, field_name: k))
34 | uploader.cache!(v)
35 | @cache_names[k] = uploader.cache_name
36 | files_params[k] = v.original_filename
37 | end
38 | super values.merge new_values.merge(files_params)
39 | end
40 |
41 | def method_missing(name, *args, &block)
42 | model.fields.where(liquor_name: name.to_s).exists? ? values[name.to_s] : super
43 | end
44 |
45 | def respond_to_missing?(method_name, include_private = false)
46 | model.fields.where(liquor_name: method_name.to_s).exists? || super
47 | end
48 |
49 | protected
50 |
51 | def cache_names_present?
52 | cache_names.present?
53 | end
54 |
55 | def store_files
56 | values.each do |k, v|
57 | next unless cache_names.keys.include? k
58 | uploader = EntryFileUploader.new(OpenStruct.new(model: self, field_name: k))
59 | uploader.retrieve_from_cache!(cache_names[k])
60 | uploader.store!
61 | end
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/models/form.html.slim:
--------------------------------------------------------------------------------
1 | .row
2 | .col-md-6.col-sm-12
3 | .form-group
4 | label for="kms_model_name" = Kms::Model.human_attribute_name(:kms_model_name)
5 | input#kms_model_name.form-control type="text" ng-model="model.kms_model_name"
6 | .form-group
7 | label for="collection_name" = Kms::Model.human_attribute_name(:collection_name)
8 | input#collection_name.form-control type="text" ng-model="model.collection_name"
9 | small.help-block = I18n.t(:collection_name_field_hint)
10 | .form-group
11 | label for="description" = Kms::Model.human_attribute_name(:description)
12 | textarea#description.form-control ng-model="model.description"
13 | small.help-block = I18n.t(:description_field_hint)
14 | .form-group
15 | label for="label_field" = Kms::Model.human_attribute_name(:label_field)
16 | select#label_field.form-control ng-model="model.label_field" ng-options="field.liquor_name as field.name for field in model.fields_attributes"
17 | small.help-block = I18n.t(:label_field_hint)
18 | .form-group
19 | label for="allow_creation_using_form" style="margin-right:10px;"
20 | = Kms::Model.human_attribute_name(:allow_creation_using_form)
21 | toggle-switch ng-model="model.allow_creation_using_form" on-label=I18n.t(:yes_word) off-label=I18n.t(:no_word)
22 | small.help-block = I18n.t(:allow_creation_using_form_field_hint)
23 | .col-md-6.col-sm-12
24 | .widget.model-iteration-code-snippet
25 | .widget-body
26 | .widget-content
27 | .widget-icon.pull-left
28 | i class="fa fa-clipboard"
29 | .title Model iteration hint
30 | .comment Use sample code below for iterating your model entries in Template, Page or Snippet:
31 | br
32 | pre
33 | | {% for element in: models.{{ model.collection_name }} do: %}
34 | br
35 | code ng-non-bindable=""
36 | | <p>{{
37 | | element.{{ model.label_field }}
38 | code ng-non-bindable=""
39 | | }}</p>
40 | br
41 | | {% end for %}
42 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/models/fields.html.slim:
--------------------------------------------------------------------------------
1 | .form-group ng-controller="FieldsController"
2 | table.table
3 | thead
4 | tr
5 | th colspan="4"
6 | h4 = Kms::Model.human_attribute_name(:fields)
7 | tbody as-sortable="fieldsSortableOptions" ng-model="model.fields_attributes"
8 | tr ng-repeat="field in model.fields_attributes" ng-hide="field._destroy" as-sortable-item=""
9 | td
10 | i.fa.fa-bars as-sortable-item-handle=""
11 | | {{ field.name }}
12 | td
13 | | {{ field.liquor_name }}
14 | td
15 | i.fa ng-class="findTypeByField(field).icon"
16 | | {{ formatType(field) }}
17 | td
18 | | {{ field.required }}
19 | td
20 | .btn-group.pull-right
21 | a.btn.btn-sm.btn-danger ng-click="removeField(field)"
22 | i.fa.fa-times
23 | tr ng-hide="model.fields_attributes.length > 0"
24 | td
25 | i = I18n.t(:no_fields_yet)
26 | .row
27 | .col-lg-3
28 | input.form-control type="text" required="" placeholder=Kms::Field.human_attribute_name(:name) ng-model="field.name"
29 | .col-lg-3
30 | input.form-control type="text" required="" placeholder=Kms::Field.human_attribute_name(:liquor_name) ng-model="field.liquor_name"
31 | .col-lg-2
32 | ui-select ng-model="field.type" theme="bootstrap"
33 | ui-select-match placeholder=I18n.t(:select_field_type)
34 | i.fa ng-class="$select.selected.icon"
35 | | {{ $select.selected.name }}
36 | ui-select-choices repeat="type.id as type in types"
37 | i.fa ng-class="type.icon"
38 | | {{ type.name | highlight: $select.search }}
39 | .col-lg-2 ng-show="isAssociationField(field)"
40 | select#field_class_name.form-control ng-model="field.class_name" ng-options="templatable_type.type as templatable_type.title for templatable_type in templatable_types" required="" placeholder=Kms::Field.human_attribute_name(:class_name)
41 | option value="" disabled="" selected="" = I18n.t(:select_model)
42 | .col-lg-2
43 | a.btn.btn-small.btn-primary ng-click="addField()"
44 | i.fa.fa-plus
45 |
--------------------------------------------------------------------------------
/app/assets/javascripts/kms_models/application/routes.coffee.erb:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | angular.module('KMS').config ['$stateProvider', '$urlRouterProvider', ($stateProvider, $urlRouterProvider) ->
4 |
5 | # Application routes
6 | $stateProvider
7 | .state('models', {
8 | url: '/kms/models',
9 | views:
10 | "header":
11 | template: "<%= Kms::Model.model_name.human(count: 1.1) %>"
12 | "@":
13 | controller: 'ModelsController',
14 | controllerAs: 'models',
15 | templateUrl: 'models/index.html',
16 | })
17 | .state('models.new', {
18 | url: '/new',
19 | views:
20 | "header@":
21 | template: "<%= I18n.t(:new_model) %>"
22 | "@":
23 | controller: 'ModelsController',
24 | controllerAs: 'models',
25 | templateUrl: 'models/new.html',
26 | })
27 | .state('models.edit', {
28 | url: '/:id/edit',
29 | views:
30 | "header@":
31 | template: "<%= I18n.t(:edit_model) %>"
32 | "@":
33 | controller: 'ModelsController',
34 | controllerAs: 'models',
35 | templateUrl: 'models/edit.html',
36 | })
37 | .state('models.entries', {
38 | url: '/:modelId/entries',
39 | views:
40 | "header@":
41 | template: "<%= Kms::Entry.model_name.human(count: 1.1) %>"
42 | "@":
43 | controller: 'EntriesController',
44 | controllerAs: 'entries',
45 | templateUrl: 'entries/index.html',
46 | })
47 | .state('models.entries.new', {
48 | url: '/new',
49 | views:
50 | "header@":
51 | template: "<%= I18n.t(:new_entry) %>"
52 | "@":
53 | controller: 'EntriesController',
54 | controllerAs: 'entries',
55 | templateUrl: 'entries/new.html'
56 | })
57 | .state('models.entries.edit', {
58 | url: '/:id/edit',
59 | views:
60 | "header@":
61 | template: "<%= I18n.t(:edit_entry) %>"
62 | "@":
63 | controller: 'EntriesController',
64 | controllerAs: 'entries',
65 | templateUrl: 'entries/edit.html'
66 | })
67 | ]
68 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Version 1.1.0 2018-02-09
2 |
3 | * [added] add Date Field - closes #5
4 | * [added] add description to Models - closes #6
5 | * [added] add small UI element - model iteration hint
6 | * [changed] better error messages + hint for Label Field
7 | * [changed] use FontAwesome icons for identifying different field types
8 | * [fixed] use current locale for CKEditor - closes #17
9 | * [fixed] correct processing for CheckboxFields - closes #11
10 |
11 | ## Version 1.0.1 2017-08-09
12 |
13 | * [fixed] fix "uninitialized constant Rails::Generators::Base"
14 |
15 | ## Version 1.0.0 2017-08-02
16 |
17 | * [fixed] find_by and find_all_by now can find results not only by dynamic fields but also by id, slug, position and etc.
18 | * [added] add slug attribute to entry drop
19 | * [added] support Amazon S3 storage - closes #14
20 | * [added] add blank state screens for models and entries
21 | * [added] automatic Model's collection_name and Fields liquor_name generating - closes #16
22 | * [changed] replace JSON column with JSONB - closes #15
23 | * [added] Sorting (by dran'n'drop) Model's fields - closes #4
24 | * [fixed] fetch item only in scope of model - page's templatable type
25 |
26 | ## Version 0.8.0 2017-03-23
27 |
28 | * [fixed] fix incorrect url for file field when no value was stored
29 | * [changed] preserve order of has_many field values - closes #9
30 | * [added] Rspec setup
31 | * [added] add feature allowing model elements creation using website forms
32 |
33 | ## Version 0.7.0 2016-12-05
34 |
35 | * [fixed] fix bug when running kms_models install generator
36 | * [changed] Rails 5 support
37 |
38 | ## Version 0.6.0 2016-10-01
39 |
40 | * [changed] allow to edit Entry slug, but autogenerate on creation
41 | * [added] English translations
42 |
43 | ## Version 0.5.0 2016-02-09
44 |
45 | * [fixed] fix saving unchecked checkbox
46 | * [fixed] element updating not working when some field are empty
47 | * [changed] allow to pass File object when storing values for File Fields
48 | * [changed] allow more file types for file fields - doc, xls, pdf, mp4, webm (previously only images allowed)
49 | * [changed] alert about errors using Alertify
50 | * [added] add ability to order model entries - using `order` method
51 | * [added] register model in ModelDrop after creation - no need in app restart
52 | * [added] Collection name method added - `item.model_collection_name`
53 | * [added] expose entry id field in liquor
54 | * [added] add confirmation before Model deleting
55 | * [added] added BelongsTo field for Models (similar to Rails belongs_to)
56 | * [added] support searching/filtering model entries with 'find_all_by', 'find_by'
57 |
--------------------------------------------------------------------------------
/lib/drops/kms/entry_drop.rb:
--------------------------------------------------------------------------------
1 | module Kms
2 | class EntryDrop < Liquor::Drop
3 |
4 | attributes :id, :created_at, :slug, :permalink, :model_collection_name
5 | scopes :order
6 |
7 | # overriding methods cause we work with 'json' column
8 | class Scope
9 | def find_by(_, fields={})
10 | fields, = Liquor::Drop.unwrap_scope_arguments([ fields ])
11 |
12 | plain_fields, json_fields = fields_partition(fields)
13 | result = @source.where(fields_query(fields), *(json_fields.values + plain_fields.values).map(&:to_s)).first
14 | Liquor::DropDelegation.wrap_element result if result
15 | end
16 |
17 | def find_all_by(_, fields={})
18 | fields, = Liquor::Drop.unwrap_scope_arguments([ fields ])
19 |
20 | plain_fields, json_fields = fields_partition(fields)
21 | result = @source.where(fields_query(fields), *(json_fields.values + plain_fields.values).map(&:to_s))
22 | Liquor::DropDelegation.wrap_scope(result)
23 | end
24 |
25 | def order(*args)
26 | args = Liquor::Drop.unwrap_scope_arguments(args)
27 | parsed_args = args.map do |arg|
28 | order_clause = arg.split(' ')
29 | if order_clause[0].in? Kms::Entry.column_names - ['values']
30 | arg
31 | else
32 | ["values ->> '#{order_clause[0]}'", order_clause[1].to_s].join(' ')
33 | end
34 | end
35 | # we use reorder because by default we order by position
36 | Liquor::DropDelegation.wrap_scope @source.reorder(*parsed_args)
37 | end
38 |
39 | private
40 |
41 | def fields_partition(fields)
42 | fields.partition {|name, _| (Kms::Entry.column_names - ['values']).include? name.to_s}.map(&:to_h)
43 | end
44 |
45 | def fields_query(fields)
46 | plain_fields, json_fields = fields_partition(fields)
47 | json_fields_query = json_fields.map {|name, _| "values ->> '#{name}' = ?" }.join(" AND ")
48 | plain_fields_query = plain_fields.map {|name, _| "#{name} = ?"}.join(" AND ")
49 | [json_fields_query, plain_fields_query].reject(&:empty?).join(' OR ')
50 | end
51 |
52 | end
53 |
54 | def initialize(source)
55 | self.class.instance_eval do
56 | source.model.fields.pluck(:liquor_name).each do |field_name|
57 | export field_name.to_sym
58 | end
59 | end
60 | super(source)
61 | end
62 |
63 | def method_missing(name, *args, &block)
64 | field = source.model.fields.find_by(liquor_name: name.to_s)
65 | field ? field.get_value(source) : super
66 | end
67 |
68 | def respond_to_missing?(method_name, include_private = false)
69 | source.model.fields.where(liquor_name: method_name.to_s).exists? || super
70 | end
71 |
72 | def created_at
73 | source.created_at.to_s
74 | end
75 |
76 | def model_collection_name
77 | source.model.collection_name
78 | end
79 |
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/app/assets/javascripts/kms_models/application/controllers/fields_controller.coffee.erb:
--------------------------------------------------------------------------------
1 | FieldsController = ($scope, $state, Restangular, $stateParams, TransliterationService) ->
2 | $scope.types = [
3 | { id: 'Kms::StringField', name: "<%= I18n.t("field_types.string") %>", icon: 'fa-font'},
4 | { id: 'Kms::TextField', name: "<%= I18n.t("field_types.text") %>", icon: 'fa-file-text-o'},
5 | { id: 'Kms::CheckboxField', name: "<%= I18n.t("field_types.checkbox") %>", icon: 'fa-check-square-o'},
6 | { id: 'Kms::DateField', name: "<%= I18n.t("field_types.date") %>", icon: 'fa-calendar'},
7 | { id: 'Kms::FileField', name: "<%= I18n.t("field_types.file") %>", icon: 'fa-image'},
8 | { id: 'Kms::HasManyField', name: "<%= I18n.t("field_types.has_many") %>", icon: 'fa-sitemap'},
9 | { id: 'Kms::BelongsToField', name: "<%= I18n.t("field_types.belongs_to") %>", icon: 'fa-leaf'},
10 | ]
11 |
12 | $scope.fieldsSortableOptions =
13 | orderChanged: (event)->
14 | for field, index in event.dest.sortableScope.modelValue
15 | field_copy =
16 | id: field.id
17 | position: index
18 | Restangular.restangularizeElement($scope.model, field_copy, 'fields').put()
19 |
20 | Restangular.all('resources').getList().then (templatable_types)->
21 | $scope.templatable_types = templatable_types
22 |
23 | Restangular.all('users').customGET('kms_user').then (current_user) ->
24 | $scope.currentUser = current_user
25 | $scope.currentUser.admin = $scope.currentUser.role == 'admin'
26 |
27 | $scope.field = {}
28 |
29 | $scope.$watch 'field.name', (newValue, oldValue) ->
30 | if newValue? and !$scope.field.id
31 | $scope.field.liquor_name = _.snakeCase TransliterationService.translit(newValue, 5).replace(/`/g, '')
32 |
33 | $scope.findTypeByField = (field) ->
34 | _.find $scope.types, (type) -> type.id == field.type
35 |
36 | $scope.formatType = (field) ->
37 | fieldType = $scope.findTypeByField(field)
38 | if $scope.isAssociationField(field) then "#{fieldType.name} (#{$scope.getDisplayableTemplatableType(field)})" else fieldType.name
39 |
40 | $scope.getDisplayableTemplatableType = (field)->
41 | templatable_type = _.find $scope.templatable_types, (templatable_type) -> templatable_type.type == field.class_name
42 | templatable_type.title
43 |
44 | $scope.isAssociationField = (field)->
45 | field.type == 'Kms::HasManyField' or field.type == 'Kms::BelongsToField'
46 |
47 | $scope.isValidField = ->
48 | $scope.field.name and $scope.field.liquor_name and $scope.field.type
49 |
50 | $scope.addField = ->
51 | if $scope.isValidField()
52 | $scope.model.fields_attributes.push($scope.field)
53 | $scope.field = {}
54 |
55 | $scope.removeField = (field)->
56 | field['_destroy'] = '1' # for rails deletion
57 | #$scope.model.fields = _.without($scope.model.fields, field)
58 |
59 |
60 | angular.module('KMS')
61 | .controller('FieldsController', ['$scope', '$state', 'Restangular', '$stateParams', 'TransliterationService', FieldsController])
62 |
--------------------------------------------------------------------------------
/spec/controllers/kms/models/models_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Kms
4 | module Models
5 | describe ModelsController, type: :controller do
6 | routes { Kms::Models::Engine.routes }
7 |
8 | login_user
9 | describe '#index' do
10 | it 'returns models json representation' do
11 | model = FactoryGirl.create(:model)
12 | get :index, format: :json
13 |
14 | expect(response).to be_success
15 | expect(json.length).to eq(1)
16 | expect(json[0].keys).to include('id', 'collection_name', 'kms_model_name', 'label_field', 'allow_creation_using_form', 'fields_attributes')
17 | end
18 | end
19 | describe '#show' do
20 | it 'returns model json representation' do
21 | model = FactoryGirl.create(:model)
22 | get :show, params: { id: model.id }, format: :json
23 | expect(response).to be_success
24 | expect(json.keys).to include('id', 'collection_name', 'kms_model_name', 'label_field', 'allow_creation_using_form', 'fields_attributes')
25 | end
26 | end
27 | describe '#create' do
28 | context 'when validation failed' do
29 | it "returns errors" do
30 | post :create, params: { model: { kms_model_name: '' } }, format: :json
31 | expect(response.unprocessable?).to be true
32 | expect(json['errors']).to_not be nil
33 | end
34 | end
35 | context 'when valid' do
36 | it "returns no content" do
37 | attributes = { kms_model_name: 'Posts', collection_name: 'posts', description: 'Posts', allow_creation_using_form: true }
38 | post :create, params: { model: attributes }, format: :json
39 | expect(response).to have_http_status(204)
40 | expect(Kms::Model.last.attributes.symbolize_keys).to include attributes
41 | end
42 | end
43 | end
44 | describe '#update' do
45 | context 'when validation failed' do
46 | it "returns errors" do
47 | model = FactoryGirl.create(:model)
48 | put :update, params: { id: model.id, model: { kms_model_name: '' } }, format: :json
49 | expect(response.unprocessable?).to be true
50 | expect(json['errors']).to_not be nil
51 | end
52 | end
53 | context 'when valid' do
54 | it "returns no content" do
55 | model = FactoryGirl.create(:model)
56 | new_attributes = { kms_model_name: 'Comments', collection_name: 'comments', allow_creation_using_form: true }
57 | put :update, params: { id: model.id, model: new_attributes }, format: :json
58 | expect(response).to have_http_status(204)
59 | expect(Kms::Model.last.attributes.symbolize_keys).to include new_attributes
60 | end
61 | end
62 | end
63 | describe '#destroy' do
64 | it 'destroys model and returns no content' do
65 | model = FactoryGirl.create(:model)
66 | expect{delete :destroy, params: { id: model.id }, format: :json}.to change{Kms::Model.count}.by(-1)
67 | expect(response).to have_http_status(204)
68 | end
69 | end
70 |
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | models: "Models"
3 | add_model: "Add Model"
4 | new_model: "New Model"
5 | edit_model: "Edit Model"
6 | update_model: "Update Model"
7 | add_entry: "Add Entry"
8 | new_entry: "New Entry"
9 | edit_entry: "Edit Entry"
10 | update_entry: "Update Entry"
11 | no_fields_yet: "No fields yet. Add first field using form below."
12 | select_field_type: "Select field type"
13 | select_model: "Select Model"
14 | collection_name_field_hint: "You can access model collection like this: models.your_collection_name (ex., models.services)"
15 | description_field_hint: "Optional field. Just write some description so everyone would be aware what's the purpose of this Model"
16 | label_field_hint: "Add at least one Field below. And then you could choose one that would be used for item permalink generation and entries list displaying"
17 | allow_creation_using_form_field_hint: "On website you can place a form allowing to create model entries"
18 | has_many_field_placeholder: "Select related objects..."
19 | belongs_to_field_placeholder: "Select related object..."
20 | models_description: "Models are where you organize your content (basically dynamic) like 'News', 'Blog', 'Services' and etc."
21 | create_first_model: "Create first model"
22 | entries_description: "Here you would create content for corresponding Model"
23 | create_first_entry: "Create first entry"
24 | field_types:
25 | string: "String"
26 | text: "Text"
27 | checkbox: "Checkbox"
28 | date: "Date"
29 | file: "File"
30 | has_many: "Has many"
31 | belongs_to: "Belongs to"
32 | liquor_help:
33 | variables:
34 | models:
35 | main_description: 'Variable "models" gives access to models collections of entries. You can access collection using model collection name - "Collection name (for Liquor)" field. For example, if you created some Model with "services" collection name, then you could iterate its collection using "for" tag like this:'
36 | endpoints:
37 | entries:
38 | post:
39 | main_description: This endpoint allows you to setup form submitting Model's elements. For example, if you have a model with collection name "posts" (and field with "title" Liquor name), you could create a form with action="/entries/posts" and an input with name="entry[title]"
40 | parameters:
41 | entry: Value of each parameter will be saved to corresponding Model's entry field
42 |
43 | activerecord:
44 | models:
45 | kms/model:
46 | one: "Model"
47 | few: "Models"
48 | many: "Models"
49 | other: "Models"
50 | kms/entry:
51 | one: "Entry"
52 | few: "Entries"
53 | many: "Entries"
54 | other: "Entries"
55 | attributes:
56 | kms/model:
57 | kms_model_name: "Name"
58 | collection_name: "Collection name (for Liquor)"
59 | description: "Description"
60 | fields: "Fields"
61 | label_field: "Label field (used for URL/slug generating)"
62 | allow_creation_using_form: "Allow creation using form"
63 | kms/field:
64 | name: "Name"
65 | liquor_name: "Name for Liquor"
66 | type: "Type"
67 | required: "Required"
68 | kms/entry:
69 | slug: "Slug"
70 |
--------------------------------------------------------------------------------
/config/locales/ru.yml:
--------------------------------------------------------------------------------
1 | ru:
2 | models: "Модели"
3 | add_model: "Добавить модель"
4 | new_model: "Новая Модель"
5 | edit_model: "Редактирование модели"
6 | update_model: "Обновить модель"
7 | add_entry: "Добавить элемент"
8 | new_entry: "Новый Элемент"
9 | edit_entry: "Редактирование Элемента"
10 | update_entry: "Обновить элемент"
11 | no_fields_yet: "Пока нет ни одного свойства. Добавьте первое свойство с помощью формы ниже."
12 | select_field_type: "Выберите тип свойства"
13 | select_model: "Выберите модель"
14 | collection_name_field_hint: "К коллекции можно будет обратиться так: models.your_collection_name (напр., models.services)"
15 | description_field_hint: "Необязательное поле. Напишите небольшое описание, чтобы было понятно назначение данной Модели"
16 | label_field_hint: "Добавьте хотя бы одно Свойство ниже. И затем вы можете выбрать свойство для генерации ссылки на элемент, а так же для отображения элементов в списке"
17 | allow_creation_using_form_field_hint: "На сайте можно будет разместить форму для создания элементов модели"
18 | has_many_field_placeholder: "Выберите связанные объекты..."
19 | belongs_to_field_placeholder: "Выберите связанный объект..."
20 | models_description: "Модели - это ваш динамический контент. Например, 'Новости', 'Блог', 'Услуги' и тд."
21 | create_first_model: "Создать первую модель"
22 | entries_description: "Здесь вы можете создавать элементы соответствующей Модели"
23 | create_first_entry: "Создать первый элемент"
24 | field_types:
25 | string: "Строка"
26 | text: "Текст"
27 | checkbox: "Чекбокс"
28 | date: "Дата"
29 | file: "Файл"
30 | has_many: "Имеет много"
31 | belongs_to: "Связано с"
32 | liquor_help:
33 | variables:
34 | models:
35 | main_description: 'Переменная models предоставляет доступ к коллекциям моделей, определённых пользователем на вкладке "Модели". Доступ осуществляется через название коллекции модели - поле "Название коллекции (для Liquor)" при создании модели. Например, если создана модель с названием коллекции services, то можно использовать for для итерации по элементам коллекции следующим образом:'
36 | endpoints:
37 | entries:
38 | post:
39 | main_description: Этот запрос позволяет создавать формы для отправки элементов модели. Например, если есть модель с названием коллекции "posts" (и поле с названием для Liquor - "title"), то можно создать форму с action="/entries/posts" и input с name="entry[title]"
40 | parameters:
41 | entry: Значение каждого параметра такого вида будет сохранено в соответствующее поле элемента модели
42 | activerecord:
43 | models:
44 | kms/model:
45 | one: "Модель"
46 | few: "Модели"
47 | many: "Моделей"
48 | other: "Модели"
49 | kms/entry:
50 | one: "Элемент"
51 | few: "Элемента"
52 | many: "Элементов"
53 | other: "Элементы"
54 | attributes:
55 | kms/model:
56 | kms_model_name: "Название"
57 | collection_name: "Название коллекции (для Liquor)"
58 | description: "Описание"
59 | fields: "Свойства"
60 | label_field: "Поле, используемое для генерации ссылок на объекты"
61 | allow_creation_using_form: "Разрешить создание элементов с помощью форм"
62 | kms/field:
63 | name: "Название"
64 | liquor_name: "Название для Liquor"
65 | type: "Тип"
66 | required: "Обязательное"
67 | kms/entry:
68 | slug: "Ссылка/URL"
69 |
--------------------------------------------------------------------------------
/app/assets/javascripts/kms_models/application/controllers/entries_controller.coffee.erb:
--------------------------------------------------------------------------------
1 | EntriesController = ($scope, $state, Restangular, $stateParams, Alertify, ErrorsService) ->
2 | $scope.modelStore = Restangular.all('models')
3 | $scope.store = Restangular.one("models", $stateParams.modelId).all("entries")
4 | $scope.editorOptions =
5 | filebrowserUploadUrl: '/kms/assets/ckeditor'
6 | language: '<%= I18n.locale.to_s %>'
7 |
8 | #Restangular.all('users').customGET('kms_user').then (current_user) ->
9 | #$scope.currentUser = current_user
10 | #$scope.currentUser.admin = $scope.currentUser.role == 'admin'
11 |
12 | $scope.entriesSortableOptions =
13 | orderChanged: (event)->
14 | for entry, index in event.dest.sortableScope.modelValue
15 | entry_copy =
16 | id: entry.id
17 | position: index
18 | Restangular.restangularizeElement($scope.model, entry_copy, 'entries').put()
19 |
20 | $scope.modelStore.get($stateParams.modelId).then (model)->
21 | $scope.model = model
22 | fields = $scope.getAssociationFields(model)
23 | _.each fields, (field)->
24 | $scope[field.liquor_name] = [] # pre-init
25 | Restangular.one("models", field.class_name).all("entries").getList().then (entries)->
26 | $scope[field.liquor_name] = entries
27 | $scope.initAssociationField(field)
28 |
29 | $scope.initAssociationField = (field)->
30 | if field.type == 'Kms::HasManyField'
31 | fieldEntries = _.map $scope.entry.values[field.liquor_name], (entryId)->
32 | _.find $scope[field.liquor_name], { 'id': parseInt(entryId) }
33 | $scope.entry.values[field.liquor_name] = _.compact fieldEntries
34 | else
35 | $scope.entry.values[field.liquor_name] = _.find $scope[field.liquor_name], (element)->
36 | $scope.entry.values[field.liquor_name] == element.id.toString()
37 |
38 | $scope.store.getList().then (entries)->
39 | $scope.entries = entries
40 |
41 | if $stateParams.id
42 | $scope.store.get($stateParams.id).then (entry)->
43 | $scope.entry = entry
44 | else
45 | $scope.entry = {values: {}}
46 |
47 | $scope.getAssociationFields = (model)->
48 | _.filter model.fields_attributes, (field) -> field.type == 'Kms::HasManyField' or field.type == 'Kms::BelongsToField'
49 |
50 | $scope.getFieldTemplateName = (field)->
51 | typeSplitted = field.type.split '::'
52 | _.snakeCase(typeSplitted[typeSplitted.length - 1])
53 |
54 | $scope.create = ->
55 | fd = new FormData
56 | if $scope.entry.slug
57 | fd.append("entry[slug]", $scope.entry.slug)
58 | for key, value of $scope.entry.values
59 | fd.append("entry[values][#{key}]", value || '')
60 | $scope.store.withHttpConfig({ transformRequest: angular.identity }).post(fd, null, {"Content-Type": undefined}).then ->
61 | $state.go('models.entries', modelId: $scope.model.id)
62 | ,(response)->
63 | Alertify.error(ErrorsService.prepareErrorsString(response.data.errors))
64 |
65 | $scope.update = ->
66 | fd = new FormData
67 | if $scope.entry.slug
68 | fd.append("entry[slug]", $scope.entry.slug)
69 | for key, value of $scope.entry.values
70 | # continue if value == undefined
71 | unless _.isEmpty(value)
72 | if value.constructor.name == 'Array'
73 | for element in value
74 | id = if element.constructor.name == 'Object' then element.id else element
75 | fd.append("entry[values][#{key}][]", id)
76 | else if value.constructor.name != 'Object'
77 | fd.append("entry[values][#{key}]", value || '')
78 | else
79 | fd.append("entry[values][#{key}]", if value? then value else '')
80 | $scope.entry.withHttpConfig({ transformRequest: angular.identity }).post('', fd, '', {"Content-Type": undefined}).then ->
81 | $state.go('models.entries', modelId: $scope.model.id)
82 | ,(response)->
83 | Alertify.error(ErrorsService.prepareErrorsString(response.data.errors))
84 |
85 | $scope.destroy = (entry)->
86 | if(confirm('<%= I18n.t(:are_you_sure) %>'))
87 | entry.remove().then ->
88 | $scope.entries = _.without($scope.entries, entry)
89 |
90 |
91 |
92 | angular.module('KMS')
93 | .controller('EntriesController', ['$scope', '$state', 'Restangular', '$stateParams', 'Alertify', 'ErrorsService', EntriesController])
94 |
--------------------------------------------------------------------------------
/spec/internal/config/initializers/devise.rb:
--------------------------------------------------------------------------------
1 | # Use this hook to configure devise mailer, warden hooks and so forth.
2 | # Many of these configuration options can be set straight in your model.
3 | Devise.setup do |config|
4 | # The secret key used by Devise. Devise uses this key to generate
5 | # random tokens. Changing this key will render invalid all existing
6 | # confirmation, reset password and unlock tokens in the database.
7 | config.secret_key = '57e00a5d56d16d1341110df1455911bf2890d652efc6a2cf52ea3f341405770878d6a471530601ad1a7058a2e073537315eb3852e34b8384a93c108e05097725'
8 |
9 | # ==> Mailer Configuration
10 | # Configure the e-mail address which will be shown in Devise::Mailer,
11 | # note that it will be overwritten if you use your own mailer class
12 | # with default "from" parameter.
13 | config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
14 |
15 | # Configure the class responsible to send e-mails.
16 | # config.mailer = 'Devise::Mailer'
17 |
18 | # ==> ORM configuration
19 | # Load and configure the ORM. Supports :active_record (default) and
20 | # :mongoid (bson_ext recommended) by default. Other ORMs may be
21 | # available as additional gems.
22 | require 'devise/orm/active_record'
23 |
24 | # ==> Configuration for any authentication mechanism
25 | # Configure which keys are used when authenticating a user. The default is
26 | # just :email. You can configure it to use [:username, :subdomain], so for
27 | # authenticating a user, both parameters are required. Remember that those
28 | # parameters are used only when authenticating and not when retrieving from
29 | # session. If you need permissions, you should implement that in a before filter.
30 | # You can also supply a hash where the value is a boolean determining whether
31 | # or not authentication should be aborted when the value is not present.
32 | # config.authentication_keys = [ :email ]
33 |
34 | # Configure parameters from the request object used for authentication. Each entry
35 | # given should be a request method and it will automatically be passed to the
36 | # find_for_authentication method and considered in your model lookup. For instance,
37 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
38 | # The same considerations mentioned for authentication_keys also apply to request_keys.
39 | # config.request_keys = []
40 |
41 | # Configure which authentication keys should be case-insensitive.
42 | # These keys will be downcased upon creating or modifying a user and when used
43 | # to authenticate or find a user. Default is :email.
44 | config.case_insensitive_keys = [ :email ]
45 |
46 | # Configure which authentication keys should have whitespace stripped.
47 | # These keys will have whitespace before and after removed upon creating or
48 | # modifying a user and when used to authenticate or find a user. Default is :email.
49 | config.strip_whitespace_keys = [ :email ]
50 |
51 | # Tell if authentication through request.params is enabled. True by default.
52 | # It can be set to an array that will enable params authentication only for the
53 | # given strategies, for example, `config.params_authenticatable = [:database]` will
54 | # enable it only for database (email + password) authentication.
55 | # config.params_authenticatable = true
56 |
57 | # Tell if authentication through HTTP Auth is enabled. False by default.
58 | # It can be set to an array that will enable http authentication only for the
59 | # given strategies, for example, `config.http_authenticatable = [:database]` will
60 | # enable it only for database authentication. The supported strategies are:
61 | # :database = Support basic authentication with authentication key + password
62 | # config.http_authenticatable = false
63 |
64 | # If http headers should be returned for AJAX requests. True by default.
65 | # config.http_authenticatable_on_xhr = true
66 |
67 | # The realm used in Http Basic Authentication. 'Application' by default.
68 | # config.http_authentication_realm = 'Application'
69 |
70 | # It will change confirmation, password recovery and other workflows
71 | # to behave the same regardless if the e-mail provided was right or wrong.
72 | # Does not affect registerable.
73 | # config.paranoid = true
74 |
75 | # By default Devise will store the user in session. You can skip storage for
76 | # particular strategies by setting this option.
77 | # Notice that if you are skipping storage for all authentication paths, you
78 | # may want to disable generating routes to Devise's sessions controller by
79 | # passing skip: :sessions to `devise_for` in your config/routes.rb
80 | config.skip_session_storage = [:http_auth]
81 |
82 | # By default, Devise cleans up the CSRF token on authentication to
83 | # avoid CSRF token fixation attacks. This means that, when using AJAX
84 | # requests for sign in and sign up, you need to get a new CSRF token
85 | # from the server. You can disable this option at your own risk.
86 | # config.clean_up_csrf_token_on_authentication = true
87 |
88 | # ==> Configuration for :database_authenticatable
89 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If
90 | # using other encryptors, it sets how many times you want the password re-encrypted.
91 | #
92 | # Limiting the stretches to just one in testing will increase the performance of
93 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
94 | # a value less than 10 in other environments. Note that, for bcrypt (the default
95 | # encryptor), the cost increases exponentially with the number of stretches (e.g.
96 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
97 | config.stretches = Rails.env.test? ? 1 : 10
98 |
99 | # Setup a pepper to generate the encrypted password.
100 | # config.pepper = '941376f1bd6d84642f36af53a6ab6e4cde564019db133c446f53ee1fb2999536d72793983406f515b7bcb9fdb5e2cf174e9bdfab23edf0427a80b6a6977ee48e'
101 |
102 | # ==> Configuration for :confirmable
103 | # A period that the user is allowed to access the website even without
104 | # confirming their account. For instance, if set to 2.days, the user will be
105 | # able to access the website for two days without confirming their account,
106 | # access will be blocked just in the third day. Default is 0.days, meaning
107 | # the user cannot access the website without confirming their account.
108 | # config.allow_unconfirmed_access_for = 2.days
109 |
110 | # A period that the user is allowed to confirm their account before their
111 | # token becomes invalid. For example, if set to 3.days, the user can confirm
112 | # their account within 3 days after the mail was sent, but on the fourth day
113 | # their account can't be confirmed with the token any more.
114 | # Default is nil, meaning there is no restriction on how long a user can take
115 | # before confirming their account.
116 | # config.confirm_within = 3.days
117 |
118 | # If true, requires any email changes to be confirmed (exactly the same way as
119 | # initial account confirmation) to be applied. Requires additional unconfirmed_email
120 | # db field (see migrations). Until confirmed, new email is stored in
121 | # unconfirmed_email column, and copied to email column on successful confirmation.
122 | config.reconfirmable = true
123 |
124 | # Defines which key will be used when confirming an account
125 | # config.confirmation_keys = [ :email ]
126 |
127 | # ==> Configuration for :rememberable
128 | # The time the user will be remembered without asking for credentials again.
129 | # config.remember_for = 2.weeks
130 |
131 | # If true, extends the user's remember period when remembered via cookie.
132 | # config.extend_remember_period = false
133 |
134 | # Options to be passed to the created cookie. For instance, you can set
135 | # secure: true in order to force SSL only cookies.
136 | # config.rememberable_options = {}
137 |
138 | # ==> Configuration for :validatable
139 | # Range for password length.
140 | config.password_length = 8..128
141 |
142 | # Email regex used to validate email formats. It simply asserts that
143 | # one (and only one) @ exists in the given string. This is mainly
144 | # to give user feedback and not to assert the e-mail validity.
145 | # config.email_regexp = /\A[^@]+@[^@]+\z/
146 |
147 | # ==> Configuration for :timeoutable
148 | # The time you want to timeout the user session without activity. After this
149 | # time the user will be asked for credentials again. Default is 30 minutes.
150 | # config.timeout_in = 30.minutes
151 |
152 | # If true, expires auth token on session timeout.
153 | # config.expire_auth_token_on_timeout = false
154 |
155 | # ==> Configuration for :lockable
156 | # Defines which strategy will be used to lock an account.
157 | # :failed_attempts = Locks an account after a number of failed attempts to sign in.
158 | # :none = No lock strategy. You should handle locking by yourself.
159 | # config.lock_strategy = :failed_attempts
160 |
161 | # Defines which key will be used when locking and unlocking an account
162 | # config.unlock_keys = [ :email ]
163 |
164 | # Defines which strategy will be used to unlock an account.
165 | # :email = Sends an unlock link to the user email
166 | # :time = Re-enables login after a certain amount of time (see :unlock_in below)
167 | # :both = Enables both strategies
168 | # :none = No unlock strategy. You should handle unlocking by yourself.
169 | # config.unlock_strategy = :both
170 |
171 | # Number of authentication tries before locking an account if lock_strategy
172 | # is failed attempts.
173 | # config.maximum_attempts = 20
174 |
175 | # Time interval to unlock the account if :time is enabled as unlock_strategy.
176 | # config.unlock_in = 1.hour
177 |
178 | # Warn on the last attempt before the account is locked.
179 | # config.last_attempt_warning = false
180 |
181 | # ==> Configuration for :recoverable
182 | #
183 | # Defines which key will be used when recovering the password for an account
184 | # config.reset_password_keys = [ :email ]
185 |
186 | # Time interval you can reset your password with a reset password key.
187 | # Don't put a too small interval or your users won't have the time to
188 | # change their passwords.
189 | config.reset_password_within = 6.hours
190 |
191 | # ==> Configuration for :encryptable
192 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use
193 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
194 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
195 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
196 | # REST_AUTH_SITE_KEY to pepper).
197 | #
198 | # Require the `devise-encryptable` gem when using anything other than bcrypt
199 | # config.encryptor = :sha512
200 |
201 | # ==> Scopes configuration
202 | # Turn scoped views on. Before rendering "sessions/new", it will first check for
203 | # "users/sessions/new". It's turned off by default because it's slower if you
204 | # are using only default views.
205 | # config.scoped_views = false
206 |
207 | # Configure the default scope given to Warden. By default it's the first
208 | # devise role declared in your routes (usually :user).
209 | # config.default_scope = :user
210 |
211 | # Set this configuration to false if you want /users/sign_out to sign out
212 | # only the current scope. By default, Devise signs out all scopes.
213 | # config.sign_out_all_scopes = true
214 |
215 | # ==> Navigation configuration
216 | # Lists the formats that should be treated as navigational. Formats like
217 | # :html, should redirect to the sign in page when the user does not have
218 | # access, but formats like :xml or :json, should return 401.
219 | #
220 | # If you have any extra navigational formats, like :iphone or :mobile, you
221 | # should add them to the navigational formats lists.
222 | #
223 | # The "*/*" below is required to match Internet Explorer requests.
224 | # config.navigational_formats = ['*/*', :html]
225 |
226 | # The default HTTP method used to sign out a resource. Default is :delete.
227 | config.sign_out_via = :get
228 |
229 | # ==> OmniAuth
230 | # Add a new OmniAuth provider. Check the wiki for more information on setting
231 | # up on your models and hooks.
232 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
233 |
234 | # ==> Warden configuration
235 | # If you want to use other strategies, that are not supported by Devise, or
236 | # change the failure app, you can configure them inside the config.warden block.
237 | #
238 | # config.warden do |manager|
239 | # manager.intercept_401 = false
240 | # manager.default_strategies(scope: :user).unshift :some_external_strategy
241 | # end
242 |
243 | # ==> Mountable engine configurations
244 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine
245 | # is mountable, there are some extra configurations to be taken into account.
246 | # The following options are available, assuming the engine is mounted as:
247 | #
248 | # mount MyEngine, at: '/my_engine'
249 | #
250 | # The router that invoked `devise_for`, in the example above, would be:
251 | config.router_name = :kms
252 | config.parent_controller = 'Kms::ApplicationController'
253 | #
254 | # When using omniauth, Devise cannot automatically set Omniauth path,
255 | # so you need to do it manually. For the users scope, it would be:
256 | # config.omniauth_path_prefix = '/my_engine/users/auth'
257 | end
258 |
--------------------------------------------------------------------------------