Paper Trail Gem: How to use a custom item_type for specific model - ruby

Is there an option to configure the item_type for a version? I have a class Post, and the default item_type for that would be Post; is there an option to configure that be Foo?
UPDATE with example:
class Post < ActiveRecord::Base
has_paper_trail
end
post = Post.create # this creates a new post along with a version.
version = post.versions.first
version.item_type = 'Post' # Item type here is the name of the base model 'Post'.

Is there an option to configure the item_type for a version? I have a class Post, and the default item_type for that would be Post; is there an option to configure that be Foo?
No. When you call has_paper_trail, it adds a polymorphic association named item. Therefore, PaperTrail does not control the database column item_type, ActiveRecord does.
Here is the definition of the has_many association:
has_many(
versions_association_name, # Usually "versions"
-> { order(model.timestamp_sort_order) }, # Usually "created_at"
class_name: version_class_name, # Usually "PaperTrail::Version"
as: :item # Specifies a polymorphic interface
)

Just stumbled across this, realise its a bit old now but thought I'd share my solution as I had the same requirement. This is only a partial solution but it works me
I have extended the PaperTrail Version class and overriden the item_type getter
class AuditTrail < PaperTrail::Version
xss_foliate :except => [:object, :object_changes]
def item_type
if(self[:item_type] == "SomethingIWantToChange")
return "Different String"
end
return self[:item_type]
end
end
I then have set each of my models to use this class like so:
has_paper_trail :class_name => 'AuditTrail'
Then I can query the versions table and objects returned will run through the overridden getter and the item_type will be as I require:
audit_records = AuditTrail.where(someproperty: "something")
So this doesn't actually alter it in the DB when it is written, but is the best way I could find to present it differently to my frontend
Note that it doesn't alter the item_type if you fetch versions without using a query from your extended object i.e:
Someobject.find(1).versions.last
^ this still returns the item_type from the DB

You want the class_name option. This should work with your example:
class Post < ActiveRecord::Base
has_paper_trail :class_name => 'Foo'
end
This will version the Post class as Foo.
You can find more details in the Paper Trail documentation in the Custom Version Classes section.

Related

Ruby on Rails inheritance? [duplicate]

How to implement inheritance with active records?
For example, I want a class Animal, class Dog, and class Cat.
How would the model and the database table mapping be?
Rails supports Single Table Inheritance.
From the AR docs:
Active Record allows inheritance by
storing the name of the class in a
column that by default is named "type"
(can be changed by overwriting
Base.inheritance_column). This means
that an inheritance looking like this:
class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end
When you do Firm.create(:name =>
"37signals"), this record will be
saved in the companies table with type
= "Firm". You can then fetch this row again using Company.find(:first, "name
= ‘37signals’") and it will return a Firm object.
If you don‘t have a type column
defined in your table, single-table
inheritance won‘t be triggered. In
that case, it‘ll work just like normal
subclasses with no special magic for
differentiating between them or
reloading the right type with find.
A pretty good tutorial is here: http://juixe.com/techknow/index.php/2006/06/03/rails-single-table-inheritance/
Models:
class Animal < ActiveRecord::Base; end
class Dog < Animal; end
class Cat < Animal; end
Migration:
class CreateAnimals < ActiveRecord::Migration
def self.up
create_table :animals do |t|
# Other attributes...
t.string :type
end
end
def self.down
drop_table :animals
end
end
ActiveRecord supports mapping inheritance hierarchies to a single table(Single-table inheritance. Table would have a column type which stores name of actual class and is used to select other class-specific columns.
It is possible to implement multi-table inheritance mapping, as shown here, but this particular way is not portable, AFAIK.
Delegated Types
One particular way of doing this is via Delegated Types - this makes sense only if you want to paginate all animals together, and to view cats and dogs together, then the delegated type is particularly useful. I also like it because you don't need to have empty columns, for where it doesn't make sense, as is the case with Single Table Inheritance solutions.
# Schema: entries[ id, created_at, updated_at, animalable_type, animalable_id ]
class Animal < ApplicationRecord
delegated_type :animalable, types: %w[ Cat Dog ]
end
module Animalable
extend ActiveSupport::Concern
included do
has_one :animal, as: :animalable, touch: true
end
end
# Schema: cats[ id, selfishness_level ]
class Cat < ApplicationRecord
include Animalable
end
# Schema: dogs[ id, favourite_game, wag_tail_level ]
class Dog < ApplicationRecord
include Animalable
end

Join query in Hanami-model

Is it possible to create join query in subclass of Hanami::Repository?
I found that this pull request implements this feature but I can't find it in current codebase.
Hanami model based on rom, that's why you can use Relation#join method with a needful relation.
For this you need to call join method for one relation and set other relation as an attribute:
class PostRepository < Hanami::Repository
associations do
has_many :comments
end
# ...
def join_example(date_range)
posts # => posts relation
comments # => comments relation
posts
.join(comments) # set relation object here
.where(comments[:created_at].qualified => date_range)
.as(Post).to_a
end
end
And that's all.
Some helpful links:
rom-sql tests for left_join
A real example

You can have_one if you're true

In my website (written with sinatra) I am trying to set up a database. I have 2 tables, here referred to as Table1 and Table2.
models.rb
class Table1 < ActiveRecord::Base
Table1.where(bool:true) has_one :table2 # PSUDO-CODE
# So that every record where bool:true has the relationship
# but every record where bool:false or bool:nil doesn't
end
class Table2 < ActiveRecord::Base
belongs_to :table1
end
I am trying to find a way to make the section labeled PSUDO-CODE into actual code. How can I do that?
You can't do this directly: a class either has a relationship or it doesn't (although of course there may be no associated record)
You can set conditions on an association, but to the best of my knowledge you can only really set conditions on the associated collection (i.e. table 2 in this case)
You can however override the generated method, so for example
class Table1 < ActiveRecord::Base
has_one :table2
def table2(*args)
bool ? super : nil
end
end
This works with current versions of activerecord - not how far back this is supported (older version defined the association methods directly on the class so you couldn't call super)

Make friendly_id scope play nice with subclassed ActiveRecord model

I have a subclassed ActiveRecord model which uses a separate table to store records and friendly_id (4.1.0.beta.1) to generate slugs. Problem is friendly_id is using the parent class's table to check for existing slugs, instead of using the child table. Basically I'd like friendly_id to scope its checks to the right table.
Example:
class Parent
friendly_id :name, :use => :slugged
end
class Child < Parent
self.table_name = 'children'
end
Parent.create(name: 'hello').slug
> 'hello'
Child.create(name: 'hello').slug
> 'hello--2'
I want friendly_id to generate the 'hello' slug for the second create, because there are no records in the children table with that slug. Is there a way to configure or monkey patch the class friendly id uses for its queries?
EDIT: added friendly_id version for future reference
I'm posting my own solution to this problem, just in case someone is having the same problem. I should reiterate that this problem was found on version 4.1.0.beta.1 of the friendly_id gem (which at the time was the most recent version), so this issue may not occur any more.
To solve this problem, I basically configured slug_generator_class to use my own class, so I could monkey patch the culprit method.
In my model:
friendly_id do |config|
config.slug_generator_class = SubclassScopableSlugGenerator
end
In an initializer, I overrode the FriendlyId::SlugGenerator.conflicts method so I could access the sluggable_class var:
# Lets a non-STI subclass of a FriendlyId parent (i.e. a subclass with its
# own dedicated table) have independent slug uniqueness.
class SubclassScopableSlugGenerator < FriendlyId::SlugGenerator
private
def conflicts
# this is the only line we're actually changing
sluggable_class = friendly_id_config.model_class
pkey = sluggable_class.primary_key
value = sluggable.send pkey
base = "#{column} = ? OR #{column} LIKE ?"
# Awful hack for SQLite3, which does not pick up '\' as the escape character without this.
base << "ESCAPE '\\'" if sluggable.connection.adapter_name =~ /sqlite/i
scope = sluggable_class.unscoped.where(base, normalized, wildcard)
scope = scope.where("#{pkey} <> ?", value) unless sluggable.new_record?
length_command = "LENGTH"
length_command = "LEN" if sluggable.connection.adapter_name =~ /sqlserver/i
scope = scope.order("#{length_command}(#{column}) DESC, #{column} DESC")
end
end

Mongoid association & null object pattern?

How would you implement the null object pattern on a Mongoid relation?
Class Owner
include Mongoid::Document
embeds_one :preference
end
Most owners won't have a preference, and thus I want them to have a NullPreference instead, as described in Ben Orenstein's excellent talk.
What I would like is something like this:
class NullPreference
def name
'no name'
end
end
owner = Owner.new
preference = owner.preference
preference.name
=> 'no name'
I found a related question regarding the same thing in ActiveRecord, no answers though.
Edit: I'm using Mongoid 2.6 otherwise I could've used autobuild: true and get a real Preference and use the defaults instead.
An obvious way is to build a layer of abstraction over that field.
class Owner
include Mongoid::Document
embeds_one :preference_field # internal field, don't use directly
def preference
preference_field || NullPreference.new
end
def preference= pref
self.preference_field = pref
end
end
Maybe there are simpler ways.

Resources