I've done some searching here and I've not been able to find anything that quite answers what I'm looking for. If I failed in my search I apologize.
Moving on, I am new to Rails and I'm working on an application to test the waters if you will. I'm using Devise for authentication and it's proving quite useful. That said, I've run into a big of a road block with where a certain check for data would go, and how I would go about it.
I have three tables: users, games, and users_games (I read that this was an acceptable naming convention for relational tables, correct me if I'm wrong). On a Games page I would like to display a certain message if the currently logged in User has this Game added to their account (in users_games). I'm unsure of where to perform this check, or if it even matters at all.
As for the actual checking, my initial idea would be something along the lines of:
games_controller.rb
class GamesController < ApplicationController
def index
#games = Game.all
end
def show
#game = Game.find(params[:id])
#user_owns = UsersGames.where(:game_id => #game.id, :user_id => current_user.id)
end
end
Then on the view checking if #user_owns has a value or not.
Thanks in advance for any insight or wisdom you can offer.
What about this way, may be you don't need users_games
if game has_many users and user belongs_to game
def show
#game = Game.find_by_user_id(current_user.id)
end
Then on the view checking if #game has a value or not.
If your Users<->Games relationship is a simple HABTM with no additional attributes on the join table, i.e.
class User < AR::Base
has_and_belongs_to_many :games
class Game < AR::Base
has_and_belongs_to_many :users
you don't need to have a separate model for the join table, provided that you follow the Rails naming convention that requires you to follow the lexicographical order when naming your join table, i.e. in your case it would be games_users, not the other way around like you have it now.
Going back to your original question, I think it can be as simple as this:
def show
#game = Game.find(params[:id])
#game_owned = current_user.games.include? #game
end
You can also make this a method on your User model:
class User < AR::Base
...
def owns_game?(game)
self.games.include?(game)
end
end
and then call current_user.owns_game?(#game) in your controllers/views.
Related
I'm putting together a side project for a teacher/student type of website where the student will share a dashboard with the teacher. The teacher and student can both upload files and leave comments in the dashboard.
The part that I'm stuck on is the permission. For student, I've set up the index controller to this method
def index
#Homework = Homework.where(:user_id = current_user)
end
With this, I'm able to have the student only see the work that they have, but I'm confused on how to get the teacher to see each individual student's work?
Suggestions? Thanks
Here's a simple solution if you only ever need to support a single class in your application:
def index
if current_user.teacher?
#homeworks = Homework.all
else
#homeworks = Homework.where(user_id: current_user)
end
end
Otherwise, your Homework schema does not seem to be correctly designed. See your query below:
Homework.where(:user_id = <student_id>)
This works to retrieve a student's homeworks, but it does not work to retrieve a teacher's students' homeworks. You may need a class_id for a teacher to see each individual student's work:
Homework.where(:class_id = <class_id>, :user_id = <student_id>)
A Class has_many Teachers and has_many Students. This design will allow you to support multiple classes.
Some more guiding questions:
Is teacher/student both kept in the same User model?
How do you differentiate between teacher/student in your current User model?
Is there a "user_type" column somewhere in User?
What happens if the current_user is of the "teacher" user_type?
For complex user permissions, use CanCanCan: https://github.com/CanCanCommunity/cancancan
Don't use uppercase instance variables. ex: #Homework should be #homework
Check out the gem CanCan. Install it (follow the instructions, you should have to put something in application controller), Then, put in your ability file:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Homework, user_id: user.id
end
end
Then at the top of your StudentController put
load_and_authorize_resource
And the index action should look like:
#homework = #student.homework
Now, you didn't post your whole controller so this is a much as I can help.
I believe you may have a bigger underlying issue. You have students and teachers has_many homework i read in your comment. Then in your example you use user_id. You are likely overriding your students and teacher ownership of homework. You would need a has_and_belongs_to_many relationship OR you would need a student_id and teacher_id columns on the homework table
Cancan automatically generate a number of instance variables which can make it feel like magic. Watch the free railscasts on cancan the same guy who made the video wrote the CanCan library.
I am building a Ruby on Rails 4.1 app that has the following models that should make sense when you see the model names:
Domains, Teams, Users, Meetings etc
Now, a Team belongs to a domain and a User to a team, also meetings belong to a user. A domain is simply an organisation or company that is using the software.
After the creation of an admin user that user has to initially create a Domain, then create the first team, then other users can sign up.
As you can probably anticipate, in the process of creating the initial Domain and Team (which is done in the new/create of the Team and Domain controllers) I am having to access and change references in all three. So, for example, creating the first Domain will have to associate the admin user, creating a Team will involve linking it to the user, then also the parent domain.
So, it's all getting a bit mixed up, with the controller needing to access several models.
My question is, where does this logic belong? In the spirit of thin controller you would normally farm it out to the model, but it involves more than one model. Is this where the new Rails 4 concerns become useful or should I just put it in the controller?
I am relatively new to rails, so please keep that in mind if you are kind enough to reply - thanks!
Nested resources
class Domain
has_many :teams
end
class Team
has_many :users
belongs_to :domain
end
class User
has_many :meetings
belongs_to :team
end
class Meeting
belongs_to :user
end
Then in your controllers:
class TeamsController < ApplicationController
def new
#team = Team.new
end
def create
#domain = Domain.find(params[:domain_id])
#team = #domain.teams.build(params[:team])
#team.save
respond_with #team
end
end
That's what we call a "nested" controller, in the routes.rb file:
resources :domain do
resources :team
end
The URL will look like that:
/domains/:domain_id/teams
/domains/:domain_id/teams/:team_id
Same logic apply for other models. This should give you a starting point to build your application.
The following line
#domain.teams.build(params[:team])
automatically link a domain to a team setting the reference (id) for you.
However you should not do deep nesting according to rails guide so that's where the builder design pattern can come in handy.
Builder design pattern
However, if things start to get to messy, I would suggest using a dedicated ruby class to "build" your objects and their relationship. We usually call those classes a "Builder":
class TeamBuilder
attr_reader :domain, :params
def initialize(domain, params = {})
#domain = domain
#params = params
end
def build
domain.teams.build(params)
end
end
Here it's again doing very simple task. For user and meetings for example:
class UserBuilder
attr_reader :team, :params
def initialize(team, params = {})
#team = team
#params = params
end
def build
team.users.build(params).tap do |user|
user.foo = 'foo'
user.meetings.build(...)
user.meetings << MeetingBuilder.new(user, { ... })
end
end
end
class MeetingBuilder
# ...
end
Here we use the MeetingBuilder within the UserBuilder to build a meeting.
Usage:
user = UserBuilder.new(team, { ... }).build
user.save
Ideally, a model shouldn't care about, or even know about, other classes. So between the model and the controller, this kind of logic definitely belongs in the controller.
I would probably go with a third class though, taking care of that messy stuff, like a DomainFactory for example. Which is just a plain old ruby object (poro), no active record or anything, with the sole purpose of creating domains.
A tip is to read up on loose coupling and single responsibility.
I know this is a really simple question but I guess my brain and google-fu isn't working so well today.
Let's say I have an Event, with Registrants, and they can pay for the event using one or more payments.
I'm trying to create a payment linked to a registrant (who is linked to an event).
So my payment should have both registrant_id and event_id.
My URL looks something like this: (nested routes)
http://mysite.com/events/1/registrants/1/payments/new
My controller looks something like:
def create
#event = Event.find(params[:event_id])
#registrant = Registrant.find(:first, conditions: {id: params[:registrant_id], event_id: params[:event_id]} )
#payment = Payment.new params[:payment]
end
I know there is a much better way to do it, but I'm having trouble with the wording to properly google it :)
What syntax should I be using to make the .new automatically aware of the event_id and registrant_id?
Based on the discussion in the comments, there are several ways that the question can be addressed: the direct way and the Rails way.
The direct approach to creating objects that are related is to create the object using new_object = ClassName.new as suggested in the question. Then take the id of the created object and set that on an existing object (directly with existing_object.id = new_object.id or through some other method if additional logic is required). Or set the id on a new object by defining a custom initializer, such as:
class Payment
def initializer id_of_registrant
#registrant_id = id_of_registrant
end
...
end
The advantage of this approach is that it allows you to assign registrant IDs that may come from a range of objects with different classes, without having to deal with unnecessary or perhaps incorrect (for your solution) inheritance and polymorphism.
The Rails way, if you always have a direct relationship (1 to 1) between a Registrant and a 'mandatory' Payment is to use a has_many or belongs_to association, as described in the Rails guide: http://guides.rubyonrails.org/association_basics.html
For the example classes from the question:
class Registrant < ActiveRecord::Base
has_one :payment
end
class Payment < ActiveRecord::Base
belongs_to :registrant
end
You will want to use the appropriate migration to create the database tables and foreign keys that go with this. For example:
class CreateRegistrants < ActiveRecord::Migration
def change
create_table :registrants do |t|
t.string :name
t.timestamps
end
create_table :payments do |t|
t.integer :registrant_id
t.string :account_number
t.timestamps
end
end
end
Of course, if you registrants only optionally make a payment, or make multiple payments, then you will need to look at using the has_many association.
With the has and belongs associations, you can then do nice things like:
#payment.registrant = #registrant
if you have instantiated the objects by hand, or
#payment.new(payment_amount)
#registrant = #payment.build_registrant(:registrant_number => 123,
:registrant_name => "John Doe")
if you would like the associations populated automatically.
The Rails Guide has plenty of examples, though in my experience only trying the most appropriate one for your actual use case will show if there are restrictions that could not be anticipated. The Rails approach will make future queries and object building much easier, but if you have a very loose relationship model for your objects you may find it becomes restrictive or unnatural and the equivalent associations are better coded by hand with your additional business rules.
It's not great practice to set id attributes directly, as the id might not refer to an actual database row. The normal thing to do here would be to use CanCan (https://github.com/ryanb/cancan), which seems like it would solve all your problems.
EDIT:
If you're not using authentication of any kind then I'd either put the load methods in before_filters to keep things clean:
before_filter :load_event
def load_event
#event = Event.find params[:event_id]
end
or define some funky generic loader (unnecessarily meta and complex and not recommended):
_load_resource :event
def self._load_resource resource_type
before_filter do
resource = resource_type.constantize.find params[:"#{ resource_type }_id]
instance_variable_set :"##{ resource_type }", resource
end
end
I am building an Invoicing Application that basically follows the following pattern:
Users < Clients < Projects < Invoices
Now in order to generate autoincrementing invoice numbers for each User I put this in my Invoice model:
before_create :create_invoice_number
def create_invoice_number
val = #current_user.invoices.maximum(:number)
self.number = val + 1
end
However, it seems that the current_user variable cannot be accessed from within models in Rails?
What can I do to solve this problem?
This is due to separation of concerns in Rails and is a somewhat sticky issue to deal with. In the Rails paradigm, models should have no knowledge of any application state beyond what they're passed directly, so most Rails coders will tell you that any model needing to know about a current_user is code smell.
That said, there are three ways to do this, each "more correct" (or at least I would consider them so).
First, try creating an association to the user inside the invoice and link the invoice to the user in the controller:
class InvoicesController < ApplicationController
...
def create
#invoice = current_user.invoices.create(params[:invoice])
...
end
And in your model:
belongs_to :user
def create_invoice_number
self.user.invoices.maximum(:number) + 1
end
If that doesn't work, do this manually in the controller. It's true that controllers should always be as skinny as you can manage, but since this is clearly an application-level concern the controller is the place to put it:
class InvoicesController < ApplicationController
...
def create
#invoice = Invoice.create(params[:invoice])
#invoice.update_attribute(:number, current_user.invoices.maximum(:number))
...
end
Lastly, if you really, really want to bridge the controller and model, you can do so with ActionController::Sweepers. They are not intended for this purpose but will certainly get the job done for you.
there should not be any arise of such case still if you want then make use of observers in rails
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).