I have a problem with globalize and friendly_id. The site has 2 languages Ru and En. Gem friendly_id, globalize and friendly_id-globalize configured and work. If I change the language from Russian to English, all is well:
http://127.0.0.1:3000/ru/o-saite -> http://127.0.0.1:3000/en/about-site
But when I change from English to Russian going wrong redirection:
http://127.0.0.1:3000/en/about-site -> http://127.0.0.1:3000/ru/about-site
page model:
class Page < ActiveRecord::Base
validates :title, :content, :slug, presence: true
validates :slug, uniqueness: true
validates :title, length: { minimum: 3, maximum: 255 }
validates :content, length: { minimum: 5 }
# globalize
translates :title, :content, :slug
# FriendlyId
extend FriendlyId
friendly_id :slug_candidates, use: [:slugged, :finders, :globalize]
def slug_candidates
[
:title,
[:title, :id]
]
end
def should_generate_new_friendly_id?
title_changed?
end
def normalize_friendly_id(string)
title.to_s.to_slug.normalize(transliterations: :russian).to_s
end
end
migration:
class TranslatePage < ActiveRecord::Migration
def self.up
Page.create_translation_table!({
title: :string,
content: :text,
slug: :string
}, {
migrate_data: true
})
end
def self.down
Page.drop_translation_table! migrate_data: true
end
end
from application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.i18n.default_locale = :ru
config.i18n.fallbacks = true
page controller
class PagesController < ApplicationController
before_action :load_page, only: [:show]
def show
end
private
def load_page
#page = Page.friendly.find(params[:id])
redirect_to action: action_name, id: #page.friendly_id, status: 301 unless #page.friendly_id == params[:id]
end
def page_params
params.require(:page).permit(:title, :content,:slug, :published)
end
end
what could be the problem?
SOLVED?
Problem was in the views. In layouts/aplication.html.slim was:
ul class='change_lang'
li = link_to_unless I18n.locale == :en, "EN", locale: :en
li = link_to_unless I18n.locale == :ru, "RU", locale: :ru
Now in pages/show.slim
- content_for :change_lang do
li
- link = I18n.with_locale(:ru){page_path(#page, locale: 'ru')}
= link_to 'RU', link
li
- link = I18n.with_locale(:en){page_path(#page, locale: 'en')}
= link_to 'EN', link
In layouts/aplication.html.slim
ul class='change_lang'
- if content_for?(:change_lang)
= yield :change_lang
- else
li = link_to_unless I18n.locale == :en, "EN", locale: :en
li = link_to_unless I18n.locale == :ru, "RU", locale: :ru
https://github.com/norman/friendly_id-globalize/issues/7
There are more minimalistic approach. But this method server is hung.
http://www.leanpanda.com/blog/2015/09/12/alternate-sitemap/
Redirect same page to different language with Globalize & Friendly_id
i've a generic answer.
please let me now if it works for you.
if you have a namespace you can set up in :shop, if not just url_for(obj)
in your application_helper.rb
def languages(obj=nil)
content_for(:switch_locale) do
I18n.available_locales.each do |locale|
I18n.with_locale(locale) do
concat(
if obj
content_tag(:li, (link_to locale, url_for([:shop, obj]) ))
else
content_tag(:li, (link_to locale, url_for(locale: locale.to_s) ))
end
)
end
end
end
end
in your views
- languages #category
or simply
- languages
in application.html.(erb/haml.slim) or wherever you want to render the translations.
=yield(:switch_locale)
Related
I get this error on my posts index page :
This the model :
class Post < ApplicationRecord
include Filterable
belongs_to :region
belongs_to :category
belongs_to :topic
validates :title, presence: true, length: { maximum: 500 }
validates :content, presence: true
validates :published_at, presence: true
translates :title, :content, :slug, touch: true, fallbacks_for_empty_translations: true
has_attached_file :image, styles: { thumb: "100x70#", featured: "1560x868#", small: "760x868#", big: ">1600x1600" }
validates_attachment :image, content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] }
validates_attachment_presence :image
scope :published, -> (published) { where(published: (['true', true].include? published)).order(featured: :desc, published_at: :desc) }
scope :published_until_now, -> { where("published_at < ?", Time.now).merge(Post.published(true)) }
scope :topic, -> (topic_id) {
joins(:topic).where('topic_id = ?', topic_id) }
scope :category, -> (post_category) {
joins(:category).where('category_id = ?', post_category) }
scope :match, -> (search_term) {
with_translations(I18n.locale).where('content like ? or title like ?', "%#{search_term}%", "%#{search_term}%") }
self.per_page = 10
after_save :unfeature_older_posts, if: Proc.new { |post| post.featured? }
extend FriendlyId
friendly_id :title, use: :globalize
def unfeature_older_posts
featured_posts = Post.where(featured: true).where.not(id: id).order(published_at: :desc)
if featured_posts.size == 1
featured_posts.last.update(featured: false)
end
end
end
This the controller :
class PostsController < ApplicationController
before_action :get_pages_tree, :get_privacy_policy, only: [:index, :show]
def index
#filters = params.slice(:topic, :category)
#posts = Post.published_until_now
.filter(#filters)
.paginate(:page => params[:page], per_page: 11)
end
def show
#post = Post.friendly.find(params[:id])
end
end
and filter is defined here :
module Filterable
extend ActiveSupport::Concern
module ClassMethods
def filter(filtering_params)
results = self.where(nil)
filtering_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end
results
end
end
end
I'm not sure where to go from here. I recently upgraded to Ruby on Rails 5 and Ruby 2.7.0, I don't know if it's related.
Try replacing module ClassMethods with class_methods do.
If it works, then please keep in mind:
filter method comes from Ruby. It's defined in Array. As you can see in the doc, filter method on Array takes no argument. That's the direct cause of the error you see.
In Rails, when methods on Array are called on ActiveRecord object (in your case, Post.published_until_now) and when methods cannot be found on a model, it automatically converts itself into an Array. So, it calls filter method on Array. Generally, you don't want to define methods such as filter which is confusing.
model and view not have attributes when i run "rails g account_scaffold account/student name:string age:integer".
Code as follows:
lib/generators/account_scaffold/account_scaffold_generators.rb:
require 'rails/generators/resource_helpers'
require "rails/generators/model_helpers"
module Rails
module Generators
class AccountScaffoldGenerator < Rails::Generators::NamedBase
include Rails::Generators::ModelHelpers
include Rails::Generators::ResourceHelpers
check_class_collision suffix: "Controller"
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
class_option :disable_common, type: :boolean, default: false, desc: 'SETS: --no-helper --no-view-specs --no-request-specs --no-routing-specs'
hook_for :orm, :in => :rails, :required => true, :as => :model do |invoked|
invoke invoked, [singular_name]
end
def create_controller_files
if #options[:disable_common]
#options = #options.merge(helper: false)
end
template "controller.rb", File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb")
end
hook_for :template_engine, :test_framework, as: :scaffold do |invoked|
controller_name_param = controller_class_path.blank? ? singular_name : (controller_class_path.join("/") + "/" + singular_name)
if #options[:disable_common]
invoke invoked, [ controller_name_param ], {view_specs: false, request_specs: false, routing_specs: false}
else
invoke invoked, [ controller_name_param ]
end
end
hook_for :helper, as: :scaffold do |invoked|
invoke invoked, [ controller_name ]
end
hook_for :assets do |assets|
invoke assets, [controller_name]
end
hook_for :resource_route do |route|
invoke route, [controller_name]
end
end
end
end
lib/templates/slim/scaffold/_form.html.slim
= simple_form_for(#<%= singular_name %>) do |f|
.form-inputs
<%- attributes.each do |attribute| -%>
= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %>
<%- end -%>
.form-actions
= f.button :submit
The cleanest way I find to solve it was to override the initializer. I have the following generator in my app:
module Rails::Generators
class GrapesGenerator < ::Rails::Generators::ScaffoldGenerator
attr_reader :fields
def initialize(args, *options) #:nodoc:
#fields = args[1..-1]
super
end
hook_for :orm, as: :model do |instance, orm|
instance.invoke orm, [ instance.options[:model_name], instance.fields ]
end
end
end
I have created a simple before_validation:
before_validation :strip_tabs
def strip_tabs
end
In my class I want to loop through all my attributes and remove tabs from each value. Most posts I found on SO are people who want to set 1 attribute. But I want to edit all my values.
Question:
How can I loop all self attributes of a model and edit them.
Friend suggested this, but content_column_names does not exist:
self.content_column_names.each {|n| self[n] = self[n].squish}
UPDATE 1: More code:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
attr_accessor \
:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang
validates :e_mail, :email => {:strict_mode => true}
validate :validate_telephone_number
validate :age_is_min_17?
before_validation :strip_tabs
def strip_tabs
binding.remote_pry
end
def age_is_min_17?
birthdate_visible = PersonalField.not_hidden.find_by_name "BIRTHDATE"
if birthdate_visible && birthdate && birthdate > (Date.current - 17.years)
#errors.add(:birthdate, I18n.t("apply.errors.birthdate"))
end
end
def validate_telephone_number
telephone_visible = PersonalField.not_hidden.find_by_name "TELEPHONE"
telephone_current = telephone.dup
if telephone_visible && telephone_current && !telephone_current.empty?
if telephone_current[0] == '+' || telephone_current[0] == '0'
telephone_current[0] = ''
#errors.add(:telephone, I18n.t("apply.errors.telephone")) if !telephone_current.is_number?
else
#errors.add(:telephone, I18n.t("apply.errors.telephone"))
end
end
end
def initialize(hash)
simple_attributes = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
simple_attributes.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end
Update 2: Rails Version:
I'm using Rails '3.2.17'
You can do as following:
before_validation :strip_tabs
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.squish.presence
end
end
But I think that .squish will not work on created_at, updated_at, id, ... Because they are not String!
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.kind_of?(String) ? value.squish.presence : value
end
end
Since your class is not a Rails model (ActiveRecord::Base), you can do as following:
def strip_tabs
self.instance_variables.map do |attr|
value = self.instance_variable_get(attr)
value = value.squish if value.kind_of?(String)
self.instance_variable_set(attr, value)
end
end
This should work
def strip_tabs
self.attributes.each do |attr_name, attr_value|
modified_value = ... # calculate your modified value here
self.write_attribute attr_name, modified_value
end
end
Because it's not an ActiveRecord model you won't have attributes or column_names, but you already have an array of your attribute names in your initialize function. I would suggest making that into a constant so you can access it throughout the model:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
SIMPLE_ATTRIBUTES = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
attr_accessor *SIMPLE_ATTRIBUTES
before_validation :strip_tabs
def strip_tabs
SIMPLE_ATTRIBUTES.each{ |attr| self[attr] = self[attr].squish }
end
...
def initialize(hash)
SIMPLE_ATTRIBUTES.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end
I'm using friendly_id 5.0.0.rc1, and also active_admin.
It would appear everything is working perfectly as expected, except for the fact that updating a record's slug attribute/column in active_admin does not do anything (it keeps it the same)
I find the same behavior just using console:
p = Post.first
p.slug
#=> 'test'
p.slug = 'another-test'
p.save
#=> true
p.slug
#=> 'test
My config:
FriendlyId.defaults do |config|
config.use :reserved
config.reserved_words = %w(admin new edit index session users register)
config.use :finders
config.use :slugged
config.slug_column = 'slug'
config.sequence_separator = '-'
config.use Module.new {
def should_generate_new_friendly_id?
slug.blank? || slug_changed?
end
}
end
My model:
class Post < ActiveRecord::Base
default_scope { order('created_at DESC') }
validates :title, presence: true
validates :body, presence: true
validates :views, presence: true, numericality: { only_integer: true }
extend FriendlyId
friendly_id :title, use: [:slugged, :history]
end
my controller:
class PostsController < ApplicationController
def index
#posts = Post.all.page(params[:page]).per(10)
end
def show
#post = Post.find_by_slug!(params[:id])
if request.path != post_path(#post)
redirect_to #post, :status => :moved_permanently and return
else
#post.increment :views if #post
end
end
end
Thanks!
Usually when using friendly id, you never update the slug manually. Instead:
def should_generate_new_friendly_id?
slug.blank? || title_changed?
end
And then every time you change the title, it will automatically update the slug.
more exactly, you should use self.title_changed?
def should_generate_new_friendly_id?
slug.blank? || self.title_changed?
end
Incase anyone else lands here and just need to change a slug:
p = Post.first
p.slug
#=> 'test'
tmp_title = p.title
p.title = 'another-test'
p.slug = nil
p.save
#=> true
p.title = tmp_title
p.slug
#=> 'another-test'
I'm about to translate all time zones to Russian and I've done such things:
model:
# lib/i18n_time_zone.rb
class I18nTimeZone < ActiveSupport::TimeZone
def self.all
super.map { |z| create(z.name, z.utc_offset) }
end
def to_s
translated_name = I18n.t(name, :scope => :timezones, :default => name)
"(GMT#{formatted_offset}) #{translated_name}"
end
end
view:
<%= time_zone_select :user, :time_zone, nil, :model => I18nTimeZone %>
locale file (/config/locales/ru.yml):
ru:
timezones:
"Midway Island": "Мидуэй"
"Samoa": "Самоа"
....
But there are cases when original string includes some dots (".") Like "St. Petersburg"
And I18n.t() tells me that translation is missing.
How can I avoid it?
Just remove the dot for the translation keys.
def to_s
translated_name = I18n.t(key, :scope => :timezones, :default => name)
"(GMT#{formatted_offset}) #{translated_name}"
end
def key
#key ||= name.gsub(/\./, "")
end
ru:
timezones:
"Midway Island": "Мидуэй"
"Samoa": "Самоа"
"St Petersburg" : "some one translate this"