Rails: Designing relations between models - ruby-on-rails

My application needs to implement a functionality for performing competitions of photographers. Process looks like this: Administrator creates a competition, then user can apply a request for participating. Admin can approve request or it can deny it. Admin does it by creating a response with response status field assigned to ACCEPTED (0) or BANNED (100).
class Competition
end
class User
end
class CompetitionRequest
belongs_to :user
belongs_to: competition
end
class CompetitionResponse
ACCEPTED = 0
BANNED = 100
belongs_to :competition_request
end
class Photo
belongs_to :competition
end
But later i need to figure out how to filter banned photos from showing them to users and jury.
And Photo has no direct connection to CompetitionResponse. Photo and CompetitionResponse have relations to Competition but i can not figure out how to make filtration possible. I tried doing it via plain sql (find_by_sql) inside Photo model (it returns records only if appropriate request was approved) but it does not seems to be a good design, because i reference value defined in CompetitionResponse class in Photo model.
Later i changed design and make a before_save filter inside CompetitionResponse model, it checks if status == BANNED and if true, marks all the photos posted to competition by user (who's request was banned) as banned (i added a status field to Photo model).
But now i feel it looks not good. CompetitionRequest model takes too much responsibility.
What is the best way to design it ? May be there must some kind of Observer or something like this or another design pattern ?

First step I would take is assign each Photo to the user it "belongs_to". This will allow you to, at any time, check whether the "owning" user was indeed accepted or banned for the competition that photo refers to...

It seems to me that you should have a many-to-many relation between Competition and User. When the admin accepts a user into the competition, you associate the records. This keeps things simple in your data model and also allows for cases where Users get into a Competition without requesting it (perhaps as part of a tournament?).
Also consider getting rid of CompetitionResponse - it seems like this should just be an attribute of CompetitionRequest, as there would only be one response to a request, right?

Related

Recurring database entries

Quick question about recurring DB entries.
Say I have users posting their Trips on a platform. Now, the user can say these Trips are recurring - she/he makes that trip every Tuesday and Thursday.
To make things even more interesting, say that every Trip has Requests attached to it, that other users can make. And they can make these Requests for every recurring trip.
Any ideas on how would I handle a case like this one on the back-end? Using Rails and Postgres.
Thanks in advance!
User
has_many :trips
Trip
belongs_to :user
has_many :requests
Request
belongs_to :user
belongs_to :trip
Add recurring_start and recurring_end attributes on Trip, and perhaps a recur attribute on Request. I don't know that you need to create any additional records for each Trip then, do you?
If so, you want your business logic handling that. Something like Sidekiq with a Query object that fetches Trips that are due for recurrence and creates the new trip with (for example) updated start and end dates…
class Trip < ApplicationModel
scope :recurring, -> { where(recur: true) }
scope :due_for_recurrence, -> { recurring.where(Trip.arel_table[:end_date].lt(Time.now)) }
end
You can use something like DeepCloneable if you want to automatically clone/dup associated records as well.
Since every trip can have different requests, they all need their own individual ID. It would make sense to treat them exactly the same as non-recurring trips, except for one situation: when the creator of the trips wants to edit or delete all the instances of a recurring trip. For that, you may want to make an additional table for recurring trips, with a one-to-many relation from recurring trip IDs to trip IDs, and allow users to own recurring trip IDs as well as trip IDs. That way the user interface can display the two kinds logically and they won't get lost.
Just make sure that whenever a trip is edited or deleted, the recurring trip table is updated. That could either be accomplished by simply disallowing edits or deletion to trips that are part of a recurring trip, or make the trips table store an optional recurring trip ID.

Cost of constantly querying an associated nested model (does activerecord cache results?)

Let's say I have User model with a boolean flag called email_notifications. This lets us know whether this user cares to receive email notifications from the app.
Recently I've been moving all these flags and settings into a separate sub-model call Preference to keep it away from the core user model. A User has_one Preference.
class User < ActiveRecord::Base
has_one :preference, dependent: :destroy
accepts_nested_attributes_for :preference
scope :email_recipients, -> { where(preference: { email_notification: true} ) }
def can_receive_email?
preference.email_notification
end
end
As you can see, I have a helper scope and a helper method on the model.
However, now that it lives in another table, I have always query the association :preference first and then get the value I need. It's not directly available from the User model itself any more.
Is this considered bad practice? Or does Rails help out and cache associated models so it doesn't have to re-query every time? I like organizing my various configs in a separate model, which I DO think is good practice, but this potential cost of constantly re-querying is stopping me.
Thanks!
EDIT:
I'm aware that I can do User.includes(:preference).blah_blah, however I want to avoid updating this in EVERY place where I call the user, and it may not always be known ahead of time whether I'm going to query the sub-model and need to include an .includes()
Rails associations are stored in memory after they're accessed so calling user.preference wouldn't hit the database except for the first time it's referenced.
Also, includes wouldn't have much of an effect in this case since this is a has_one. includes is usually useful for eager loading many associations in a single larger query rather than hitting the database each time a child object is called.

What is the best practice for model fetching from external source

So I have the ActiveRecord model like this
class User < ActiveRecord::Base
has_many :posts
end
And I have Api class that fetches the attributes from web for User and returns a hash, which in turn needs some preprocessing to fit into the user model. E.g. the response from api is {response: {id:20, stars:{count:20}}} needs to be processed to user.id=20 and user.stars_count=20.
Now there are 2 ways that I can easily see to do the fecting thing. One way is add a method to Api like get_user, that does all the processing returns a new User model with filled attributes. The other is to add fetch method to User that does all the processing.
In the first case I don't like that my Api class will be filled with a lot of get_model methods like get_user, get_post, get_all_posts_for_user etc. and quickly can become unmanagable. In the second case the model is filled with a lot of preprocessing stuff methods like fetch, fetch_all_posts and looks nasty too.
So what is the best practice to deal with this problem?
Take a look at Active Resource, you don't have to do what you are doing manually.

Organization model extends user model

I have User model and Organization model. The only difference is that organization has_many users, all other properties are same. I don't want to put it in one table/model. How can I remove tons of code duplicating in this models? Should I use Concerns? I think, it will be not normal if User model will looks like :
class User < ActiveRecord::Base
include user_concern
end
So, how can I extend user model in other model? And how to generate this model with rails g with all User's fields inside?
beware STI
I would keep with concerns rather than using STI. STI often causes more problem that it solves (type mismatches, form urls, etc), plus inheritance won't make sense, here : an user is not a kind of company, and a company is not a kind of user.
That's a naming problem
I think your problem is a naming one. Obviously, your concern should not be "UserConcern". The question is : what kind of methods do you group in that concern ?
Are your methods about social relation between users ? Then, you need a Socializable concern. Are they about subscribing to mailing list ? Then you need a Subscribable concern.
It's ok to have several ones, even if they have a single method in it, because developers won't wonder "what the hell does this do ?" if all concerns are correctly named.
What to duplicate anyway
You should also probably let class level method calls out concerns.
If it's ok for scopes to be embedded in concerns (after all, they resolve in method definitions), it feels less natural to me to put relations in there.
It's ok to duplicate #has_many :foos, we do it all the time in separate models, and it's already difficult enough to get an idea of table schema from a model without hiding more information.
You could use single table inheritance (STI) for this. To get it to work, your model needs a type-field of type string, in which ActiveRecord stores the actual type of your record. Then, you just extend your base model for this.
migration
add_column :users, :type, :string
models
class User < ActiveRecord::Base and class Organisation < User.
ActiveRecord will now fill your type-field with the model-name, and store both in your users table (since this is the one the organisation model is inheriting from).
Have a look at the according section on http://api.rubyonrails.org/classes/ActiveRecord/Base.html .
However, in your case, I'd create a base model, e.g. Address, and then extend User and Organisation from it, to maintain semantically correct models.

Preventing duplicate requests in rails

We have a Rails API with the following API call
POST /book
user_id (integer): The user id to book the class for
class_id (integer): The id of the class to book
In this call, we send out a request to a third party API to book the class. The problem is we want to guarantee that duplicate requests for a (user_id, class_id) pair aren't sent. We could prevent users from clicking on the book button multiple times but we want that extra level of protection. We don't want duplicate charges on our hands.
Any ideas how we can enforce something like this? Thanks!
You could create an new model called Booking with an associated database table and then create a new booking whenever this post request occurs. To make sure that no previous bookings exist, you can validate uniqueness in your model like so:
validates_uniqueness_of :user_id, :scope => :class_id
If creating a booking fails in the controller, then you know that a booking already exists and can return a message to that effect.

Resources