rails model :has_many with self arguments (NOT association) - ruby-on-rails

Well, I don't know if I'm completely wrong, but I really can't find a very practical and straight forward way to do something like this:
class User < ActiveRecord::Base
has_many :creations
end
but the thing is, I just want the user to have many creations if the user.developer == true
where user.developer is just a boolean field inside the Users table.
So any ideas on how exactly could I do it directly from the model?
Resuming, when the user is not a developer if you try to get User.first.creations, User.first.creations.new ... create...destroy, etc you get a NoMethodError but if it is a developer you can build a new creation.
The only way I managed to do it is extending the model and from the extension check if the proxy_owner.developer == true but by doing this I had to rewrite all the actions such new, create, update, etc...
Any help would be much appreciated
Thanks a lot

How about subclassing User and only specifying the has_many on the developer subclass? Developer would then pick up any logic from User and Users wouldn't have any creations.
class User < ActiveRecord::Base
end
class Developer < User
has_many :creations
end

Including this may work. If not you may have to resort to alias_method_chain, but I hear that has links to serious organised crime, so watch yourself.
module CreationsJustForDevelopers
def creations(*args)
if developer?
super
else
raise NoMethodError, "Only developers get creations."
end
end
end
Not sure what you are referring to with all that talk of overriding new, create, update etc… but the only other method I can think of to remove is creation_ids, but who cares about that?

Related

Creating reminders/notifications in Rails

We're building an intranet in Ruby on Rails and now we want to add functionality for having reminders when you should have done something but haven't.
For example, say you have a model, Meeting, I want to send reminders to everyone who has had a meeting but where the meeting report is empty (meeting.date < Date.today && meeting.report == ""). And a project model where you have some other criteria (maybe project.last_report.date < lastMonday && !project.closed)
Now, I can imagine creating these reminders with some kind of rake task and then removing them by some event trigger when you update Meeting or whatever, but that seems like I'll get spaghetti code everywhere.
My second idea is to make a separate module that, on each page load, fetches all the entries that could be related and runs all these checks, and then returns Reminders, however, this will probably be hella slow to hit the database like this. (Maybe caching would be an option but still)
So, anybody done something like this and have any ideas on how to solve our problem?
Thanks!
I can't see any issue with spaghetti code if you let each object that requires a Reminder to manage it's own reminders. If you want to be an OOP purist you could probably create a separate class (e.g., MeetingReminderManager in your example) to manage the reminders but that seems like overkill here. Consider...
class Reminder
belongs_to :source, polymorphic: true
belongs_to :user
end
class Meeting
has_many :reminders, as: :source
has_many :users
after_create :build_reminders, if: ->{|meeting| meeting.report.blank? }
after_update :destroy_reminders, if: ->{|meeting| !meeting.report.blank? }
private
def build_reminders
users.each{|user| self.reminders.create user_id: user.id, due_on: self.date }
end
def destroy_reminders
self.reminders.delete_all
end
end
I don't see a problem with spaghetti and background job in ruby on rails. I think making them is the path to go. Check whatever is suit you: http://railscasts.com/?tag_id=32

ActiveRecord Associations - Where to put functionality?

I'm looking for some best-practice advice for the following situation.
I have the following skeleton ActiveRecord models:
# user.rb
class User < ActiveRecord::Base
has_many :country_entries, dependent: destroy
end
# country_entry.rb
class CountryEntry < ActiveRecord::Base
belongs_to :user
validates :code, presence: true
end
Now suppose I need to get a comma-separated list of CountryEntry codes for a particular user. The question is, where do I put this method? There are two options:
# user.rb
#...
def country_codes
self.country_entries.map(&:code)
end
#...
-or-
# country_entry.rb
#...
def self.codes_for_user(user)
where(user_id: user.id).map(&:code)
end
#...
And so the APIs would be: #current_user.country_codes -or- CountryEntry.codes_for_user(#current_user)
Seems like placing the code in country_entry.rb decouples everything a little more, but it makes the API a little uglier. Any general or personal-experience best practices on this issue?
Instance method VS Class method: If the method is for an instance, of course it is better to be an instance method.
In user model VS in Coutry model: User model wins. Law of Demeter suggests one dot only in Ruby. If you have chance to do that, of course it's better to follow.
Conclusion: Your first method wins.
# user.rb
def country_codes
self.country_entries.map(&:code)
end
Add: Reference for Law of Demeter
http://en.wikipedia.org/wiki/Law_of_Demeter
http://rails-bestpractices.com/posts/15-the-law-of-demeter
http://devblog.avdi.org/2011/07/05/demeter-its-not-just-a-good-idea-its-the-law/
Now this is really an interesting question. And it has so many answers ;-)
From your initial question I would suggest you put the code in the association itself
class User < ActiveRecord::Base
has_many :country_entries do
def codes
proxy_association.owner.country_entries.map(&:code)
end
end
end
so you could do something like this
list_of_codes = a_user.country_entries.codes
Now obviously this is a violation of the Law of Demeter.
So you would best be advised to offer a method on the User object like this
class User < ActiveRecord::Base
has_many :country_entries do
def codes
proxy_association.owner.country_entries.map(&:code)
end
end
def country_codes
self.country_entries.codes
end
end
Obviously nobody in the Rails world cares about the Law of Demeter so take this with a grain of salt.
As for putting the code into the CountryEntry class I am not sure why you would do this. If you can look up country codes only with the user I dont see the need to create a class method. You are anyway only able to look that list up if you have a User at hand.
If however many different objects can have a country_entries association than it makes sense to put it as a class method into CountryEntry.
My favorite would be a combination of LOD and a class method for reuse purposes.
class User < ActiveRecord::Base
has_many :country_entries
def country_codes
CountryEntry.codes_for_user(self)
end
end
class CountryEntry < ActiveRecord::Base
belongs_to :user
validates :code, presence: true
def self.codes_for_user(some_id)
where(ref_id: some_id).map(&:code)
end
end
In terms of API developers get from the two proposals, adding to the user model seems pretty straightforward. Given the problem:
Now suppose I need to get a comma-separated list of CountryEntry codes for a particular user.
The context is made of a user, for which we want to get the code list. The natural "entry point" seems a user object.
Another way to see the problem is in terms of responsibilities (thus linking to #robkuz entry on Demeter's). A CountryEntry instance is responsible for providing its code (and maybe a few other things). A CountryEntry class is basically responsible for providing attributes and methods common to all its instances, and no more (well). Getting the list of comma-separated codes is a specialized usage of CountryEntry instances that only User objects care of apparently. In this case, the responsibility belongs to the current user object. Value in the eye of the beholder...
This is inline with most answers on the thread, although in the solutions so far, you do not get a comma-separated list of codes, but an array of codes.
In terms of performance, note there is probably a difference too because of lazy evaluation. Just a note---someone more deeply familiar with ActiveRecord could comment on that!
I think #current_user.country_codes is a better choice in this case because it will be easier to use in your code.

Scoping the find method and nested assocations

I have run into an issue regarding how to identify which user owns particular resources so that I can prevent inappropriate access to them.
I have the following nested associations:
User has many
Profiles has one
SamplePage has many
Subjects
Once they become nested this deep it's become very unwieldy to access the user object via the associations and then compare that to current user e.g.:
#subject.sample_page.profile.user == current_user
I've read that a better way of restricting access is to scope the retrieval of a model to the current user. e.g:
#profile = current_user.profiles.find(params[:id])
That makes a lot of sense to me but how would I do a similar thing to get a Subject back? I've not found any examples that used nested associations.
not sure to understand what you want to do, and not sure i can help you since i'm a huge noob, but i would try something like this (assumed that current_user returns a User):
class Profile < ActiveRecord::Base
has_many :subjects, :through => :sample_pages
end
and in your controller:
#subject = current_user.profiles.subjects.find(params[:id])
more handy this way:
class User < ActiveRecord::Base
def subjects
profiles.subjects
end
end
#subject = current_user.subjects.find(params[:id])
all of this should be lazy loaded, as explained here : http://asciicasts.com/episodes/202-active-record-queries-in-rails-3
however, if it is a frequent operation, you may want to redesign things a bit, as long chains of associations mean heavy queries (lots of joins).

How to add some constraints not to allow two way relationship in ROR

I am using ruby on rails to develop some system. In my system i have two models namely course and course_prerequisite. Course A could be prerequisite to course B but course B should never be prerequisite back to course A. how can i enforce this kind of relationship in my system?
One way that I think of is through validations:
class Course < ActiveRecord::Base
has_many :course_prerequisites
belongs_to :user
validate :course_prerequisites
private
def course_prerequisites
unless user.completed_all_prerequisites(course_prerequisites)
errors.add_to_base("Must complete all prerequisites")
end
end
end
Something like that. Note that adding an error programmatically might not invalidate your model. If that happens to you (try it out) either raise an exception or see if the model is valid before saving.

What is the best way of preventing the last record in a has_many collection being removed?

I have two ActiveRecord classes. A simplified view of these classes:
class Account < ActiveRecord::Base
has_many :user_account_roles
end
class UserAccountRole < ActiveRecord::Base
belongs_to :account
# Has a boolean attribute called 'administrator'.
end
What I'm struggling with is that I'd like to be able to apply two validation rules to this:
* Ensuring that the last UserAccountRole cannot be removed.
* Ensuring that the last UserAccountRole that is an administrator cannot be removed.
I'm really struggling to understand the best way of achieving this kind of structural validation. I've tried adding a before_remove callback to the association, but I don't like that this has to throw an error which would need to be caught by the controller. I'd rather this be treated as 'just another validation'.
class Account < ActiveRecord::Base
has_many :user_account_roles, :before_remove => check_remove_role_ok
def check_remove_relationship_ok(relationship)
if self.user_account_relationships.size == 1
errors[:base] << "Cannot remove the last user from this account."
raise RuntimeError, "Cannot remove the last user from this account."
end
end
end
I don't think this makes any difference, but I'm also using accepts_nested_attributes_for.
Why not use a simple validation on Account?
class Account < ActiveRecord::Base
has_many :user_account_roles
validate :at_least_one_user_account_role
validate :at_least_one_administrator_role
private
def at_least_one_user_account_role
if user_account_roles.size < 1
errors.add_to_base('At least one role must be assigned.')
end
end
def at_least_one_administrator_role
if user_account_roles.none?(&:administrator?)
errors.add_to_base('At least one administrator role must be assigned.')
end
end
end
This way nothing needs to be raised, and the record won't be saved unless there's at least one role, and at least one administrator role. Thus when you re-render your edit form on error, this message will show up.
You could place the validation on UserAccountRole. If it is the only UserAccountRole associated with the Account, then it can't be deleted.
An easier solution may be to question an underlying assumption of your design. Why have UserAccountRole be an AR backed model? Why not just make it a plain ruby class? Is the end user going to dynamically define roles? If not, then you could greatly simplify your dilemma by making it a regular ruby class.

Resources