GithubHelp home page GithubHelp logo

openscript / awesome_hstore_translate Goto Github PK

View Code? Open in Web Editor NEW
17.0 4.0 9.0 85 KB

Using PostgreSQLs hstore datatype to provide model data translation with ActiveRecord.

License: MIT License

Ruby 96.87% Shell 0.45% Dockerfile 2.68%

awesome_hstore_translate's Introduction

Awesome Hstore Translate

Gem Version

This gem uses PostgreSQLs hstore datatype and ActiveRecord models to translate model data. It is based on the gem hstore_translate by Rob Worely. An alternative is json_translate.

  • Works with Rails 5 and 6
  • No extra columns or tables needed to operate
  • Clean naming in the database model
  • Everything is well tested

Features

  • v0.1.0 Attributes override / Raw attributes
  • v0.1.0 Fallbacks
  • v0.1.0 Language specific accessors
  • v0.2.0 Awesome Hstore Translate as drop in replace for hstore_translate
    • with_[attr]_translation(str) is not supported
  • v0.2.2 Support record selection via ActiveRecord (e. g. where, find_by, ..)
  • v0.3.0 Support record ordering via ActiveRecord order
  • backlog Support friendly_id (see friendly_id-awesome_hstore gem)

Requirements

  • ActiveRecord >= 5
  • I18n

Installation

Add this line to your application's Gemfile:

gem 'awesome_hstore_translate'

And then execute:

$ bundle

Or install it yourself as:

$ gem install awesome_hstore_translate

Usage

Use translates in your models, to define the attributes, which should be translateable:

class Page < ActiveRecord::Base
  translates :title, :content
end

Make sure that the datatype of this columns is hstore:

class CreatePages < ActiveRecord::Migration
  def change
    # Make sure you enable the hstore extenion
    enable_extension 'hstore' unless extension_enabled?('hstore')

    create_table :pages do |t|
      t.column :title, :hstore
      t.column :content, :hstore
      t.timestamps
    end
  end
end

Use the model attributes per locale:

p = Page.first

I18n.locale = :en
p.title # => English title

I18n.locale = :de
p.title # => Deutscher Titel

I18n.with_locale :en do
  p.title # => English title
end

The raw data is available via the suffix _raw:

p = Page.new(:title_raw => {'en' => 'English title', 'de' => 'Deutscher Titel'})

p.title_raw # => {'en' => 'English title', 'de' => 'Deutscher Titel'}

Translated attributes:

Page.translated_attribute_names # [:title]

Fallbacks

It's possible to fall back to another language, if there is no or an empty value for the primary language. To enable fallbacks you can set I18n.fallbacks to true or enable it manually in the model:

class Page < ActiveRecord::Base
  translates :title, :content, fallbacks: true
end

Set I18n.default_locale or I18n.fallbacks to define the fallback:

I18n.fallbacks.map(:en => :de) # => if :en is nil or empty, it will use :de

p = Page.new(:title_raw => {'de' => 'Deutscher Titel'})

I18n.with_locale :en do
  p.title # => Deutscher Titel
end

It's possible to activate (with_fallbacks) or deactivate (without_fallbacks) fallbacks for a block execution:

p = PageWithoutFallbacks.new(:title_raw => {'de' => 'Deutscher Titel'})

I18n.with_locale(:en) do
  PageWithoutFallbacks.with_fallbacks do
    assert_equal('Deutscher Titel', p.title)
  end
end

Accessors

Convenience accessors can be enabled via the model descriptor:

class Page < ActiveRecord::Base
  translates :title, :content, accessors: [:de, :en]
end

It's also make sense to activate the accessors for all available locales:

class Page < ActiveRecord::Base
  translates :title, :content, accessors: I18n.available_locales
end

Now locale-suffixed accessors can be used:

p = Page.create!(:title_en => 'English title', :title_de => 'Deutscher Titel')

p.title_en # => English title
p.title_de # => Deutscher Titel

Translated accessor attributes:

Page.translated_accessor_names # [:title_en, :title_de]

Find

awesome_hstore_translate patches ActiveRecord, so you can conviniently use where and find_by as you like.

Page.create!(:title_en => 'English title', :title_de => 'Deutscher Titel')
Page.create!(:title_en => 'Another English title', :title_de => 'Noch ein Deutscher Titel')

Page.where(title: 'Another English title')  # => Page with title 'Another English title'

Order

awesome_hstore_translate patches ActiveRecord, so you can conviniently use order as you like.

Page.create!(:title_en => 'English title', :title_de => 'Deutscher Titel')
Page.create!(:title_en => 'Another English title', :title_de => 'Noch ein Deutscher Titel')

Page.all.order(title: :desc)  # => Page with title 'English title'

Limitations

awesome_hstore_translate patches ActiveRecord, which create the limitation, that a with where chained first_or_create and first_or_create! doesn't work as expected. Here is an example, which won't work:

Page.where(title: 'Titre français').first_or_create!

A workaround is:

Page.where(title: 'Titre français').first_or_create!(title: 'Titre français')

The where clause is internally rewritten to WHERE 'Titre français' = any(avals(title)), so the title: 'Titre français' is not bound to the scope.

Upgrade from hstore_translate

  1. Replace the hstore_translate with awesome_hstore_translate in your Gemfile
  2. Activate accessors, if you used the hstore_translate accessors
  3. Replace with_[attr]_translation(str) with equivalents (see "Support record selection via ActiveRecord" feature)

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

awesome_hstore_translate's People

Contributors

edouard-per-angusta avatar galetahub avatar github-actions[bot] avatar mayazcherquoi avatar openscript avatar roman-wb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

awesome_hstore_translate's Issues

Make the gem dependant from pq

The gem only works with pq, because it patches ActiveRecord in a way, that other databases wont work anymore. Furthermore 'hstore' is a PostgreSQL specific thing.

`find_by` won't pass the correct args to `super`

In active_record/core.rb:

        def find_by(*args)
          attrs = args.first
          if attrs.is_a?(Hash) && contains_translated_attributes(attrs)
            where(attrs).limit(1).first
          else
            super(args)
          end
        end

The line:

super(args)

Should use the splat operator:

super(*args)

Or even simpler:

super

Otherwise args are squashed into a single array.

Incompatibility with Rails 6.1

The definition for the where method in Rails 6.1 is the next one

   def where(*args)
      if args.empty?
        WhereChain.new(spawn)
      elsif args.length == 1 && args.first.blank?
        self
      else
        spawn.where!(*args)
      end
    end

    def where!(opts, *rest) # :nodoc:
      self.where_clause += build_where_clause(opts, rest)
      self
    end

The definition for this library is the next one:

 def where(opts = :chain, *rest)
        if opts.is_a?(Hash)
          query = spawn
          translated_attrs = translated_attributes(opts)
          untranslated_attrs = untranslated_attributes(opts)

          unless untranslated_attrs.empty?
            query.where!(untranslated_attrs, *rest)
          end

          translated_attrs.each do |key, value|
            if value.is_a?(String)
              query.where!(":value = any(avals(#{key}))", value: value)
            else
              super
            end
          end

          query
        else
          super
        end
      end

We need to find a way to fix this incompatibility in terms of parameters to make it work in Rails 6.1 and next.

Fallback logic is incorrect with accessors

I believe fallback logic is incorrect:

Given

class Entity
  translates :title, accessors: [:en, :es]
end

entity.title_en = "english"
entity.title_es = nil

Expected

I18n.locale = :en
entity.title 
=> "english" # correct

I18n.locale = :es
entity.title 
=> "english" # correct: fallback to default_locale

entity.title_en
=> "english"

entity.title_es
=> nil # should not fall back

Actual

entity.title_es
=> "english" # incorrect

I believe this is incorrect and accessors should provide the real data otherwise it is impossible to edit models. Also this is how it is implemented in globalize_accessors.

In other words they should provide direct mapping to title_raw, this is what I did:

module Translates
  def translates(*args)
    super(*args)
    define_translated_accessors(*args)
  end

  private

  def define_translated_accessors(*attrs)
    attrs.each do |attr|
      I18n.available_locales.each do |locale|
        define_method "#{attr}_#{locale}=" do |value|
          write_translated_attribute(attr, value, locale)
        end

        define_method "#{attr}_#{locale}" do
          Hash(send("#{attr}_raw"))[locale.to_s]
        end
      end
    end
  end
end

Cant save record

Hi I have this issue after following the doc:
PG::InternalError: ERROR: Unexpected end of string : SELECT 1 AS one FROM "tags" WHERE "tags"."title" = $1 LIMIT $2

Sorting

Does the system allow for easy ordering?

eg. order(name: :asc) where name is a translated field? How well does it combine with fallbacks?

I did not see anything in the documentation, nor did I see anything in the code that would support this on first sight

Would it be hard to implement?

issue with first or create

Record.where(param: "param 1").first_or_create
NoMethodError: undefined method empty?' for nil:NilClass`

I believe it is this line :
if translations.has_key?(cur.to_s) && !translations[cur.to_s].empty?in instance_methods.rb

hash = {
  param_one: "param 1",
  param_two: "param 2"
}
hash.each do |param, title|
  record = Record.where(param: param.to_s)
  Record.create(param: param.to_s,   title_fr: title) unless record.present?
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.