e9_attributes 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+