Join query in Hanami-model - ruby

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

Related

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

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.

Activerecord/Datamapper - Have one child belong to many parents

How would you set up an activerecord/datamapper association for the following scenario:
A user creates a "bookshelf" which has many books(a book object just has an isbn that is used to query an api, and has_many review objects associated with it). Let's say Jack creates a "bookshelf" with a book object. Then, lets say that Jill creates a "bookshelf" with the same book object(it has the same id and the same reviews). The book object has the following code as of now:
class Book < ActiveRecord::Base
has_many :reviews
end
Then, when you view the page for a book (you click the link to it from the "bookshelf" created by Jack) you should see the same book object when you clicked the link to it from Jill's "bookshelf" (e.g. both "bookshelves" have a link to /books/23 because they have the same book object).
I have not been able to figure this out with the has_many association because that requires me to make a new book each time a user adds a book to their "bookshelf." I have trouble understanding the has_and_belongs_to_many relationship, is that what should be used here? I was not able to find any similar questions on SO, so any help is greatly appreciated.
I am using Rails 4 with Ruby 2.1.
Here is a drawing of what I would like to accomplish:
Drawing
Yes, you would have to define many-to-many relationship between a Bookshelf and a Book. There are two ways to achieve this in Rails:
Option 1) Use has_and_belongs_to_many
See guide
According to official documentation has_and_belongs_to_many association:
Specifies a many-to-many relationship with another class. This associates two classes via an intermediate join table. Unless the join table is explicitly specified as an option, it is guessed using the lexical order of the class names. So a join between Developer and Project will give the default join table name of “developers_projects” because “D” precedes “P” alphabetically.
So, your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_and_belongs_to_many :books
end
class Book < ActiveRecord::Base
has_and_belongs_to_many :bookshelves
has_many :reviews
end
Add a join table generation to your migrations:
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
end
end
end
This will create a books_bookshelves table in your database. The table will have no primary key. There would be two foreign keys to your models Book and Bookshelf.
So, if you call self.books in the context of an user's bookshelf, you will get a list of books in the bookshelf. Vice versa, calling self.bookshelves in the context of a book will return a set of bookshelves the book belongs to.
The problem with this approach is that every time you add a new book to the bookshelf a new record is created in the database. If you are okay with that, there is no easier option than using has_and_belongs_to_many association. Otherwise, I recommend you to go with the Option #2.
Option 2) Use has_many :through
Another option is to use has_many, :through association (see guide). You would have to define one more model to do that, but it might come handy in some use cases (see below for an example).
Your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_many :books, through: :books_bookshelves
has_many :books_bookshelves
end
class Book < ActiveRecord::Base
has_many :bookshelves, through: :books_bookshelves
has_many :books_bookshelves
has_many :reviews
end
class BooksBookshelf < ActiveRecord::Base
belongs_to :book
belongs_to :bookshelf
end
Probably the best thing about using has_many :through association is that it allows you to add custom columns to the join table (e.g. add column count to keep track how many books of the same type are there in the bookshelf).
The migration would look pretty much the same as the one we used in Option 1, except for the fact we are adding an unique constraint on the foreign keys (please note that adding the constraint is optional):
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
# add your custom columns here
end
add_index :books_bookshelves, [:book_id, :bookshelf_id], unique: true # to make sure you won't create duplicate records
end
end
By going with this approach, adding a new would be a bit more complicated as you would have to make sure you are not inserting duplicate records in the join table. (However, you may remove the unique constraint from the migration, to achieve exactly the same kind of behavior as you would get with has_and_belongs_to_many.)

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)

Is there a way in MongoMapper to achieve similar behavior as AR's includes method?

Is there a feature equivalent in MongoMapper to this:
class Model < ActiveRecord::Base
belongs_to :x
scope :with_x, includes(:x)
end
When running Model.with_x, this avoids N queries to X.
Is there a similar feature in MongoMapper?
When it's a belongs_to relationship, you can turn on the identity map and run two queries, once for your main documents and then one for all the associated documents. That's the best you can do since Mongo doesn't support joins.
class Comment
include MongoMapper::Document
belongs_to :user
end
class User
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
end
#comments = my_post.comments # query 1
users = User.find(#comments.map(&:user_id)) # query 2
#comments.each do |comment|
comment.user.name # user pulled from identity map, no query fired
end
(Mongoid has a syntax for eager loading, but it works basically the same way.)

Rails 3 scope only select certain attributes for a has_many relationship

This looks like it should be something pretty easy but I can't seem to get it to work. I have a model with a has_many relationship and I'd like a scope on the parent that allows me to select only certain attributes for each.
An example:
class Bakery < ActiveRecord::Base
has_many :pastries
scope :summary, select([:id, :name, 'some option calling pastries.summary'])
class Pastry < ActiveRecord::Base
belongs_to :bakery
scope :summary, select([:id, :image_url])
I'd like to be able to call something like Bakery.first.summary and get a Bakery model with only the id and name populated and for each pastry in it's pastries array to only have the id and image_url attributes populated.
You could do this, but it won't affect the SQL queries that are made as a result (assuming you're trying to optimise the underlying query?):
class Pastry
...
def summary
{
:id => self.id,
:image_url => self.image_url
}
end
end
class Bakery
...
def summary
pastries.collect {|i| i.summary }
end
end
This would then give you an array of hashes, not model instances.
ActiveRecord doesn't behave how you're expecting with models - it will fetch whatever data it thinks you need. You could look at using the Sequel gem instead, or executing a raw SQL query such as:
Pastry.find_by_sql("SELECT id, name from ...")
But this could give you unexpected behaviour.

Resources