Rails Models and unique combinations - ruby-on-rails

I have a Rails app which has a table called friendrequests. It looks like this:
user1_id:integer user2_id:integer hasaccepted:boolean
I'm creating an option to add friends, but a friendrequest can only be send once. So you cannot have something like this in the database's data:
user1_id | user2_id | hasaccepted
1 | 2 | false
1 | 2 | false
or
user1_id | user2_id | hasaccepted
1 | 2 | false
2 | 1 | false
The user1_id/user2_id combination must be unique, not the columns themselves, so this would be possible:
user1_id | user2_id | hasaccepted
1 | 2 | false
1 | 3 | false
Is it possible to define this in a model? How can I do this?

For the first case, in your FriendRequest model, use validates_uniqueness_of(:user1_id, :scope => :user2_id). You might also need the reverse. For the second case I'd override #validate in FriendRequest and do a check in there (see the API docs for details on how the method should perform).

Try this:
validates_each :user1_id, :user2_id do |record, attr, value|
if FriendRequest.exists?( :user1_id => [record.user1_id, record.user2_id],
:user2_id => [record.user1_id, record.user2_id])
record.errors.add attr, 'Duplicate friend request'
end
end

I would also make sure to add an index to the database itself. Validation checks in the model can't be relied upon to preserve data integrity because there are edge cases in a concurrent system where the validation will not protect you.
eg, another record may be inserted after the validation check, but before the new record is created:
Model A: Validate (pass)
Model B: Validate (pass)
Model A: Insert (success)
Model B: Insert (success: bad data)

Related

Ruby On Rails + PostgreSQL: If not matching data found, return the row as nil

I have these two models:
class ModelA < ApplicationRecord
has_one :model_b
has_one :model_b
end
class ModelB < ApplicationRecord
belongs_to :model_a
end
Data in DB tables:
model_a
id | ...
1 | ...
2 | ...
3 | ...
model_b
id | model_a_id | value_a | value_b
1 | 1 | abc | def
2 | 2 | ghi | jkl
For every record in the the model_a, I want to get a record from table model_b - I can get it like this.
ModelA.joins('LEFT JOIN model_b ON model_b.model_a_id = model_a.id')
This query would return me the rows with ID 1 and 2 from the table model_a. However, I would like to get returned also the row with ID 3 from the table model_a and for this row, I would want to get returned the associated (in this case, non-existing) row from model_b with these values:
value_a: NULL
value_b: NULL
How do I do that? I tried to play with different JOINS, with CASE IF/ELSE/END, but I happened to not find the right combination.
As I need to be able to filter/query these data, I believe it would be probably better to solve this on the PSQL level, rather than on Rails.
EDIT: RIGHT JOIN returns me only the first 2 rows form model_a.
EDIT2: This is the desired output:
modal_a.id | modal_b.value_a | modal_b.value_b
1 | abc | def
2 | ghi | jkl
3 | null | null
Thank you advance.
That's called a left outer join
ModelA.joins('LEFT OUTER JOIN model_b ON model_b.model_a_id = model_a.id')
It will return all ModelA records even if no modelB record is present.
In pure rails...
ModelA.includes(:model_b)
To explicitly include the columns that may have nil...
records = ModelA.includes(:model_b).select('*, model_b.value_a as model_b_value_a, model_b.value_b as model_b_value_b')
This lets you do records.first.id to see the model_a id, and records.first.model_b_value_a etc to see the value from model_b
For records without an associated model_b record, records.first.model_b_value_a will return nil

Unlimited arbitrary properties (key/value pairs) for an ActiveRecord model

Using ruby on rails, I have a Customer table that I want to be able to add unlimited properties (key value pairs) to. I'm not sure what the key/value pairs will be yet so I'm not sure how to do this. For example, one customer could be:
Customer 1 properties:
color: 'yellow'
brand: 'nike'
sales: '33'
Customer 2 properties:
color: 'red'
phone_number: '1111111111'
purchases: '2'
Basically, customers can have any number of properties in a key/value pair.
How can I do this?
The "traditional" way to do this is with the Entity-Attribute-Value, or EAV pattern. As the name suggests, you'll create a new table with three columns: one for the "entity," which in this case is the Customer, one for the "attribute" name or key, and one for the value. So you'd have a table like this:
customer_properties
+----+-------------+--------------+------------+
| id | customer_id | key | value |
+----+-------------+--------------+------------+
| 1 | 1 | color | yellow |
| 2 | 1 | brand | nike |
| 3 | 1 | sales | 33 |
| 4 | 2 | color | red |
| 5 | 2 | phone_number | 1111111111 |
| 6 | 2 | purchases | 2 |
+----+-------------+--------------+------------+
You'll definitely want an INDEX on key and maybe on value (and customer_id, of course, but Rails will do that for you when you use relation or belongs_to in your migration).
Then in your models:
# customer.rb
class Customer < ActiveRecord::Base
has_many :customer_properties
end
# customer_property.rb
class CustomerProperty < ActiveRecord::Base
belongs_to :customer
end
This enables usage like this:
customer = Customer.joins(:customer_properties)
.includes(:customer_properties)
.where(customer_properties: { key: "brand", value: "nike" })
.first
customer.customer_properties.each_with_object({}) do |prop, hsh|
hsh[prop.key] = prop.val
end
# => { "color" => "yellow",
# "brand" => "nike",
# "sales" => "33" }
customer.customer_properties.create(key: "email", value: "foo#bar.com")
# => #<CustomerProperty id: 7, customer_id: 1, key: "email", ...>
As database design goes this is pretty solid, but as you can see it has some limitations: In particular, it's cumbersome. Also, you're restricted to a single value type (:string/VARCHAR is common). If you go this route you'll likely want to define some convenience methods on Customer to make accessing and updating properties less cumbersome. I'm guessing there are probably gems specifically for making the EAV pattern work nicely with ActiveRecord, but I don't know them off the top of my head and I hope you'll forgive me for not googling, since I'm mobile.
As Brad Werth points out, if you just need to store arbitrary properties and not query by them, serialize is a great alternative, and if you use PostgreSQL even the querying problem is surmountable thanks to its great hstore feature.
Good luck!
You may want to look into the hydra_attribute gem, which is an implementation of the Entity-Attribute-Value (EAV) pattern for ActiveRecord models.
You should be able to use serialize for this, and assign your properties hash to your properties attribute, and retrieve them in the same way.

Rails custom validation checking for duplicates

A table has following fields: badge_id, output_id, timely, removed, updated_at. For each badge_id, there can't have two valid records with the same output_id. But it doesn't mean that (badge_id, output_id) is a unique combination. Removed column indicates the current row has been removed or not. Basically delete or update operation triggers inserting a new row in the table with the latest change. So for example, we have a record like this:
badge_id| output_id| removed| timely | updated_at
1 | 1 | N | Y | 2013-11-26
To remove that record, we actually insert another row and now it reads like
badge_id| output_id| removed| timely | updated_at
1 | 1 | N | Y | 2013-11-26
1 | 1 | Y | Y | 2013-11-27
Because the latest record of (badge_id: 1, output_id: 1) has removed column set, it means that combination has been deleted. But I can't have two rows of same (badge_id: 1, output_id: 1), both have removed as "N" like:
badge_id| output_id| removed| timely | updated_at
1 | 1 | N | N | 2013-11-26
1 | 1 | N | Y | 2013-11-27
So every time to add a new output_id for a certain badge_id, I have to check for duplication. But usual validates uniqueness of (badge_id, output_id) from ActiveModel doesn't work here. How do I write a clean custom validation for this? Thanks.
UPDATE:
I think I might have missed some key points. A record can be added and then deleted and then added repeatedly. So a combination of (badge_id, output_id, removed) isn't unique either. When add a new record, we need to check for (badge_id, output_id), whether latest record has removed set as 'Y' or not.
So for possible answer like
validate_uniqueness_of :badge_id, scope: [:output_id],
conditions: -> { where(removed: "N") }
At the condition where clause, it should have order by updated_at desc and the first one has removed: 'N'. How do I fit that kind of condition into this one line code? Or there's a better way of doing this?
You could do something like:
validates :unique_badge_and_output_ids
Then unique_badge_and_output_ids could be:
def unique_badge_and_output_ids
unless Object.find_by_badge_id_and_output_id_and_removed(self.badge_id, self.output_id, self.removed).blank?
self.errors.add "record already exists" # obviously a better error here would be ideal
end
end
You can specify an SQL condition on validates_uniqueness_of:
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of
It is also possible to limit the uniqueness constraint to a set of
records matching certain conditions. In this example archived articles
are not being taken into consideration when validating uniqueness of
the title attribute:
class Article < ActiveRecord::Base
validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
end
So in your example:
class YourModel < AR::Base
validate_uniqueness_of :badge_id, scope: [:output_id],
conditions: -> { where(removed: "N") }
end
Hopefully I understand your use case properly.
Try validating the uniqueness of the removed and scoping it to both the badge_id and output_id columns but only when the removed field is N:
class Model < ActiveRecord::Base
validates_uniqueness_of :removed,
scope: [:badge_id, :output_id],
conditions: -> { where.not(removed: 'Y') }
end
There's a chance it might just work.

Will Paginate can order by certain columns but not by other columns on Model

I am doing some maintenance on a Rails 2 application using Ruby 1.8.7. I am using will_paginate.
I have the following problem:
When calling current_user.products.paginate(:page => 1, :order => "updated_at asc") it orders, as expected, based on the updated_at property in products.
However, when calling current_user.products.paginate(:page => 1, :order => "released_at asc") it does not order on released_at as expected. In fact it doesn't matter whether I specify ordering to happen according to "asc" or "desc", I get the same collection returned.
updated_at and released_at are both attributes defined on the model and exists in the database and all values are non-null. There is no default_scope defined on the models.
What could be causing this and how to correct this?
Adding Desc products below (for brevity I am only listing the relevant fields):
| Field | Type | Null | Key | Default | Extra |
| released_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
The Product model inherits from a model that also defines the released_at attribute. The method for released_at is overloaded in the Ruby on Rails code so that if one would call product.released_at on an instance product of Product, then it would actually call the released_at value on the parent model. As a result, from the rails console it always looks as if released_at is not null and contains a value.
However, looking at the database directly shows that released_at is null in the Product model and as such, even though the will_paginate request goes through correctly, no ordering is done because the values are all the same (null). In the case where the exact same query is performed on the updated_at attribute, sorting works because that attribute is not null. I missed this because I checked for empty values from the Rails Console rather than in MySQL directly.
My solution was to define a named_scope on the Product model that sorts based on the value in the parent model. I then call the paginate function on the named scope.
Sometimes it need to have an index defined for ordering fields to make it working.

Retrieving created_at value in Rails?

Is there a way I could retrieve created_at value from the Database in Rails?
The table has columns like ticket_created, ticket_updated. By default a created_at column is also included. If I try doing:
#tickets = Ticket.find(:all)
for ticket in #tickets
puts ticket.created_at
end
the above code is returning ticket_created instead of the automatically generated created_at
The sample database row looks like this.
id | title | link | key | summary | priority | status | created_at | updated_at | ticket_created | ticket_updated
Try this to really put yourself at ease that the two columns contain different values.
Ticket.find(:all).map {|t|
{:attributes => t.attributes,
:created_at => t.created_at,
:ticket_created => t.ticket_created}
}.inspect
Then try t.created_at == t.ticket_created to make sure they aren't equal and then lastly, rename the ticket_created column to something_else and see if that does the trick.

Resources