I'm a bit new to rails and I'm having a problem I try to retrieve a "proposition" object in a controller.
my datamodel is :
user -> ad -> proposition
So I created the below "proposition" model :
class Proposition < ActiveRecord::Base
belongs_to :ad
attr_accessible :email, :name
end
Therefore, we have an association between an "ad" and "proposition" (many propositions can be associated to an ad).
In the same way, the "ads" belongs to a user :
class Ad < ActiveRecord::Base
belongs_to :user
...
No problem when I try to retrieve the ads from a given user :
class RegistrationsController < Devise::RegistrationsController
def edit
#ads = current_user.ads
But when I try to retrieve the associated proposition :
class RegistrationsController < Devise::RegistrationsController
def edit
#ads = current_user.ads
#propositions = current_user.ads.propositions
I get the following error message :
undefined method `propositions' for #<ActiveRecord::Relation:0x007fee649009c0>
Any guess what's going on ?
Thanks
The error you have is caused by the fact you have relation, and the propositions (if you have the has_many :propositions in your Ad class) is defined on an instance of Ad.
So this means that
ad = Ad.first
ad.propositions
will work, but current_user.ads.propositions will not.
I am assuming you want to find all proposition linked to a user, through the ads. In that case it is pretty simple to fix.
In your user model write
class User
has_many :ads
has_many :propositions, through: :ads
end
make sure Ad has the needed relations:
class Ad
belongs_to :user
has_many :propositions
end
and then in your controller, you can just write
#propositions = current_user.propositions
You need to define the other side of the relation for this to work:
class Ad
has_many :propositions
end
Now, your current_user.ads.propositions will not work anyway, as the 'propositions' are for a single ad. If you want to be able to collect all propositions for all ads for a single users, you could create a method such as:
class User
has_many :ads
all_props = []
def propositions
ads.each {|ad| all_props += ad.propositions}
end
end
Correct me if i'm wrong but from the look of your code i think you have a database model with a user having many ads and an ad having many propositions. You need to define your database models like this:
User model:
class User < ActiveRecord::Base
has_many :ads
end
Ad model:
class Ad < ActiveRecord::Base
has_many :propositions
belongs_to :user #if you want to extract user with a specific ad
end
and in your registerations controller you need to do something like this:
class RegistrationsController < Devise::RegistrationsController
def edit
#ads = current_user.ads #returns a collection of ads for current user
#ad = current_user.ads.find(params[:id]) #find and returns your specific ad
#propositions = #ad.propositions #returns a collection of propositions related to your ad
end
end
Related
I'm actually not sure if this is a Pundit or general permissions architectural problem, but I setup a simple Pundit policy to restrict the actions a member within a company can perform. Users are joined as a Member to a company in a has_many, through: relationship. The Member model has a role attribute of owner or user.
Given a User that is a member of a Store, how can I restrict the access in a controller for the User's association to the Store? Below is a Admin::MembersController where a store owner can invite other members. How can I restrict this to the given User in pundit through their member association to the store? The policy below doesn't work, returning an array of records. If I were to check against only the first record it works but I feel that is because of my limited understanding.
All of the tutorials and documentation I see online for CCC and Pundit
involve application-wide permissions. But I need more granular
control.
For example, my application has hundreds of companies. Each company
has a user who is an "owner" and they login each day to look at their
earnings information. That owner/user wants to invite Joe Smith to the
application so they can also look at the data and make changes. But
they don't want Joe Smith to be able to see certain types of data. So
we restrict Joe Smith's access to certain data for that company.
class Admin::MembersController < Admin::BaseController
def index
#company_members = current_company.members
authorize([:admin, #company_members])
end
end
Policy
class Admin::MemberPolicy < ApplicationPolicy
def index?
return [ record.user_id, record.store_id ].include? user.id
## this works return [ record.first.user_id, record.first.store_id ].include? user.id
end
end
User.rb
class User < ApplicationRecord
# Automatically remove the associated `members` join records
has_many :members, dependent: :destroy
has_many :stores, through: :members
end
Member.rb
class Member < ApplicationRecord
belongs_to :store
belongs_to :user
enum role: [ :owner, :user ]
end
Store.rb
class Store < ApplicationRecord
has_many :members
has_many :users, through: :members
end
I got some insight from the contributors on Pundit; the most reasonable way to go about it this is to use a domain object which represents the context that a user is in - there is information about this in the Readme (https://github.com/varvet/pundit#additional-context). The UserContext object will provide references to a user and organization.
class ApplicationController
include Pundit
def pundit_user
if session[:organization_id]
UserContext.new(current_user, Organization.find(session[:organization_id]))
else
UserContext.new(current_user)
end
end
end
class UserContext
attr_reader :user, :organization
def initialize(user, organization = nil)
#user = user
#organization = organization
end
end
I think what you are looking for is scopes in pundit. You want to restrict certain data access to members of store and show that data to owner of that store.
For that purpose you need to change your query according to the user role
Something like this:
class EarningPolicy < ApplicationPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
# here check for owner and show all data
if user.members.owner # here you query your memberships to find the owner role.
scope.all
else
# here show only data relevant to member
scope.where(published: true)
end
end
end
end
You can now use this class like this in your controller
def index
#earnings = earning_scope(Earning)
end
Hope it helps
I have a signed in user profile and each profile has its own phone-book that no other user can access. The question is how should i implement it. Considering User as one controller and phone-book as another i'm not able to establish a relation between the two for a specific user login.
What should be my approach?
I have a sparate model for User and separate model for phone-book and have established a relation between them using has_many and belongs_to macro.
Let's start with the models. You say that each User has only one PhoneBook so I would say that the right models should rather be:
class User < ActiveRecord::Base
has_one :phone_book
end
class PhoneBook < ActiveRecord::Base
belongs_to :user
end
Now, about the controllers.
When you have a signed in User you will eventually have a "session thing" going on.
Let's say you're using devise, then you will have a variable current_user that references the logged in user. So the PhoneBooksController will be something like:
class PhoneBooksController < ApplicationController
def index
#phone_book = current_user.phone_book
end
end
Of course if your users can have more than one PhoneBook we go back to the has_many association:
class User < ActiveRecord::Base
has_many :phone_book
end
class PhoneBook < ActiveRecord::Base
belongs_to :user
end
and the controller becomes:
class PhoneBooksController < ApplicationController
def index
#phone_books = current_user.phone_books
end
def show
#phone_book = PhoneBook.find_by_id(params[:id])
end
end
At last, if you want these phone books to be publicly readable I suggest you stick with a REST kind of URI
/phone_books/:id <-- good
/users/:id/phone_books/:phone_book_id <-- too complex
Hope I could help
You might want to place the page in /users/:user_id/phone_books/:id.
To achieve that,
You have to configure the paths in config/routes.rb:
resources :users do
resources :phone_books
end
And in app/controllers/phone_books_controller.rb, find the user and their address book:
class PhoneBooksController < ApplicationController
before_action :find_user
def show
#address_book = #user.address_books.find(params[:id])
end
private
def find_user
#user = User.find(params[:user_id])
end
end
For more information about nested resources, please see the Getting Started with Rails guide.
I was trying to find answer on my question, but didn't success with it.
I have models Event, participants, participation_form, invitation, user.
Event has_many participants
User has_many invitations
User has_many participation_form
For Participant I want to have field like "based_on" and it will be references with invitation or participation_form.
I have one idea about it - make two fields and one model method that will be check which field contains value and return "based_on"
My question is - is there any way to reference one model to two models with pair of fields - class (model name) and value (id) so I will add another type if I need it in future.
You could use polymorphic associations for that: (http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Could you tell more about models relations so I can write some example? Why do you need Participant model?
As mentioned byKuba Ploskonka, you'll probably benefit from a polymorphic association here:
--
Setup
For Participant I want to have field like "based_on" and it will be references with invitation or participation_form.
As per your specifications, you'll want to use the following:
#app/models/participation.rb
Class Participation < ActiveRecord::Base
belongs_to :participle, polymorphic: true
end
#app/models/invitation.rb
Class Invitation < ActiveRecord::Base
has_many :participations, as: :participle
end
#app/models/participation_form.rb
Class ParticipationForm < ActiveRecord::Base
has_many :participations, as: :participle
end
This will give you the ability to save your objects as follows:
#app/controllers/invitations_controller.rb
Class InvitationsController < ApplicationController
def create
#invitation = Invitation.new invitation_params
#invitation.participations.build #-> will save a blank "Participation" object
#inviation.save
end
end
I have two objects - User and Reviews. One User can write many reviews, but one review can be written by one user. Every user has a picture. I have:
class Review < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :reviews
end
Why can't I do the following?
review.user.picture
You have to establish has_one/belongs_to associations on user/picture models and of course to have appropriate database migrations (foreign key user_id on picture table and foreign key user_id on review table).
Models look like:
class Review < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :reviews
has_one :picture
end
class Picture
belongs_to :user
end
To try it, first create data in rails console:
user = User.create
review = Review.create user: user
picture = Picture.create user: user
Now you can find user picture if you have only reference to review object. Exit console and run again and type:
review = Review.last
review.user.picture
It returns picture object.
More info:
http://guides.rubyonrails.org/association_basics.html#choosing-between-belongs-to-and-has-one
Based on the info you provided, I'm assuming you still need to set up a foreign key in the database. The database migration for the the reviews table should look similar to this:
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.integer :user_id
t.timestamps
end
add_index :reviews, :user_id
end
end
Then you would be able to add a related user_id to a new review object. For instance, in rails console:
Review.new(user_id: 1)
Assuming that you defined picture as a column in your User table, you should be able to run review.user.picture without any issues.
Method
The bottom line is your picture method will not be present in your user model
You can either use an instance method to create some functionality in your User model, use an ActiveRecord Association, or use something like Paperclip to provide an appended object on your user object
Association
You'll need to create the correct association for your review & picture objects -
#app/models/user.rb
Class Review < ActiveRecord::Base
has_many :reviews
has_one :picture
end
#app/models/picture.rb
Class Picture < ActiveRecord::Base
belongs_to :user
end
This will allow you to call:
#review = Review.find params[:id]
#picture = #review.user.picture
A bonus tip for this is if you wanted to ensure compatibility with the law of dementer, you could use the delegate method in your user model:
#app/models/user.rb
Class User < ActiveRecord::Base
...
delegate :attribute, to: :picture, prefix: true
end
This will allow you to call the likes of:
#review.user.picture_name
Paperclip
If you're using a picture model to store images, you may wish to use the likes of Paperclip to give you the ability to use the functionality of images in your Picture model:
#app/models/user.rb
Class User < ActiveRecord::Base
...
delegate :url, to: :picture
end
#app/models/picture.rb
Class Picture < ActiveRecord::Base
has_attached_file :picture
end
These will all help you. Using this answer with Иван Бишевац's will help you profusely :)
A User can only have two types of Subscriptions: DailySubscription and WeeklySubscription. When the user is at the new and edit action, I'd like them to check off either of the subscriptions they would like to get.
I'm comfortable using nested fields (as per Ryan Bates' screencast here) but I think when I add inheritance, it really complicating matters. Is there a better way?
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
My initial efforts with the controller are wacky:
class UsersController < ApplicationController
def new
#user = User.new
# I can't use #user. subscriptions.build as Rails doesn't
# know what type of model to add!
#user.subscriptions = [DailySubscription.new, WeeklySubscription.new]
end
...
end
I think I am conceptually really missing something here but I can't figure it out. Help!
Judging from your description, your user has only two possible subscription choices: daily and/or weekly. Therefore you dont need to have a has_many association because two has_ones would suffice(note polymorphic subscribeable below:
class User < ActiveRecord::Base
has_one :daily_subscription, :as => :subscribeable
has_one :weekly_subscription, :as => :subscribeable
end
class Subscription < ActiveRecord::Base
belongs_to :subscribeable, :polymorphic => true
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
furthermore for the controller you just need to initialize User. Upon initialization, #user.daily_subscription and weekly_subscription will be null as determined by .blank? method. When you go ahead and create the user in the create method, you will need to populate these fields with instances of corresponding subscriptions.
class UsersController < ApplicationController
def new
#user = User.new
# bam -- youre done.
end
...
end