Rails virtual model without serialization - ruby-on-rails-4

I am wondering, what would be the best option to implement a virtual model.
Let's say we have users, we would like them to have a gender attribute.
The easiest option would be to have a Gender model and then do a belongs_to :gender
Then, each time you ask for a user, it will also perform a SELECT on the genders table
Therefore, does anyone have an idea on how to implement a virtual model that would not be persisted in the database and just be a regular model with the ability through the belongs_to association to perform a select on Male or female ?
I know I could just use Serialization but I feel like it's a bit of a waste of memory/space/ressources to store a serialized object each time I create a user and would definitely love a solution where I can use the traditional gender_id:integer ...
Thank you for your help guys.

I suggest you use enum field (Rails 4.1+), which allows its value to be within a predefined set of values (you can enumerate them, hence the name). It is persisted as a plain integer in database, but convenience methods are generated for easily manipulating them:
class User < ActiveRecord::Base
enum gender: [ :male, :female ]
end
user.male!
user.female? # => false
user.gender # => "male"
User.male # => a collection of users with "male" specified in gender
Conversation.genders # => { "male" => 0, "female" => 1 }

Related

rails_admin get the current entity and display custom enum

I'm trying to customize an enum field with something else than only the name.
For example my entity record from database have columns as: name, postal_code, id etc ..
and I would like to have something like this in the dropdown "#{name} #{postal_code}, #{department}
I'm doing this:
field :city, :enum do
enum do
# Here I would like to get the entity from DB in order to have all
# columns to something similar as =>
entity.collect.each { |c| "#{c.name} (#{c.postal_code}), #
{c.department.name}"}
end
end
but I don't know how to get the active records (entity in my example) of the actual value of City entity.
How can I do this?
note: that department belongs to another model who is associated to City
Considering the OP comments i'm going to asume that the model in question has this association defined like this:
belongs_to :city
And the city field itself something like this
rails_admin do
edit do
field :city
end
end
Because this way rails admin will render a select drop down that will allow you to search the cities without loading them all.
After that on the City model you can define the title method, quoting the docs
By default it tries to call "name" or "title" methods on the record in question. If the object responds to neither, then the label will be constructed from the model's classname appended with its database identifier. You can add label methods (or replace the default [:name, :title]) with:
RailsAdmin.config {|c| c.label_methods << :rails_admin_title }
The title method can then be defined
class City < ApplicationRecord
def rails_admin_title
"#{self.name} (#{self.postal_code}), #{self.department.name}"
end
end
In a related issue, you will probably want to configure how rails admin searches for the city, i'll just link to the docs.
You can do it, but you need to define a name and a (stable)value for each select drop down, a simple hash out to do it like this:
field :city, :enum do
enum do
entity = bindings[:object]
City.all.map do |c|
{c.id => "#{c.name} (#{c.postal_code}), #{c.department.name}"}
end
end
end
Im assuming each city has an id, but you can use any value you want to store on that field on your DB.
So your users would see the nice formatted string, but rails admin would post the form with the city id on the city field.

When to use NullBooleanField in Django

I have a button that, when clicked, should save in the database that the user has drunk water. I just wanted to check whether NullBooleanField would be the correct way to define this.
A broader question that if answered would be useful to the community is a list of optimal circumstances under which to use NullBooleanField. But I'm not asking that here. Just in case you wanted a better challenge.
Thank you in advance.
The question you need to answer to find out whether you should use the BooleanField or the NullBooleanField is actually concerning the possible states of the value of the field you want to represent in your model:
2 possible states:
user has drunk water
user has not drunk water
→ use BooleanField
3 possible states:
user has drunk water
user has not drunk water
it is not known whether the user has or has not drunk water
→ use NullBooleanField.
UPDATE:
NullBooleanField is deprecated in version 3.1. Instead use BooleanField with null=True.
Django 2.1 introduced null=True for BooleanField. Using NullBooleanField is now discouraged.
So use, x = BooleanField(null=True) instead of x = NullBooleanField()
Here's a simple use case: If you only need to record the "Yes" or "No" status, use Boolean without null. But if you want to have 3 conditions say, "Yes", "No", and "Don't Know", use it with null=True.
I think you should use NullBooleanField only when you have three possible choices: Unknown, Yes (True) and No (False).
In your case you have only two possible values - Yes (user has drunk water) and No (user has NOT drunk water) so a BooleanField would be better.
One more reason to use a BooleanField in your case is because the default form widget for this field is a CheckboxInput (docs), while the default form widget for a NullBooleanField is a NullBooleanSelect (docs). And since you use a checkbox, a BooleanField would do the job better.
Take advantage of the NULL properties
I use it quite often when I need to enforce some specific constrains in my data, but allow some others. Multiple NULL values can coexist in a column defined UNIQUE. Let's take an address model implementation as an example:
The business rules are:
A user can have up to 1 billing address
A user can have multiple shipping addresses
One way to implement that is by making a single address table with a foreign key to the user and an extra flag that indicates if that address is a billing address or not:
class Address(models.Model):
... # <- address fields
user = models.ForeignKey(User, on_delete=models.CASCADE)
billing_address = models.NullBooleanField(default=None)
You can now simply enforce the business rules at a database level by making user and billing_address unique together.:
class Meta:
constraints = [
models.UniqueConstraint(
fields=['user', 'billing_address'],
name='ensure single billing address'
)
]
The trick to make this work is that the billing_address must be True when the address is a billing address but it should be None (instead of False) when the address is a shipping address.
You can further enforce the validation by adding another constraint to make sure that no False values are added. But this is usually not necessary and could be done at the application level:
class Meta:
constraints = [
models.UniqueConstraint(
fields=['user', 'billing_address'],
name='ensure single billing address'
),
models.CheckConstraint(
check=~Q(billing_address=False),
name='no False allowed'
)
]
Biggest advantage of using NullBooleanField for PostgreSQL database is your table won't be re-written, any new field without null=True will cause a table re-write, it's fine for small tables but could take a significant amount of time and resource for large tables and you won't be able to write to your table during a re-write

Arel: active relation from Arel::SelectManager with join

Let us we have a Rails 4.2.x app and we have two tables posts and authors, and we want to use Arel to get the posts authored by an author with name == 'Karl'.
(In this case we could be happy with Active Record joins but this is just to keep the example simple.)
posts = Arel::Table.new :posts
authors = Arel::Table.new :authors
my_query = posts.project(Arel.star)
.join(authors)
.on(posts[:author_id].eq(authors[:id]))
.where(authors[:name].eq('Karl'))
> my_query.class
=> Arel::SelectManager
Now we could get back an array (of class Array) of posts by doing:
> Post.find_by_sql my_query
[master] Post Load (3.1ms) SELECT * FROM "posts" INNER JOIN "authors"
ON "posts"."author_id" = "authors"."id"
WHERE "authors"."name" = 'Karl'
=> [#<Post:0x005612815ebdf8
id: 7474,
...
]
So we do get an array of posts, not an active record relation:
> Post.find_by_sql(my_query).class
=> Array
Also injecting the manager into Post.where won't work
> Post.where my_query
=> #<Post::ActiveRecord_Relation:0x2b13cdc957bc>
> Post.where(my_query).first
ActiveRecord::StatementInvalid: PG::SyntaxError:
ERROR: subquery must return only one column
SELECT "posts".* FROM "posts"
WHERE ((SELECT * FROM "posts" INNER JOIN "authors" ON "posts"."author_id" = "authors"."id" WHERE "authors"."name" = 'Karel'))
ORDER BY "posts"."id" ASC LIMIT 1
I am thinking I must be missing something. In short: how do you get an active record relation from a select manager like my_query above (or another select manager accomplishing the same thing).
You can't get ActiveRecord::Relation from Arel::SelectManager neither from sql string. You have two ways to load data through ActiveRecord:
Do all query logic in Arel. In this case you can't use any of ActiveRecord::Relation methods. But you have same functionality in Arel. In your example you may set limit through Arel:
my_query.take(10)
Other way is to use Arel in ActiveRecord::Relation methods. You may rewrite your query like this:
posts = Arel::Table.new :posts
authors = Arel::Table.new :authors
join = posts.join(authors).
on(posts[:author_id].eq(authors[:id])).
join_sources
my_query = Post.
joins(join).
where(authors[:name].eq('Karl'))
> my_query.class
=> ActiveRecord::Relation
In this case you may use my_query as ActiveRecord::Relation

Rails: Records of two models in one collection_select

Part 1: What i want is to fetch records of two tables in one collection select. Later, i want to perform search based on selected item.
So far i have managed to get the records in this manner in one select:
Controller:
#result1 = Model1.all
#result2 = Model2.all
#all = #result2 | #result1
View:
<%= collection_select :id,:id,#all, :id, :id,{prompt: "All Templates"} %>
The problem here is i want to display the name form Model1 and type from Model2.
Part 2 If the user selects the name, i want to get record from Model1 and if the type is selected, i want to get records form Model2.
All i am able to get is the id of both the models in one collection select. I am out of ideas. Let me know if any more details are required. Any help is appreciated. Thanks.
You've supplied :id to collection_select for the text_method. Check the docs to see how this helper works.
One solution would be to create an 'alias' method in each of your models which you can then call in collection_select:
model1.rb
class Model1
def text_value
name
end
end
model2.rb
class Model2
def text_value
type
end
end
I've named the method, text_value, for demonstration purposes. You may need to come up with a different name for that attribute.
Incidentally type as an attribute is reserved for Single Table Inheritance tables so it would be better to use a different attribute name.
in the view
<%= collection_select :id,:id, #all, :id, :text_value, {prompt: "All Templates"} %>

Group through joined tables Rails 4

I was hoping this would be simple, but in essence I would like to count the number of Users who have certain attributes through a join table in Rails 4.
I have a table called Views which holds three columns:
t.references :viewer
t.references :viewed
t.boolean :sentmessage, default: false
I then have the fields references as:
belongs_to :viewer, :class_name => "User"
belongs_to :viewed, :class_name => "User"
Each user record is then associated with a number of other records like Stats, Questions and a number of others. I'm interested in effectively counting how many viewers of a viewed record are Male or Female (and other search fields) which is data all held in User.stat.gender.name etc.
I'm trying to use a group statement but have no idea how to drill down and count the number of Males etc. I've tried:
#results = View.where(viewed: 63).group("viewer.stat.gender")
But this is so wrong it's frightening.
Any help to do this would be appreciated.
I worked it out finally. For anyone else who is interested:
View.where(viewed_id: 63).joins(viewer: {stat: :gender}).group("name").count
Didn't realise what an INNER JOIN was but some research and some trial and error means I can now show information about the users who have visited.