I'm currently working on this tutorial: AngularJS Tutorial: Learn to Build Modern Web Apps with Angular and Rails
In the tutorial project, users can create Blog Posts and Comments for those Posts. So far I've been able to create Blog Posts (which are saved into database), but when I try to create a Comment for a Post, then I get the following error:
Started POST "/posts/16/comments.json" for 127.0.0.1 at 2015-02-15 08:32:40 +0200
Processing by CommentsController#create as JSON
Parameters: {"body"=>"6", "author"=>"user", "post_id"=>"16", "comment"=>{"body"=>"6"}}
Comments create action entered... 6
Completed 500 Internal Server Error in 9ms
NameError (undefined local variable or method `post' for #<CommentsController:0xb6036e50>):
app/controllers/comments_controller.rb:6:in `create'
Note: line “Comments create action entered... 6
” is logger.info message.
Screenshot
comments_controller.rb
class CommentsController < ApplicationController
def create
logger.info "Comments create action entered... " + params[:body]
comment = post.comments.create(comment_params)
respond_with post, comment
end
def upvote
comment = post.comments.find(params[:id])
comment.increment!(:upvotes)
respond_with post, comment
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
posts_controller.rb
class PostsController < ApplicationController
def index
respond_with Post.all
end
def create
respond_with Post.create(post_params)
end
def show
logger.info "show action entered... " + params[:id]
#respond_with Post.find(params[:id])
#the code below works, the line above resulted in error: 406 (Not Acceptable)
render json: Post.find(params[:id]).to_json
end
def upvote
post = Post.find(params[:id])
post.increment!(:upvotes)
respond_with post
end
private
def post_params
logger.info "post_params entered..."
params.require(:post).permit(:link, :title)
end
end
In the PostsController's show action, I had previously changed line: respond_with Post.find(params[:id]) to: render json: Post.find(params[:id]).to_json because line: respond_with Post.find(params[:id]) produced error: GET http://0.0.0.0:3000/posts/4 406 (Not Acceptable)
I'm not sure, but the above issue might be related to internal error (500) message, why post is not found. Also if I use line: respond_with Post.find(params[:id]) in the PostsController then I still end up with the same problem with the Comment creation.
application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
respond_to :json
def angular
render 'layouts/application'
end
end
routes.rb
FlapperNews::Application.routes.draw do
root to: 'application#angular'
resources :posts, only: [:create, :index, :show] do
resources :comments, only: [:show, :create] do
member do
put '/upvote' => 'comments#upvote'
end
end
member do
put '/upvote' => 'posts#upvote'
end
end
end
Below is post.js file that does the Ajax calls in which the o.addComment function's $http.post call tries to create the Comment in the following way: $http.post('/posts/' + id + '/comments.json', comment);
angular.module('flapperNews').factory('posts', ['$http',
function($http){
var o = {
posts: []
};
o.getAll = function() {
return $http.get('/posts.json').success(function(data){
angular.copy(data, o.posts);
});
};
o.create = function(post) {
console.log("o.create");
return $http.post('/posts.json', post).success(function(data){
o.posts.push(data);
});
};
o.upvote = function(post) {
return $http.put('/posts/' + post.id + '/upvote.json')
.success(function(data){
post.upvotes += 1;
});
};
o.get = function(id) {
return $http.get('/posts/' + id).then(function(res){
return res.data;
});
};
o.addComment = function(id, comment) {
console.log("addComment " + id + ", comments " + comment )
return $http.post('/posts/' + id + '/comments.json', comment);
};
o.upvoteComment = function(post, comment) {
console.log("o.upvoteComment " + post.id + ", comments " +comment.id)
return $http.put('/posts/' + post.id + '/comments/'+ comment.id + '/upvote.json')
.success(function(data){
comment.upvotes += 1;
});
};
return o;
}
]);
app.js
angular.module('flapperNews', ['ui.router', 'templates'])
.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home/_home.html',
controller: 'MainCtrl',
resolve: {
postPromise: ['posts', function(posts){
return posts.getAll();
}]
}
})
.state('posts', {
url: '/posts/{id}',
templateUrl: 'posts/_posts.html',
controller: 'PostsCtrl',
resolve: {
post: ['$stateParams', 'posts', function($stateParams, posts) {
console.log( "$stateParams.id " +$stateParams.id)
return posts.get($stateParams.id);
}]
}
})
$urlRouterProvider.otherwise('home')
}]);
My rails version is 4.0.2
Any help would be appreciated, because I've been struggling with the code for a couple of hours :-) Anyway, I'm glad that there is Stackoverflow forum where one can ask some advice :-)
First, this has nothing to do with angular. You don't have the post defined, so add:
post = Post.find params[:post_id]
also, i think your comment belongs_to post, you should set the post as the comment's post before saving the comment, so:
#comment.post = post
Related
I have a button click that triggers the following function. It calls the below asynchronous request.
<script>
function buy_now(user_id, deal_id) {
var purchase_confirmed = confirm("Are you sure you want to make this purchase?");
var theUrl = "/orders/create?user_id=" + user_id + "&deal_id=" + deal_id;
if(purchase_confirmed){
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, true ); // true for asynchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
}
</script>
orders_controller.rb
class OrdersController < ApplicationController
def create
#order = Order.new(user_id: orders_params[:user_id], deal_id: orders_params[:deal_id])
unless #order.save
url = "/deals/today_deal?user_id=" + orders_params[:user_id]
redirect_to url, status: :unprocessable_entity
end
end
private
def orders_params
params.permit(:user_id, :deal_id)
end
end
create.html.erb
<h1> Order successful </h1>
routes.rb
ails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
root 'deals#today_deal'
get '/deals', to: 'deals#index'
get '/deals/today', to: 'deals#today_deal'
get '/deals/new', to: 'deals#new'
get '/deals/:id', to: 'deals#show'
post '/deals/create', to: 'deals#create'
get '/orders/create', to: 'orders#create'
#set all incorrect paths to go to the root path
match '*path' => redirect('/'), via: :get
end
The problem is that once the create action(shown above) in the orders_controller is called and the subsequent view(shown above) is rendered the screen is not updated with the new view. I understand that I don't understand some concept here. It would great if you could point me to some resources to understand what I am doing wrong here. Thank you for your time.
I'm making a internal http request, from one method to another method forwarding the info, but when i make the post the second method freeze all exactly in a query in the second method, i already try to use another another database, new project, Any ideas on what is going on?
Routes
post 'rest/login'
post 'rest/verify_user/:email', to: 'auth#verify_user', as: 'verify', constraints: { email: /.*/ }
resources :users
Method 1
class RestController < ApplicationController
protect_from_forgery with: :null_session, only: Proc.new { |c| c.request.format.json? }
def login
response = RestClient.post(verify_url(params[:email]),
{'image' => params[:image]}.to_json,
{content_type: :json, accept: :json})
end
end
Method 2
class AuthController < ApplicationController
protect_from_forgery with: :null_session, only: Proc.new { |c| c.request.format.json? }
def verify_user
email = params[:email]
user = User.find_by(email: email)
if user
image = JSON.parse(request.raw_post)
diff = distance_percent(user.image,image["image"])
if diff <= 10
render status: 200
else
render status: 40
end
else
render status: 404
end
end
end
I have an Angular app with a rails back end and using the mongoid gem as a database. I'm trying to make a delete function, but I can't get it to work.
I get
No route matches [DELETE] "/api/tags"
I can't seem to get the ID. I thought I was doing that in the factory. Otherwise, I don't know how to get it.
My code:
angular:
var myApp = angular.module('tagsapp', ['ngRoute', 'ngResource']);
myApp.factory("Tag", function($resource) {
return $resource("/api/tags/:id", { _id: "#id"},
{
'create': { method: 'POST' },
'index': { method: 'GET', isArray: true },
'show': { method: 'GET', isArray: false },
'update': { method: 'PUT' },
'destroy': { method: 'DELETE' }
}
);
})
// Controllers
myApp.controller("TagListCtrl", ['$scope', '$resource', 'Tag', '$location',
function($scope, $resource, Tag, $location) {
$scope.tags = Tag.query();
$scope.saveNewTag = function() {
Tag.create({ tag: $scope.newTag }, function(response){
$scope.tags.push(response);
$scope.newTag = null;
});
}
$scope.deleteTag = function(tagId) {
Tag.destroy({ _id: tagId }, function(response){
var index = $scope.tags.indexOf(tagId);
$scope.tags.splice(index, 1)
$location.path('/')
})
};
}]);
and my rails controller:
class TagsController < ApplicationController
before_action :set_tag, only: [:show, :edit, :update, :destroy]
respond_to :json
def index
#tags = Tag.all
end
def show
#tag = Tag.find(params[:id])
end
def new
#tag = Tag.new
end
def edit
#tag = Tag.find(params[:id])
end
def create
# #tag = Tag.new(tag_params)
tag = Tag.create!(tag_params)
render json: tag, status: 201
end
def update
tag.update_attributes(tag_params)
render json: tag, status: 201
end
def destroy
#tag.destroy
respond_with #tag
end
private
def set_tag
#tag = Tag.find(params[:id])
end
def tag_params
params.require(:tag).permit(:name, :color, :order)
end
end
and my view:
<div ng-controller="TagListCtrl" ng-style="{color: myColor}">
<table>
Name: <input ng-model="newTag.name"> <br>
Color: <input ng-model="newTag.color"> <br>
Order: <input ng-model="newTag.order"> <br>
<button ng-click="saveNewTag()">Save</button>
</div>
<tr ng-show="tags.length" ng-repeat="tag in tags">
<td>{{tag.name}}</td>
<td>{{tag.color}}</td>
<td>{{tag.order}}</td>
<td>
Remove
</td>
</tr>
</table>
</div>
rails routes:
Rails.application.routes.draw do
scope "api", defaults: {format: :json} do
resources :tags
end
root to: "tags#index", anchor: false
end
I'm suspecting that my routes are off. So far, adding a new tag works in the rails index.html.erb. I didn't put in any templates for angular to route to.
I am setting up an ember app that is backed by ruby on rails. I am running into issues with my sign in action using simple-auth and simple-auth-devise. I successfully retrieve the sessions authentication token and username when I submit a correct username and password, but I am still given a 401 access denied error and I can't figure out why. I suspect that it may have to do with the naming of email versus user_email and token vs user_token business. I am taking this code mostly from dayjot, so you'd think it would be trivial to track down this bug but I am having tons of issues finding the exact issue. Thanks for any help you can give me!
The exact error I get in the rails server is:
Started GET "/users/me" for 127.0.0.1 at 2015-02-17 10:25:31 -0600
Processing by UsersController#me as JSON
Parameters: {"user"=>{}}
Filter chain halted as :authenticate_user! rendered or redirected
Completed 401 Unauthorized in 5ms (Views: 4.1ms | ActiveRecord: 0.0ms)
In rails, this is my application controller:
This is my application controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
# protect_from_forgery with: :null_session
before_action :authenticate_user_from_token!, :handle_html
around_action :user_time_zone, if: :current_user
def index
render file: 'public/index.html'
end
protected
def authenticate_user!
render(json: {}, status: 401) unless current_user
end
private
def authenticate_user_from_token!
authenticate_with_http_token do |token, options|
user_email = options[:user_email].presence
user = user_email && User.find_by_email(user_email)
if user && Devise.secure_compare(user.authentication_token, token)
request.env['devise.skip_trackable'] = true
sign_in user, store: false
end
end
end
def user_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
# If this is a get request for HTML, just render the ember app.
def handle_html
render 'public/index.html' if request.method == 'GET' && request.headers['Accept'].match(/html/)
end
end
My sessions controller looks like this:
class SessionsController < Devise::SessionsController
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
data = {
user_token: self.resource.authentication_token,
user_email: self.resource.email
}
render json: data, status: 201
end
def destroy
sign_out :user
render json: {}, status: :accepted
end
end
My serializers are these:
class UserSerializer < ActiveModel::Serializer
attributes :id, :password, :user_email, :email, :user_token, :passwordConfirmation
end
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :email_times, :last_export_time, :plan,
:plan_started, :plan_canceled, :plan_status, :trial_end,
:time_zone, :status, :created_at, :include_email_memory
end
My route is:
Rails.application.routes.draw do
# PLANS
post 'update_plan' => 'plans#update_plan', as: :update_plan
post 'update_card' => 'plans#update_card', as: :update_card
post 'cancel_plan' => 'plans#cancel_plan', as: :cancel_plan
# PASSWORDS
post 'start_password_reset' => 'users#start_password_reset'
put 'finish_password_reset' => 'users#finish_password_reset'
get 'password-reset' => 'application#index', as: :edit_user_password
# USERS
devise_for :users, controllers: { sessions: 'sessions' }, :skip => [:passwords]
resources :users, only: [:create, :update] do
get 'me' => 'users#me', on: :collection
end
# background processing admin
match "/delayed_job" => DelayedJobWeb, :anchor => false, via: [:get, :post]
# catch-all for ember app
get '*path' => 'application#index', :constraints => { :format => 'html' }
end
In the ember-cli app itself, my login controller is:
import Ember from "ember";
export default Ember.Controller.extend({
authenticator: 'simple-auth-authenticator:devise',
identification: null,
password: null,
error: null,
working: false,
actions: {
authenticate: function() {
var _this = this,
data = this.getProperties('identification', 'password');
this.setProperties({
working: true,
password: null,
error: null
});
this.get('session').authenticate('simple-auth-authenticator:devise', data).then(function() {
// authentication was successful
}, function(data) {
_this.set('working', false);
_this.set('error', data.error);
});
}
}
});
My application route is:
// ember-simple-auth
import Ember from "ember";
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
import Notify from 'ember-notify';
import ENV from 'front-end/config/environment';
export default Ember.Route.extend(ApplicationRouteMixin, {
beforeModel: function(transition) {
this._super(transition);
return this.setCurrentUser();
},
actions: {
sessionAuthenticationFailed: function(data) {
this.controllerFor('login').set('working', false);
this.controllerFor('login').set('loginErrorMessage', data.message);
},
sessionInvalidationSucceeded: function() {
this.transitionTo('index');
},
sessionAuthenticationSucceeded: function() {
var _this = this;
this.controllerFor('login').set('working', false);
this.setCurrentUser().then(function() {
if (_this.get('session.currentUser.mustSubscribe')) {
_this.transitionTo('plans');
} else {
_this.transitionTo('courses');
}
});
},
authorizationFailed: function() {
Notify.error("Could not be authenticated.. signing out.", {closeAfter: 5000});
this.get('session').invalidate();
}
},
setCurrentUser: function() {
var _this = this,
adapter = this.get('store').adapterFor('user');
if (this.get('session.isAuthenticated')) {
return new Ember.RSVP.Promise(function(resolve) {
adapter.ajax(ENV.APP.API_HOST + "/users/me", "GET", {}).then(
function(response){
_this.store.pushPayload(response);
var user = _this.store.find('user', response.user.id);
resolve(user);
},
function(response){
resolve(response);
}
);
}).then(function(user) {
_this.set('session.currentUser', user);
}, function() {
Notify.error("Could not be authenticated.. signing out.", {closeAfter: 5000});
_this.get('session').invalidate();
});
} else {
return new Ember.RSVP.Promise(function(resolve){ resolve(); });
}
}
});
Finally my login route is:
import Ember from "ember";
export default Ember.Route.extend({
activate: function() {
if (this.get('session').isAuthenticated) {
this.transitionTo('courses');
}
}
});
And Template is:
<form {{action 'register' on='submit'}} class='d-auth-form fade-in'>
{{#each errors}}
<div class="d-error">
{{this}}
</div>
{{/each}}
{{input placeholder='Email' type='email' value=email autocomplete='off' autocapitalize="none"}}
{{input placeholder='Password' type='password' value=password autocomplete='off'}}
<button type="submit" class='d-btn d-btn--success' {{bind-attr disabled=working}}>
{{#if working}}
Registering..
{{else}}
Sign up for DayJot for free
{{/if}}
</button>
<ul class='d-links'>
<li>{{#link-to 'login'}}Login to existing account{{/link-to}}</li>
</ul>
</form>
The important parts of environment.js are:
'simple-auth': {
crossOriginWhitelist: ['http://localhost:3000','http://localhost:4202','https://api.dayjot.com'],
authorizer: 'simple-auth-authorizer:devise',
authenticationRoute: 'index'
}
and
ENV['simple-auth-devise'] = {
serverTokenEndpoint: ENV.APP.API_HOST+'/users/sign_in',
identificationAttributeName: 'email'
}
Checkout the README - Ember Simple Auth Devise expects the token to be returned as token, you're using user_token however. Thus, the session will never actually be authenticated in Ember and the token won't be included in requests which leads to the 401 response.
Screenshot of what happens when I press 'delete': http://twitpic.com/4mljuy
This is what I see in my production.log:
Started POST "/clients/1" for 127.0.0.1 at 2011-04-20 13:48:26 -0500
Processing by ClientsController#destroy as JSON
Parameters: {"id"=>"1"}
nil
Completed in 3ms
This is my destroy action in my controller:
def destroy
client = Client.find(params[:id])
client.destroy
respond_to do |format|
format.html { redirect_to("/") }
format.js { render :json => ['client',params[:id]].to_json, :layout => false }
end
end
This is the delete link in my view:
<span class="icon destroy-icon" data-destroy-title="Delete <%= client.email %>?" data-destroy-url="<%= client_path(client) %>" data-compv-mapping="clientDestroyFn" title="Delete"> </span>
Here is the JS:
$('[data-destroy-url]').live('click', function(e){
console.debug("Clicked Destroy");
var element = $(this);
var mapping = compv.tools.getVariableFromString(element.attr("data-compv-mapping"), compv);
var dialog = $("div#" + mapping.dialog);
dialog.dialog('option', 'title', element.attr("data-destroy-title"));
dialog.dialog("option",
"buttons", [
{ text: "No",
click: function(){
dialog.dialog('close');
}
},
{ text: "Yes, do it!",
click: function() {
dialog.dialog('close');
$.destroy({
url: element.attr('data-destroy-url'),
success: mapping.success
});
}}
]);
dialog.dialog('open');
});
Btw, this only happens when RAILS_ENV=production and not development.
Edit: Here is my application controller:
class ApplicationController < ActionController::Base
helper :all
helper_method :current_user, :logged_in?
protect_from_forgery
before_filter :set_current_user
before_filter :authenticate_user!
def set_xhr_flash
flash.discard if request.xhr?
end
def correct_safari_and_ie_accept_headers
ajax_request_types = ['text/javascript', 'application/json', 'text/xml']
request.accepts.sort! { |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
end
protected
def set_current_user
logger.info current_user.inspect
Authorization.current_user = current_user
#User.current = current_user
end
# def after_sign_in_path_for(resource)
# if resource.is_a?(User) && resource.has_trial_expired?
# return url_for(:settings)
# end
# super
# end
end
Edit2: When I tried to delete a stage (which is an object), I got this message in my log file:
Started POST "/stages/58" for 127.0.0.1 at 2011-04-20 23:18:13 -0500
Processing by StagesController#destroy as JSON
Parameters: {"id"=>"58"}
User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 11 LIMIT 1
nil
Permission denied: No matching rules found for destroy for #<Authorization::AnonymousUser:0x000001052659c0 #role_symbols=[:guest]> (roles [:guest], privileges [:destroy], context :stages).
Rendered text template (0.0ms)
Completed 403 Forbidden in 232ms (Views: 0.9ms | ActiveRecord: 1.6ms)
Update to the latest jquery ujs driver which includes the CSRF token in each request to prevent your session from being reset since the changes in 3.0.4.
You probably have a before filter on the application controller or on your controller with basic auth requested.
Or, do you have any gems for authenication?