I'm working on creating a survey application where I create the survey on my rails server and as long as the user hasn't taken the survey the next time they open up my Android app they'll be displayed the survey to take. Once the user takes the survey I'd like to update the survey to contain that user so that they are no longer sent the same survey each time they log in. I'm using a HABTM relationship between users/surveys.
I'm able to get the survey/let the user take it/create the user choices back on the server but I'm struggling with updating the survey with the user that took just the survey. I'm pretty sure my problem is my PUT request (the auth_token is used to identify the user).
Started PUT "/api/v1/surveys/4?auth_token=8d707d9fa2b279f381eb416f1be887c0"
Here's my controller:
module Api
module V1
class SurveysController < ApplicationController
# before_action :restrict_access
respond_to :json
def survey
#survey = Survey.check_survey params[:auth_token]
redirect_to api_v1_survey_path(#survey)
end
def update
#survey = Survey.find(params[:id])
#user = User.find_user_by_token params[:auth_token]
complete_survey #survey, #user
end
My model:
class Survey < ActiveRecord::Base
has_many :surveytizations
has_many :questions, :through => :surveytizations
has_and_belongs_to_many :users, -> { uniq }
def complete_survey (survey, user)
survey.users << user
survey.number_taken += 1
if survey.number_taken == survey.survey_limit
survey.survey_finished = true
end
end
In Using curl to test Rails routes it goes into how to update an attribute of a user. I'm pretty new to curl - if I wanted to test updating an array of users with an additional user, how would I do that? I tried survey[users]=user1 but that doesn't seem to work.
Thanks in advance for your help!
Related
Feel free to say if you think something is wrong.
I extended Devise Registration controller to create a Profile object to every new user:
class Users::RegistrationsController < Devise::RegistrationsController
def new
resource = build_resource({})
resource.profile = Profile.new
resource.profile.user_id = #user.id
respond_with resource
end
They both are has_one - has_one related and in database:
create_table :profiles do |t|
t.belongs_to :user, index: { unique: true }, foreign_key: true
end
So to get the right profile of current user, I must:
private
def set_profile
#profile = Profile.where(user_id: current_user.id).first
end
And this kinda solves the problem - seems other users cant go around this query and access other profiles (or CAN THEY?), but for other resources I use Pundit to control authorisation, so now it feels a bit messy.
So thats one concern. Other - I still don't know how to act when there is no user logged, because if visiting any restricted resource, this:
private
def set_some_resource
end
end
Throws - "undefined method `id' for nil:NilClass) - how is best to avoid this?
Thanks for any advices.
You may want to start by reading the Rails guides on assocations.
To create a one to one association you use belongs_to on the side with the foreign key column and has_one on the other.
class User
has_one :profile
end
class Profile
belongs_to :user
end
ActiveRecord then automatically links the records together. In general you should avoid setting ids (or getting associated records by ids) explicitly and instead use the assocations:
class Users::RegistrationsController < Devise::RegistrationsController
# ...
def new
# calls Devise::RegistrationsController#new
super do |user|
user.profile.new
end
end
end
Devise is pretty nifty and lets you pass a block to tap into the flow instead of copypasting the whole action.
Simularily you would fetch the current users profile with:
private
def set_profile
#profile = current_user.profile
end
You can set if the callback should be called by using the if: option.
before_action :set_profile, if: :user_signed_in?
But if the action requires authentication you should make sure that it is after :authenticate_user! anyways which will halt the filter chain.
And this kinda solves the problem - seems other users cant go around
this query and access other profiles (or CAN THEY?), but for other
resources I use Pundit to control authorisation, so now it feels a bit
messy.
You don't need to use Pundit to authorize creating a profile or fetching the current users profile. Since the profile is fetched via the user the is no way for another user to access it (well without hacking).
what you might want to authorize is the show, index, edit etc actions if you create a ProfilesController.
I'm trying to write a create method that collects the ID of the profile the user is currently viewing, along with some other information that is irrelevant to this question. However, because the create method POSTs rather than GETs (as I understand it), the value of params[:id] doesn't exist so it's always null. My code is as follows:
class PostsController < ApplicationController
def new
#Post = Post.new
end
def create
#Post = Post.new(post_params)
#Post.user_id = current_user.id
#Post.target_id = params[:id] #this
if #Post.save
redirect_to :back, notice: "You added a post!"
end
end
private
def post_params
params.require(:post).permit(:body)
end
end
Is there a way to get the value of params[:id] from elsewhere, perhaps from my Users controller in the show method where it actually exists?
Keep in mind that I was successfully able to create a hidden field in the Posts form, but I didn't like the fact that users were able to edit the value using Developer Tools, allowing them to change what profile the post would go to.
If there is a direct relation between the Target and the Post model, you should express this in the controller and model structure: link
This expresses your intention and it provides all the rails automations like routing, url helpers, form helpers, a.s.o.
In your concrete example, my guess is the Target would have many Posts:
class Target < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :target
end
Which would lead to the following route structure:
resources :targets do
resources :posts
end
To create a new post for the current target you would post to:
targets/:target_id/posts
And the target id would be accessed via params[:target_id]
I'm building a simple app that has a typical User model & a Profile model. User has_one Profile and Profile belongs_to User. All seems to be working fairly well as I am basically following the Michael Hartl tutorial. However, when I try to render the view of something from the profiles table (show action), I get an error (no id) AND the profile record I created gets deleted!
Questions:
In my ProfilesController, am I defining my show action properly for the simple view I am trying to render?
Why does simply visiting the url localhost/3000/profiles/1 delete the profile record? I think it has something to do with dependent destroy (b/c removing that will stop this behavior), but I think I want to keep dependent destroy, correct?
Routes
resources :users
resources :profiles
Models
Class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
Class Profile < ActiveRecord::Base
belongs_to :user
ProfilesController
def new
#profile = current_user.build_profile
end
def create
#profile = current_user.build_profile(params[:profile])
if #profile.save
flash[:success] = "Profile created dude!"
redirect_to root_path
else
render 'new'
end
end
def show
#profile = Profile.find(params[:user_id])
end
View (profiles/show.html.erb)
<p>Display Name: <%= #profile.display_name %></p>
Check your rake routes. You will see that for your Profile#show, you have URL structure like: /profiles/show/:id.
Thus, params, must be expecting the :id instead of :user_id.
If by /profiles/show/3, you wish to show profile 3, then:
def show
#profile = Profile.find(params[:id])
end
I want to add the ability for users to invite a friend.
The email should be generated so that, if someone clicks on the link and register, that person is automatically a friend.
Not sure what the options are, but wanted some ideas and strategies as an alternative to building it from scratch.
I'm not aware of any gems that handle the entire process (user >> email >> signup). If you're just looking to create the relationship when a user comes from a specific link, create a special invitation route (the separate controller isn't necessary but just to make it clear):
# routes.rb
match '/invite/:friend_id' => 'public#invite', :as => :invite
# PublicController
def invite
session[:referring_friend] = params[:friend_id]
redirect_to root_path
end
# UsersController
def create
#user = User.new(params[:user])
if #user.save
#user.create_friendship(session[:referring_friend]) if session[:referring_friend]
...
else
...
end
end
If you want to track conversion metrics, I'd recommend creating a link model and using that to track clicks and signups:
class Link < ActiveRecord::Base
belongs_to :user
attr_accessible :user, :user_id, :clicks, :conversions
def click!
self.class.increment_count(:clicks, self.id)
end
def convert!
self.class.increment_count(:conversions, self.id)
end
end
# routes.rb
match '/invite/:link_id' => 'links#hit', :as => :invite
# LinksController
def hit
link = Link.find(params[:link_id])
link.click!
session[:referring_link_id] = link.id
redirect_to root_path # or whatever path (maybe provided by link...)
end
# UsersController
def create
#user = User.new(params[:user])
if #user.save
if session[:referring_link_id]
link = Link.find(session[:referring_link_id])
link.convert!
#user.create_friendship(link.user_id)
end
...
else
...
end
end
Which method you choose depends on what you'll want to track down the road.
I don't know gem for rails. But there's an extension for Spree, rails based e-commerce project. Check it out & probably you can refer how it's implemented.
https://github.com/spree/spree_email_to_friend
I don't know about some gem to support this, but solution should be rather trivial. I guess you need Friendship model, you can place some status in it like 'waiting_for_approvment' and send in mail link with that Friendship model id. When user accepts either way you just change status to 'approved' or even 'rejected' if you want to track that too.
Start by defining the relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, :class_name => "User", :join_table => "friends_users"
end
So really, User relates to itself with a different name. Then you can use something along the lines of:
#current_user.friends << #selected_user
in your controller.
I am trying to get to grips with the basics of authentication in Rails. To start with I have used the nifty_authentication generator by Ryan Bates. It's helping me learn the basic options for user logins etc.
I have a simple application the has a person and gift table in the database. The idea is, each user creates a list of people and then assigned possible gifts to each of those people.
So from a structural point of view:
person belongs to user
gift belongs to person
So I have the models set up as follows.
person model
class Person < ActiveRecord::Base
has_many :gifts
end
gift model
class Gift < ActiveRecord::Base
belongs_to :person
end
user model
currently doesn't contain any belongs_to has_many etc.
How do I go about making sure each user has their own list of people. So one user cannot see another users list of people or gifts.
Would I simply add the following to the user model?
has_many :people
and the following to the person model?
belongs_to :user
Would that work, or am I missing something?
Thanks,
Danny
UPDATE:
The app so far is on Heroku and Github.
http://giftapp.heroku.com/
http://github.com/dannyweb/GiftApp
Would that work, or am I missing
something?
Very short answer: yes that would work; no you are not missing something.
I looked at your code.
Instead of:
def index
#people = Person.find(:all)
end
You need something along the lines of:
def index
#people = current_user.people
end
Where current_user is the User object that refers to the logged in user.
In the create method you will need to assign the newly created person to the current_user:
def create
#person = Person.new(params[:person])
#person.user = current_user # This associates #person with current_user
if #person.save
flash[:notice] = "Successfully created person."
redirect_to #person
else
render :action => 'new'
end
end