e9_attributes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in e9_attributes.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ E9 Attributes
2
+ =============
3
+
4
+ Searchable attributes engine
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,27 @@
1
+ class E9Attributes::MenuOptionsController < Admin::ResourceController
2
+ include E9Rails::Controllers::Sortable
3
+ include E9Rails::Helpers::ResourceLinks
4
+
5
+ defaults :resource_class => MenuOption, :route_prefix => nil
6
+ has_scope :options_for, :as => :key, :only => :index
7
+
8
+ # NOTE The reason this is set in a filter instead of just a default scope value
9
+ # is that it is used in the index view to add the key param to the new
10
+ # resource link
11
+ #
12
+ before_filter :ensure_default_fetch_key, :only => :index
13
+
14
+ def create
15
+ create! { collection_path(:key => resource.key) }
16
+ end
17
+
18
+ def update
19
+ update! { collection_path(:key => resource.key) }
20
+ end
21
+
22
+ protected
23
+
24
+ def ensure_default_fetch_key
25
+ params[:key] ||= MenuOption.keys.sort.first
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ # A simple class to manage menu options, usable by other classes to build their menus.
2
+ #
3
+ class MenuOption < ActiveRecord::Base
4
+ class_inheritable_accessor :keys
5
+
6
+ def self.initialize_keys!(*keys)
7
+ return self.keys unless self.keys.nil?
8
+
9
+ keys.flatten!
10
+ keys.map!(&:to_s)
11
+
12
+ self.keys = keys
13
+ validates :key, :presence => true, :inclusion => { :in => keys, :allow_blank => true }
14
+ end
15
+
16
+ validates :value, :presence => true
17
+
18
+ acts_as_list :scope => 'menu_options.key = \"#{key}\"'
19
+
20
+ scope :options_for, lambda {|key| where(:key => key).order('menu_options.position ASC') }
21
+
22
+ ##
23
+ # A direct SQL selection of values for a given key
24
+ #
25
+ # MenuOption.fetch('Email') #=> ['Personal','Work']
26
+ #
27
+ # === Parameters
28
+ #
29
+ # [key (String)] The key for the assocated menu options.
30
+ #
31
+ def self.fetch_values(key)
32
+ connection.send(:select_values, options_for(key).order(:position).project('value').to_sql, 'Menu Option Select')
33
+ end
34
+
35
+ def to_s
36
+ value
37
+ end
38
+
39
+ # For e9 admin
40
+ def role; 'administrator' end
41
+ end
@@ -0,0 +1,51 @@
1
+ # An arbitrary 'attribute' attachable to records.
2
+ #
3
+ # Gets support for arbitrary options via InheritableOptions. By default it
4
+ # has one option, +type+, but subclasses could extend for further options by
5
+ # overwriting +options_parameters+.
6
+ #
7
+ # By default, the +type+ options are managed via the +MenuOption+ class.
8
+ #
9
+ class RecordAttribute < ActiveRecord::Base
10
+ include E9Rails::ActiveRecord::STI
11
+ include E9Rails::ActiveRecord::AttributeSearchable
12
+ include E9Rails::ActiveRecord::InheritableOptions
13
+
14
+ PARTIAL_PATH = 'e9_attributes/record_attributes'
15
+ FORM_PARTIAL = PARTIAL_PATH + '/form_partial'
16
+ LAYOUT_PARTIAL = PARTIAL_PATH + '/record_attribute'
17
+ TEMPLATES = PARTIAL_PATH + '/templates'
18
+
19
+ self.options_parameters = [:type]
20
+ self.delegate_options_methods = true
21
+
22
+ belongs_to :record, :polymorphic => true
23
+
24
+ scope :search, lambda {|query|
25
+ where(arel_table[:value].matches('%query%'))
26
+ }
27
+
28
+ ##
29
+ # Looks up the available +types+ for this attribute by fetching a
30
+ # titleized version of the class name from +MenuOption+.
31
+ #
32
+ # e.g.
33
+ #
34
+ # PhoneNumberAttribute.types
35
+ #
36
+ # is equivalent to:
37
+ #
38
+ # MenuOption.fetch_values('Phone Number')
39
+ #
40
+ def self.types
41
+ if name =~ /^(\w+)Attribute$/
42
+ MenuOption.fetch_values($1.titleize)
43
+ else
44
+ []
45
+ end
46
+ end
47
+
48
+ def to_s
49
+ options.type ? "#{value} (#{options.type})" : value
50
+ end
51
+ end
@@ -0,0 +1,12 @@
1
+ = form_for resource, :url => polymorphic_path([parent, resource]), :remote => request.xhr? do |f|
2
+ .errors= resource_error_messages!
3
+
4
+ .field
5
+ = f.label :key
6
+ = f.select :key, MenuOption.keys.sort
7
+ .field
8
+ = f.label :value, nil, :class => :req
9
+ = f.text_field :value
10
+
11
+ .actions
12
+ = f.submit
@@ -0,0 +1,24 @@
1
+ - field_map = { :fields => { :position => proc { '<div class="handle">+</div>'.html_safe }, :value => nil, :key => nil } }
2
+
3
+ %table.sortable
4
+ %thead
5
+ %tr
6
+ - field_map[:fields].keys.each do |key|
7
+ - if key.is_a?(Symbol)
8
+ %th= resource_class.human_attribute_name(key)
9
+ - else
10
+ %th= orderable_column_link(key)
11
+ %th= e9_t(:actions)
12
+ %tbody
13
+ - if collection.empty?
14
+ %tr
15
+ %td{:colspan => field_map[:fields].length + 1}= e9_t(:no_records_text)
16
+ - else
17
+ - collection.each do |record|
18
+ %tr{:id => "ids_#{record.id}", :class => cycle('odd', 'even')}
19
+ - field_map[:fields].each do |key, value|
20
+ %td{:class => "record-#{key.to_s.dasherize}"}
21
+ = value.respond_to?(:call) ? value.call(record) : record.send(key)
22
+ %td.links
23
+ = link_to_edit_resource(record)
24
+ = link_to_destroy_resource(record)
@@ -0,0 +1,2 @@
1
+ = title @edit_title || e9_t(:edit_title)
2
+ = render 'form'
@@ -0,0 +1,13 @@
1
+ = title (@index_title || e9_t(:index_title))
2
+
3
+ .toolbar
4
+ .toolbar-left
5
+ %form{:action => menu_options_path, :method => :get, :class => 'scope-selects'}
6
+ = label_tag 'menu_option_key_select', t(:view)
7
+ = select_tag 'key', options_for_select(MenuOption.keys.sort, params[:key])
8
+ .toolbar-right
9
+ = link_to_new_resource(MenuOption, :menu_option => {:key => params[:key]}, :class => 'new-menu-option')
10
+
11
+ = form_tag polymorphic_path([:update_order, resource_class]) do
12
+ %div#records_table
13
+ = render 'table', :resources => collection
@@ -0,0 +1 @@
1
+ $("table.records").replaceWith("<%= escape_javascript(render('table', :collection => collection)) %>");
@@ -0,0 +1,2 @@
1
+ = title @new_title || e9_t(:new_title)
2
+ = render 'form'
@@ -0,0 +1,2 @@
1
+ = title @show_title || e9_t(:show_title)
2
+ = render resource
@@ -0,0 +1,4 @@
1
+ %fieldset.nested-associations
2
+ %legend= form.label(association_name)
3
+ = render_record_attribute_association(association_name, form)
4
+ = link_to_add_record_attribute(association_name)
@@ -0,0 +1,10 @@
1
+ .record-attribute.nested-association
2
+ - if f.object.persisted?
3
+ = f.hidden_field :id
4
+ = f.hidden_field :_destroy, :value => 0
5
+ .field
6
+ = f.send local_assigns[:value_field_type] || :text_field, :value
7
+
8
+ = @nested_association_options
9
+
10
+ = link_to_destroy_record_attribute
@@ -0,0 +1,12 @@
1
+ E9A = window.E9A || {};
2
+ E9A.js_templates = {};
3
+ E9A.js_templates.start_child_index =
4
+ E9A.js_templates.current_child_index = <%= child_index = 100000 %>;
5
+ <%= fields_for(resource) do |f| %>
6
+ <% resource_class.record_attributes.each do |association_name| %>
7
+ <% build_associated_resource(association_name) %>
8
+ <%= f.fields_for(association_name, :child_index => child_index) do |association_form_builder| %>
9
+ E9A.js_templates.<%= association_name %> = "<%= escape_javascript(record_attribute_template(association_name, association_form_builder)) %>";
10
+ <% end %>
11
+ <% end %>
12
+ <% end %>
@@ -0,0 +1,6 @@
1
+ en:
2
+ view: View
3
+ e9_attributes:
4
+ add_record_attribute: Add
5
+ destroy_record_attribute: Remove
6
+
data/config/routes.rb ADDED
@@ -0,0 +1,15 @@
1
+ Rails.application.routes.draw do
2
+ base_path = 'admin'
3
+
4
+ scope :path => base_path, :module => :e9_attributes do
5
+ resources :menu_options, :except => [:show] do
6
+ collection { post :update_order }
7
+ end
8
+
9
+ %w(
10
+ menu_options
11
+ ).each do |path|
12
+ get "/#{path}/:id", :to => redirect("/#{base_path}/#{path}/%{id}/edit"), :constraints => { :id => /\d+/ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "e9_attributes/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "e9_attributes"
7
+ s.version = E9Attributes::VERSION
8
+ s.authors = ["Travis Cox"]
9
+ s.email = ["numbers1311407@gmail.com"]
10
+ s.homepage = "http://github.com/e9digital/e9_attributes"
11
+ s.summary = %q{Searchable attributes}
12
+ s.description = File.open('README.md').read rescue nil
13
+
14
+ s.rubyforge_project = "e9_attributes"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,24 @@
1
+ require "rails"
2
+ require "e9_base"
3
+ require "e9_attributes/version"
4
+
5
+ module E9Attributes
6
+ autoload :VERSION, 'e9_attributes/version'
7
+ autoload :Model, 'e9_attributes/model'
8
+
9
+ def E9Attributes.init!
10
+ ActiveRecord::Base.send(:include, E9Attributes::Model)
11
+ end
12
+
13
+ class Engine < ::Rails::Engine
14
+ config.e9_attributes = E9Attributes
15
+ config.to_prepare { E9Attributes.init! }
16
+
17
+ initializer 'e9_attributes.include_base_helper' do
18
+ ActiveSupport.on_load(:action_view) do
19
+ require 'e9_attributes/helper'
20
+ include E9Attributes::Helper
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,46 @@
1
+ module E9Attributes::Helper
2
+
3
+ def link_to_add_record_attribute(association_name)
4
+ content_tag :span, t(:add_record_attribute, :scope => :e9_attributes), :class => 'add-nested-association', 'data-association' => association_name
5
+ end
6
+
7
+ def link_to_destroy_record_attribute
8
+ content_tag :span, t(:destroy_record_attribute, :scope => :e9_attributes), :class => 'destroy-nested-association'
9
+ end
10
+
11
+ def render_record_attribute_form(association_name, form)
12
+ render(RecordAttribute::FORM_PARTIAL, {
13
+ :form => form,
14
+ :association_name => association_name
15
+ })
16
+ end
17
+
18
+ def render_record_attribute_association(association_name, form, options = {})
19
+ options.symbolize_keys!
20
+
21
+ association = resource.send(association_name)
22
+
23
+ unless association.empty?
24
+ form.fields_for(association_name) do |f|
25
+ concat record_attribute_template(association_name, f, options)
26
+ end
27
+ end
28
+ end
29
+
30
+ def record_attribute_template(association_name, builder, options = {})
31
+ options.symbolize_keys!
32
+
33
+ render(
34
+ :partial => options[:partial] || "record_attributes/#{association_name.to_s.singularize}",
35
+ :locals => { :f => builder }
36
+ )
37
+ end
38
+
39
+ # tries to build an associated resource, looking to the assocatiaon's model for a method
40
+ # named "%{association_name}_build_parameters}" first for any default params
41
+ def build_associated_resource(association_name)
42
+ params_method = "#{association_name}_build_parameters"
43
+ build_params = resource_class.send(params_method) if resource_class.respond_to?(params_method)
44
+ resource.send(association_name).build(build_params || {})
45
+ end
46
+ end
@@ -0,0 +1,39 @@
1
+ module E9Attributes::Model
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def has_record_attributes(*attributes)
6
+ options = attributes.extract_options!
7
+
8
+ class_inheritable_accessor :record_attributes
9
+ self.record_attributes = attributes.flatten.map do |a|
10
+ a = a.to_s.classify
11
+ a = a =~ /Attribute$/ ? a : "#{a}Attribute"
12
+ a.constantize rescue next
13
+ a.underscore.pluralize
14
+ end.compact
15
+
16
+ has_many :record_attributes, :as => :record
17
+
18
+ self.record_attributes.each do |association_name|
19
+ has_many association_name.to_sym, :class_name => association_name.classify, :as => :record
20
+ accepts_nested_attributes_for association_name.to_sym, :allow_destroy => true, :reject_if => :reject_record_attribute?
21
+ end
22
+ end
23
+ end
24
+
25
+ def build_all_record_attributes
26
+ record_attributes.each do |attr|
27
+ params_method = "#{attr}_build_parameters"
28
+ build_params = self.class.send(params_method) if self.class.respond_to?(params_method)
29
+ send(attr).send(:build, build_params || {})
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ def reject_record_attribute?(attributes)
36
+ attributes.keys.member?('value') && attributes['value'].blank?
37
+ end
38
+
39
+ end
@@ -0,0 +1,3 @@
1
+ module E9Attributes
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module E9Attributes
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ source_root File.expand_path('templates', File.dirname(__FILE__))
10
+
11
+ def self.next_migration_number(dirname) #:nodoc:
12
+ if ActiveRecord::Base.timestamped_migrations
13
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
14
+ else
15
+ "%.3d" % (current_migration_number(dirname) + 1)
16
+ end
17
+ end
18
+
19
+ def generate_migrations
20
+ migration_template 'migration.rb', 'db/migrate/create_e9_attributes.rb'
21
+ end
22
+
23
+ def copy_initializer
24
+ copy_file 'initializer.rb', 'config/initializers/e9_attributes.rb'
25
+ end
26
+
27
+ def copy_javascript
28
+ copy_file 'javascript.js', 'public/javascripts/e9_attrubutes.js'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,60 @@
1
+ ;jQuery(function($) {
2
+
3
+ /*
4
+ * change the value of the new menu option type link when the scope select
5
+ * changes, e.g. when viewing "Email" menu options, clicking "Add Menu Option"
6
+ * defaults to creating a new email menu option.
7
+ */
8
+ $('body.admin a.new-menu-option').bind('scope-select:change:key', function(e, v) {
9
+ $(this).attr('href', function(i, val) {
10
+ return val.replace(/\[key\]=([^&]*)/, '[key]='+encodeURIComponent(v));
11
+ });
12
+ });
13
+
14
+ /*
15
+ * Adds a new nested assocation. Depends on the nested association
16
+ * js templates being loaded.
17
+ */
18
+ $('a.add-nested-association').click(function(e) {
19
+ e.preventDefault();
20
+
21
+ var template;
22
+
23
+ // get the template for this attribute type
24
+ try {
25
+ template = E9CRM.js_templates[this.getAttribute('data-association')];
26
+ } catch(e) { return }
27
+
28
+ // sub in the current index and increment it
29
+ template = template.replace(
30
+ new RegExp(E9CRM.js_templates.start_child_index, 'g'),
31
+ ++E9A.js_templates.current_child_index
32
+ );
33
+
34
+ // and insert the new template before this link
35
+ $(template).insertBefore($(this));
36
+ });
37
+
38
+ /*
39
+ * Effectively destroys an added nested association, removing the container
40
+ * the association is not persisted, or hiding it and setting the _destroy
41
+ * parameter for the association if it is.
42
+ */
43
+ $('a.destroy-nested-association').live('click', function(e) {
44
+ e.preventDefault();
45
+
46
+ // grab the parent nested-association and attempt to get its hidden
47
+ // 'destroy' input if it exists.
48
+ var $parent = $(this).closest('.nested-association').hide(),
49
+ $destro = $parent.find('input[id$=__destroy]');
50
+
51
+ // If a in input ending in __destroy was found it means that this is a
52
+ // persisted record. Set that input's value to '1' so it will be destroyed
53
+ // on record commit.
54
+ if ($destro.length) { $destro.val('1'); }
55
+
56
+ // otherwise this record was created locally and has not been saved, so
57
+ // simply remove it.
58
+ else { $parent.remove(); }
59
+ });
60
+ });
@@ -0,0 +1,13 @@
1
+ class CreateE9Attributes < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :record_attributes, :force => true do |t|
4
+ t.string :type
5
+ t.references :record, :polymorphic => true
6
+ t.text :value, :options, :limit => 3.kilobytes
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :record_attributes
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: e9_attributes
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Travis Cox
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-08-19 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: |
18
+ E9 Attributes
19
+ =============
20
+
21
+ Searchable attributes engine
22
+
23
+ email:
24
+ - numbers1311407@gmail.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - README.md
35
+ - Rakefile
36
+ - app/controllers/e9_attributes/menu_options_controller.rb
37
+ - app/models/menu_option.rb
38
+ - app/models/record_attribute.rb
39
+ - app/views/e9_attributes/menu_options/_form.html.haml
40
+ - app/views/e9_attributes/menu_options/_table.html.haml
41
+ - app/views/e9_attributes/menu_options/edit.html.haml
42
+ - app/views/e9_attributes/menu_options/index.html.haml
43
+ - app/views/e9_attributes/menu_options/index.js.erb
44
+ - app/views/e9_attributes/menu_options/new.html.haml
45
+ - app/views/e9_attributes/menu_options/show.html.haml
46
+ - app/views/e9_attributes/record_attributes/_form_partial.html.haml
47
+ - app/views/e9_attributes/record_attributes/_record_attribute.html.haml
48
+ - app/views/e9_attributes/record_attributes/templates.js.erb
49
+ - config/locales/en.yml
50
+ - config/routes.rb
51
+ - e9_attributes.gemspec
52
+ - lib/e9_attributes.rb
53
+ - lib/e9_attributes/helper.rb
54
+ - lib/e9_attributes/model.rb
55
+ - lib/e9_attributes/version.rb
56
+ - lib/generators/e9_attributes/install_generator.rb
57
+ - lib/generators/e9_attributes/templates/initializer.rb
58
+ - lib/generators/e9_attributes/templates/javascript.js
59
+ - lib/generators/e9_attributes/templates/migration.rb
60
+ has_rdoc: true
61
+ homepage: http://github.com/e9digital/e9_attributes
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project: e9_attributes
84
+ rubygems_version: 1.6.2
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Searchable attributes
88
+ test_files: []
89
+