Skip to content

Commit

Permalink
Merge branch 'with_friendly_id_i18n'
Browse files Browse the repository at this point in the history
  • Loading branch information
reidab committed Nov 5, 2011
2 parents f8a437a + 3da60b7 commit 13f685f
Show file tree
Hide file tree
Showing 33 changed files with 443 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -49,6 +49,8 @@ gem "responders", "~> 0.6.2"

gem 'acts-as-taggable-on', "~> 2.1.1"

gem 'friendly_id', '4.0.0.beta14'

gem 'paper_trail', '~> 2'
gem 'paper_trail_manager'
# gem 'paper_trail_manager', :git => 'https://github.com/igal/paper_trail_manager.git'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Expand Up @@ -118,6 +118,7 @@ GEM
faraday (< 0.8, >= 0.6)
faraday_middleware (< 0.8, >= 0.6)
hashie (~> 1.0)
friendly_id (4.0.0.beta14)
fssm (0.2.7)
haml (3.1.3)
has_scope (0.5.1)
Expand Down Expand Up @@ -355,6 +356,7 @@ DEPENDENCIES
fakeweb
formtastic (~> 1.2.4)
foursquare2 (~> 1.1.0)
friendly_id (= 4.0.0.beta14)
haml (~> 3.1.2)
hoptoad_notifier
httparty (~> 0.8.0)
Expand Down
20 changes: 20 additions & 0 deletions app/controllers/application_controller.rb
Expand Up @@ -81,6 +81,17 @@ def require_admin!
end
end

# Should a login be allowed as a specific user? Used by the AuthenticationsController and views. Stub this in specs!
def self.allow_login_as_specific_user?
Rails.env == "development"
end

# Wrapper for ApplicationController::allow_login_as_specific_user? because there's no way to stub it. Integration tests can't get to the instance and RSpec's #any_instance is broken.
def allow_login_as_specific_user?
return self.class.allow_login_as_specific_user?
end
helper_method :allow_login_as_specific_user?

def page_title(value=nil)
@page_title = value unless value.nil?

Expand All @@ -107,4 +118,13 @@ def page_title(value=nil)
end
end
helper_method :page_title

# Preserve old links to resources by redirecting to their current location.
#
# The "friendly_id" slug history tracks the resource's slug over time. If a resource is available under a newer slug name, redirect its "show" action to the current name. E.g. "/request/old-name" will redirect to "/request/new-name" if the slug was changed from "old-name" to "new-name".
def redirect_historical_slugs
if self.action_name == "show" && request.path != resource_path(resource)
return redirect_to resource, :status => :moved_permanently
end
end
end
24 changes: 14 additions & 10 deletions app/controllers/authentications_controller.rb
Expand Up @@ -41,10 +41,20 @@ def auth_failure
def create
omniauth = request.env["omniauth.auth"]

if self.login_as_sample_user?
user = User.find_or_create_sample
sign_in :user, user
return redirect_to(stored_location_for(:user) || welcome_users_path)
if self.allow_login_as_specific_user? && request.env["omniauth.auth"].nil?
if params["username"] == "sample"
user = User.find_or_create_sample
sign_in :user, user
return redirect_to(stored_location_for(:user) || welcome_users_path)
elsif params["user_id"].present?
begin
user = User.find(params["user_id"])
sign_in :user, user
rescue ActiveRecord::RecordNotFound
flash[:failure] = t('auth.user_not_found', :user_id => params['user_id'])
end
return redirect_to(stored_location_for(:user) || welcome_users_path)
end
end

if auth = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'].to_s)
Expand Down Expand Up @@ -91,10 +101,4 @@ def destroy
flash[:success] = t('success.account_removed', {:account => OmniAuth::Utils.camelize(@authentication.provider)})
redirect_to home_users_path
end

protected

def login_as_sample_user?
Rails.env == "development" && params["username"] == "sample" && request.env["omniauth.auth"].nil?
end
end
1 change: 1 addition & 0 deletions app/controllers/companies_controller.rb
Expand Up @@ -4,6 +4,7 @@ class CompaniesController < InheritedResources::Base
:resource => [:join, :leave]

before_filter :authenticate_user!, :except => [:index, :show, :tag]
before_filter :redirect_historical_slugs

def tag
@tag = params[:tag]
Expand Down
1 change: 1 addition & 0 deletions app/controllers/groups_controller.rb
Expand Up @@ -4,6 +4,7 @@ class GroupsController < InheritedResources::Base
:resource => [:join, :leave]

before_filter :authenticate_user!, :except => [:index, :show, :tag]
before_filter :redirect_historical_slugs

def tag
@tag = params[:tag]
Expand Down
1 change: 1 addition & 0 deletions app/controllers/people_controller.rb
Expand Up @@ -8,6 +8,7 @@ class PeopleController < InheritedResources::Base
before_filter :require_owner_or_admin!, :only => [:edit, :update, :destroy]
before_filter :pick_photo_input, :only => [:update, :create]
before_filter :set_user_id_if_admin, :only => [:update, :create]
before_filter :redirect_historical_slugs

def index
@view = :grid if params[:grid]
Expand Down
1 change: 1 addition & 0 deletions app/controllers/projects_controller.rb
Expand Up @@ -4,6 +4,7 @@ class ProjectsController < InheritedResources::Base
:resource => [:join, :leave]

before_filter :authenticate_user!, :except => [:index, :show, :tag]
before_filter :redirect_historical_slugs

def tag
@tag = params[:tag]
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/users_controller.rb
Expand Up @@ -35,6 +35,8 @@ def welcome
@possible_duplicates += Person.unclaimed.where(name_part_query, *name_parts.map{|p| "%#{p}%"}).take(10)

@possible_duplicates.uniq!

@person.send(:set_slug)
end
end
end
Expand Down
4 changes: 0 additions & 4 deletions app/helpers/authentications_helper.rb
Expand Up @@ -18,8 +18,4 @@ def auth_label(authentication)
end
t('misc.name_on_provider',{:name => name, :provider => provider_name(authentication.provider)})
end

def display_sign_in_as_sample?
Rails.env == "development"
end
end
8 changes: 8 additions & 0 deletions app/helpers/people_helper.rb
Expand Up @@ -12,4 +12,12 @@ def people_list_title(list, tag)
t("people.index.#{title_key}.other", :tag => tag).strip
)
end

def display_record_controls?(person)
current_user && (current_user.admin? || current_user.person == person)
end

def display_login_as_control?(person)
person.user && (current_user != person.user) && allow_login_as_specific_user?
end
end
113 changes: 113 additions & 0 deletions app/mixins/customizable_slug.rb
@@ -0,0 +1,113 @@
# = CustomizableSlug
#
# This mixin provides a easy-to-use wrapper for setting up customizable slugs
# using the <tt>friendly_id</tt> plugin.
#
# @example To add a customizable slug whose default value is based on the :name
# field, add this to your ActiveRecord model:
#
# class MyModel
# customizable_slug_from :name
# end
#
# @example To use the customizable field:
#
# m = MyModel.new(:name => "foo")
#
# m.save
# m.slug # => "foo"
#
# m.custom_slug = "bar"
# m.save
# m.slug # => "bar"
module CustomizableSlug

# Override behavior of gem.
require 'friendly_id/slug_generator'
class CustomizableSlugGenerator < FriendlyId::SlugGenerator
# When generating the slug, if there's a conflict, stop immediately and
# mark the record as an error so user can pick something else.
#
# The gem's original behavior is to always generate a unique slug, even if
# this means adding a number to the end of it. This is bad design because
# if the user wants to be "foo", but that's taken, they'll end up as
# "foo-2", rather than being told to pick another slug.
def generate
if conflict?
sluggable.errors.add(:custom_slug, I18n.t('activerecord.errors.messages.taken'))
end
return normalized
end

# Check history for conflicts, if using history.
#
# The gem's original behavior doesn't check history, so if you try to
# create a conflicting record, the #save will fail with a raw SQL
# uniqueness constraint error.
def conflicts
# If any regular conflicts are found, return them immediately.
scope = super
return scope if scope.count > 0

# If no regular conflicts are found, search the history.
if friendly_id_config.model_class.included_modules.include?(FriendlyId::History)
history = FriendlyId::Slug.where(:slug => normalized, :sluggable_type => self.sluggable.class.to_s)
unless self.sluggable.new_record?
# If record exists, exclude it from the history check.
history = history.where('sluggable_id <> ?', self.sluggable.id)
end

return history if history.count > 0
end

# No conflicts of any sort found.
return []
end
end

def self.included(base)
base.send(:extend, ::CustomizableSlug::ClassMethods)
end

module ClassMethods
# Managing customizable custom slug for +attribute+, e.g. :name.
def customizable_slug_from(attribute)
# Attribute which contains the source value to use for generating the
# slug, e.g. :name.
cattr_accessor :friendly_id_source_attribute
self.friendly_id_source_attribute = attribute

# Activate "friendly_id" plugin.
extend FriendlyId
friendly_id :custom_slug_or_source, :use => :history, :slug_generator_class => ::CustomizableSlug::CustomizableSlugGenerator

# Add validation for "custom_slug" field.
validate :validate_custom_slug
end
end

# Return the user-specified custom slug or the friendly id for this record.
def custom_slug
@custom_slug.presence || self.friendly_id
end

# Set the custom slug to +value+.
def custom_slug=(value)
@custom_slug = value
end

# Return the custom slug or the value of the attribute that contains the source value.
def custom_slug_or_source
@custom_slug.presence || "#{self.send(self.class.friendly_id_source_attribute)}"
end

def validate_custom_slug
# Ensure the slug starts with a letter, or Rails will run #to_i on it to
# get a number and find the record by numeric id rather than the slug. :(
#
# TODO Figure out what to do about slugs that really start with a digit, e.g. "37signals".
if @custom_slug.present? && @custom_slug !~ /^\D/
self.errors.add(:custom_slug, I18n.t('activerecord.errors.must_start_with_non_digit'))
end
end
end
2 changes: 2 additions & 0 deletions app/models/company.rb
Expand Up @@ -14,6 +14,8 @@ class Company < ActiveRecord::Base

import_image_from_url_as :logo

customizable_slug_from :name

has_many :company_projects
has_many :projects, :through => :company_projects

Expand Down
2 changes: 2 additions & 0 deletions app/models/group.rb
Expand Up @@ -14,6 +14,8 @@ class Group < ActiveRecord::Base

import_image_from_url_as :logo

customizable_slug_from :name

has_many :group_projects
has_many :projects, :through => :group_projects

Expand Down
3 changes: 3 additions & 0 deletions app/models/person.rb
Expand Up @@ -17,6 +17,8 @@ class Person < ActiveRecord::Base

import_image_from_url_as :photo, :gravatar => true

customizable_slug_from :name

belongs_to :user
accepts_nested_attributes_for :user, :update_only => true

Expand Down Expand Up @@ -77,6 +79,7 @@ def self.find_or_create_sample(create_backreference=true)
end
return person
end

end


Expand Down
2 changes: 2 additions & 0 deletions app/models/project.rb
Expand Up @@ -14,6 +14,8 @@ class Project < ActiveRecord::Base

import_image_from_url_as :logo

customizable_slug_from :name

has_many :project_memberships
has_many :people, :through => :project_memberships

Expand Down
4 changes: 4 additions & 0 deletions app/stylesheets/basic_mobile.sass
Expand Up @@ -607,3 +607,7 @@ body.events #content
line-height: 32px
margin-bottom: .5em
@extend .auth_backgrounded

.development_only
background: #F8D9D9
padding: 0.25em
4 changes: 2 additions & 2 deletions app/views/authentications/_login_form.html.haml
Expand Up @@ -3,7 +3,7 @@
= f.input :email, :label => t('field.email_address.label'), :input_html => {:size => nil, :autocorrect => "off", :autocapitalize => "off"}
= f.input :provider, :label=>t('field.provider.label') , :collection => Authentication::PROVIDER_OPTIONS, :include_blank => false
= f.commit_button t('button.sign_in')
- if display_sign_in_as_sample?
%p.development_only
- if allow_login_as_specific_user?
%p.development_only.login_as_sample_user
DEV:
= link_to t('button.login_as_sample_user'), authentications_path(:username => "sample"), :method => :post
1 change: 1 addition & 0 deletions app/views/companies/_form.html.haml
Expand Up @@ -2,6 +2,7 @@
= display_errors_for @company
= f.inputs do
= f.input :name
= render 'site/custom_slug_field', :form => f
= f.input :url
= f.input :address, :as => :string
= f.input :description
Expand Down
1 change: 1 addition & 0 deletions app/views/groups/_form.html.haml
Expand Up @@ -2,6 +2,7 @@
= display_errors_for @group
= f.inputs do
= f.input :name
= render 'site/custom_slug_field', :form => f
= f.input :url
= f.input :mailing_list
= f.input :description
Expand Down
2 changes: 1 addition & 1 deletion app/views/people/_form.html.haml
Expand Up @@ -3,7 +3,7 @@
= display_errors_for @person
= f.inputs do
= f.input :name
-# = f.input :twitter
= render 'site/custom_slug_field', :form => f
- unless @person.new_record?
= f.semantic_fields_for :user do |u|
= u.input :email
Expand Down
10 changes: 9 additions & 1 deletion app/views/people/show.html.haml
@@ -1,6 +1,8 @@
.single_record.person{:class => @person.photo.file? ? 'with_image' : 'without_image'}
= link_to image_tag(@person.photo.url(:medium), :class => 'person_photo'), photo_person_path(@person) if @person.photo.file?

%h1= @person.name

- if current_user && @person.user.blank? && current_person.nil?
#claim_user
%p= t('people.show.are_you_this_person', :name => @person.name)
Expand Down Expand Up @@ -53,6 +55,12 @@
%p= @person.mentee_topics

.record_actions
- if current_user && (current_user.admin? || current_user.person == @person)
- if display_record_controls?(@person)
= link_to t('button.edit'), edit_person_path(@person), :class => 'edit'
= link_to t('button.delete'), person_path(@person), :confirm => t('alert.are_you_sure'), :method => :delete, :class => 'delete'
- if display_login_as_control?(@person)
<br />
- if display_login_as_control?(@person)
%p.development_only.login_as_this_user
DEV:
= link_to t('button.login_as_this_user'), authentications_path(:user_id => @person.user.id), :method => :post
1 change: 1 addition & 0 deletions app/views/projects/_form.html.haml
Expand Up @@ -2,6 +2,7 @@
= display_errors_for @project
= f.inputs do
= f.input :name
= render 'site/custom_slug_field', :form => f
= f.input :url
= f.input :description
= f.input :tag_list, :as => :text, :input_html => {:class => 'tags'}
Expand Down
10 changes: 10 additions & 0 deletions app/views/site/_custom_slug_field.html.haml
@@ -0,0 +1,10 @@
-# Display the form field for a custom slug.
-#
-# ARGUMENTS:
-# * form: The Rails form instance.
- hint = "%{example} %{url}/<em><b>%{my}-%{model}</b></em>" % { |
:example => t('field.example.fragment'), |
:url => "#{SETTINGS.organization.url}/#{controller_path}", |
:my => t('field.my.fragment'), |
:model => t("activerecord.models.#{form.send(:model_name).underscore}").underscore }
= form.input :custom_slug, :hint => hint.html_safe

0 comments on commit 13f685f

Please sign in to comment.