You can have_one if you're true - ruby

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)

Related

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.)

Clean association definitions with Ruby Sequel

I am using Jeremy Evan's Sequel to populate an (SQLite) database with data I scrape from web pages.
The database involves a number of many_to_many relationships that I express with Associations.
The associations are created in class definitions, which are always evaluated when the script is run.
Importantly, the association class definitions need to have the necessary tables in place.
Thus the table creation methods should be in the top level with the association definitions.
Here is an example:
module Thing
db = Sequel.Sqlite('data.sqlite')
db.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
db.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
db.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
First of all, I think that this is a rather dirty solution, since my method calls and class definitions sit in the same namespace.
If I try to separate the class definitions, I get No database associated with Sequel::Model error (which makes sense, but I want to defer the evaluation of the association definitions, having those after the table calls, whenever they might happen).
I want to be able to create the tables and associations in a method call. Thus, I could for example pass the name of the new database file:
def create_tables_and_schema (database_name)
db = Sequel.Sqlite(database_name)
db.create_table... #three of those, as above
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
What I think is needed is a different way to express table relations.
Any suggestions on approach and style are appreciated. Please ask for clarifications if the explanation is confusing.
Your method calls and class definitions do not need to sit in the same namespace, it's just that the tables need to be created before the model classes. An easy way to separate them is to move the table creation to a separate file. Also, usually you assign the database object to a constant.
create_tables.rb:
DB.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
DB.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
DB.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
models.rb:
DB = Sequel.sqlite('data.sqlite')
require 'create_tables'
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
You mentioned that you want to create the tables and associations in a method call, but that doesn't make sense if you are creating classes with constants. The main reason to create them via a method call is to allow for multiple databases at runtime, but that wouldn't work with your model classes since they are defined with constant names.
If you don't need multiple databases at runtime, the example above should work if you just want to separate the table creation from the model creation.
If you do need multiple databases at runtime, then creating the tables and models via a method call makes sense, but you need to create anonymous model classes, as otherwise you will have problems.

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.)

Ruby on Rails 3: How can I sort ActiveRecords by an attribute of another table?

I need to query a database table and get the rows ordered by a count of an association. Is there a Rails (like Active Record Query) way to do this?
My models and their associations are as follows:
class User < ActiveRecord::Base
has_one :business
end
class Business < ActiveRecord::Base
has_many :postulations
end
class Postulation < ActiveRecord::Base
belongs_to :business
end
I need to get a number of Users ordered by the amount of Postulations that their Business has. Is there a clean way to do this or do I just have to query with find_by_sql?
Thank you.
User.includes(:business => :postulations).group("users.id").order("count(postulations.id) desc").limit(20)
This will probably work

How to Validate Rails 3 Relational Model?

New to Rails and ActiveRecord, not sure how I can apply validation though multiple levels of relational model. I've searched all the tutorials that I can find are all validation in just one model.
How can I validate multiple relational models ?
Say I have multiple models, one model called field stores different type of attribute in sports game.(ex. final score, goals for, goals against etc) and another model value that stores all the actual values like game scores into the field
So I will have 2 classes
class Field < ActiveRecord::Base
has_many :value
end
class Value < ActiveRecord::Base
belongs_to :field
end
How can I validate the value of each attributes in value model that are corresponding to it's field ?
In rails 3 : You can create a file in your lib directory (ensure that that this file is loaded by your application) and put in a class with a validate method :
class HumanValidator < ActiveModel::Validator
def validate(record)
record.errors[:base] << "This person is dead" unless check(human)
end
private
def check(record)
(record.age < 200) && (record.age > 0)
end
end
In all model you want to validate, you invoke the validation with the of the class like this:
# app/models/person.rb
class Customer < ActiveRecord::Base
validates_with HumanValidator
end
Go read this, for validate novelty in rails 3, soure link text

Resources