I have such problem. My test checks whether the Observer called, but does not execute it.
My files:
todo_observer.rb:
class TodoObserver < ActiveRecord::Observer
def after_create(todo)
todo.add_log('creating')
end
end
todo.rb:
class Todo < ActiveRecord::Base
attr_accessible :content, :done, :order
validates :content, :presence => true,
:length => {:minimum => 2}
def add_log(event)
Logdata.start_logging(self.content, event)
end
end
logdata.rb
class Logdata < ActiveRecord::Base
attr_accessible :modification, :event
def self.start_logging(content, event)
Logdata.create!(:modification => content, :event => event)
end
end
todo_observer_spec.rb:
require 'spec_helper'
describe TodoObserver do
before(:each) do
#attr = { :modification => "Example", :event => 'Event' }
#attr_todo = { :content => "Example", :done => :false }
end
describe 'after_create' do
it "should create log about creating task" do
count_log = Logdata.all.size
todo = Todo.new(#attr_todo)
todo.should_receive(:add_log).with('creating')
todo.save!
(Logdata.all.size).should eq(count_log + 1)
end
end
end
When I run test I get such error
Failure/Error: (Logdata.all.size).should eq(count_log + 1)
expected: 1
got: 0
Its mean, that observer called,but doesn't create instance of Logdata. When I comment string(check the call)
todo.should_receive(:add_log).with('creating')
My tests were successful.And accordingly its success when I comment string (Logdata.all.size).should eq(count_log + 1)and uncomment previous string.
How does the function should_receive to create an instance of the class Logdata?
should_receive prevents the actual method from being called.
You should create two separate tests. One to check that the log is added to the todo, and one to check that the log is created.
describe 'after_create' do
it "should add a log to the todo" do
todo = Todo.new(#attr_todo)
todo.should_receive(:add_log).with('creating')
todo.save!
end
it "should create a new logdata" do
todo = Todo.new(#attr_todo)
expect {
todo.save!
}.to change {Logdata.count}.by(1)
end
end
Related
I have a weird behaviour when using User.find_or_create_by! in before_action filter as follows:
class ApplicationController < ActionController::API
before_action :authorize_request
attr_reader :current_user
private
def authorize_request
#current_user = (AuthorizeApiRequest.new(request.headers).call)[:user]
end
end
Then in AuthorizeApiRequest I'm checking for existence or creating a new User by name:
class AuthorizeApiRequest
def initialize(headers = {})
#headers = headers
end
def call
{
user: user
}
end
def user
if decoded_auth_token && decoded_auth_token[:sub]
#user ||= User.find_or_create_by!(username: decoded_auth_token[:sub])
Rails.logger.silence do
#user.update_column(:token, http_auth_header)
end
#user
end
rescue ActiveRecord::RecordInvalid => e
raise(
ExceptionHandler::InvalidToken,
("#{Message.invalid_token} #{e.message}")
)
end
end
Example of UsersController:
class UsersController < ApplicationController
def me
if user_info_service.call
json_response current_user, :ok, include: 'shop'
else
raise AuthenticationError
end
end
private
def user_info_service_class
#user_info_service_class ||= ServiceProvider.get(:user_info_service)
end
def user_info_service
#user_info_service ||= user_info_service_class.new(user: current_user)
end
end
What is weird is that sometimes the User is created twice with the same username, sometimes not.
I'm using Ember JS in the front and another call is made to shops right after the authentication with JWT. All the routes are protected. I have the impression that calling current_user is not always in the same thread or sth like that and it results in having 2 identical users:
- the first one with just a username attribute set
- another one with all the others User attributes.
Here is the User model:
class User < ApplicationRecord
validates :username, presence: true, uniqueness: { case_sensitive: false }, on: :create
validates :shop_identifier, numericality: { only_integer: true, greater_than: 0 }, on: :update
validates :first_name, presence: true, on: :update
validates :last_name, presence: true, uniqueness: { case_sensitive: false, scope: :first_name }, on: :update
before_update do |user|
user.first_name = first_name.strip.capitalize
user.last_name = last_name.strip.upcase
end
Any ideas ? Thank you
I have a many to many connection in Rails applications, it looks like this:
class Workspace
has_and_belongs_to_many :users, dependent: :destroy
end
class User
has_and_belongs_to_many :workspaces
end
class UserWorkspace
belongs_to :user
belongs_to :workspace
end
Schema:
create_table :users_workspaces do |t|
t.integer :user_id
t.integer :workspace_id
t.integer :role, default: 0
t.timestamps null: false
end
Then I want to create a new record like this:
#user.workspaces.create(:workspace_id => #workspace.id, :role => 1)
or this
#user.workspaces << #workspace
and have an error in logs:
(0.0ms) begin transaction
(0.0ms) begin transaction
(0.1ms) rollback transaction
(0.1ms) rollback transaction
Completed 500 Internal Server Error in 207ms (ActiveRecord: 5.5ms)
Completed 500 Internal Server Error in 207ms (ActiveRecord: 5.5ms)
ActiveRecord::UnknownAttributeError (unknown attribute 'workspace_id' for Workspace.):
app/controllers/sessions_controller.rb:10:in `block in sign_up'
app/controllers/sessions_controller.rb:4:in `sign_up'
What am I doing wrong?
PS Controller:
def sign_up
respond_to do |format|
#user = User.new(user_params)
if #user.save
#workspace = Workspace.new(title: "#{#user.name}'s workspace")
#workspace.save
puts "workspace id: #{#workspace.id}"
#user.workspaces.create(:workspace_id => #workspace.id, :role => 1)
puts "workspaces count: #{#user.workspaces.count}"
#user.workspace = #workspace
#user.update_attributes(user_params)
flash.now[:success] = 'Welcome! Please check activation letter in your email box.'
format.js { render 'signup_message' }
else
format.js { render 'render_signup_errors' }
end
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :name, :workspace_id)
end
There are couple of problems with your code. For example, you are creating workspaces that are already created (#user.workspaces.create), or permitting a :workspace_id that is not used, etc.
Pleas see code below:
def sign_up
respond_to do |format|
#user = User.new(user_params)
if #user.save
#workspace = Workspace.new(title: "#{#user.name}'s workspace")
if #workspace.save
# Like this
UserWorkspace.create(user: #user, workspace: #workspace, role: 1)
# Or, like this
#user.user_workspaces.create!(workspace_id: #workspace.id, role: 1)
end
flash.now[:success] = 'Welcome! Please check activation letter in your email box.'
format.js { render 'signup_message' }
else
format.js { render 'render_signup_errors' }
end
end
end
private
# You don't need :workspace_id since you are not using it anywhere
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :name)
end
I am working through Michael Hartl's Rails book and I am about halfway through chapter 10-working on account activation.
I had everything working with the mailers but then when I tried to add a new user, I got the following error message: "undefined method `activation_digest=' for #"
I have been trying to follow along in the book the best that I can. I have my users_controller.rb here:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
before_action :correct_user, only: [:edit, :update]
def new
#user = User.new
end
def index
#users = User.paginate(page: params[:page], :per_page => 10)
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
else
render 'edit'
end
end
def edit
#user = User.find(params[:id])
end
#confirms if a user is logged in
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please Log In."
redirect_to login_url
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
Here is my Model/user.rb:
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#Returns a random token
def User.new_token
SecureRandom.urlsafe_base64
end
#Remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
#Returns true if the given token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
#forgets a user
def forget
update_attribute(:remember_digest, nil)
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
The routes I have this:
root 'static_pages#home'
get 'sessions/new'
get 'users/new'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
Please let me know if anything more is needed to be seen. I do have my App up on Github under the name sample_app, my username is ravenusmc.
Looking at your project on Github, your User model doesn't have an activation_token or activation_digest column, nor does the model define them as attributes.
Your User model is trying to write to these columns in the User#create_activation_digest function which is most likely causing the issue.
You'll need to write a migration to add those columns to your User model or add them is attributes (ie attr_accessor) if they are not meant to be persisted.
Well I have a two models related with a on-to-many assoc.
#models/outline.rb
class Outline < ActiveRecord::Base
has_many :documents
end
#models/document.rb
class Document < ActiveRecord::Base
belongs_to :outline
end
#admin/outlines.rb
ActiveAdmin.register Outline do
form do |f|
f.inputs "Details" do
f.input :name, :required => true
f.input :pages, :required => true
...
f.buttons
end
f.inputs "Document Versions" do
f.has_many :documents, :name => "Document Versions" do |d|
d.input :file, :as => :file
d.buttons do
d.commit_button :title => "Add new Document Version"
end
end
end
end
end
Well as you can see in the admin/outlines.rb I already tried setting up the :name, in the has_many :documents, and the :title in the commit_button, but neither of that options work, I also tried with :legend, :title, and :label, instead of :name in the .has_many. Not working.
This is the result of that code:
Screenshot
What I want to display is "Document Versions" instead of "Documents", and "Add new Document Version" instead of "Add new Document"
If someone can have a solution it would be great
To set has_many header you can use
f.has_many :images, heading: 'My images' do |i|
i.input :src, label: false
end
See here
Looking at ActiveAdmin tests("should translate the association name in header"), there may be another way of doing this. Use your translation file.
If you look at ActiveAdmin has_many method (yuck!!! 46 lines of sequential code), it uses ActiveModel's human method.
Try adding this to your translation file
en:
activerecord:
models:
document:
one: Document Version
other: Document Versions
One quick hack is that you can hide the h3 tag through its style.
assets/stylesheets/active_admin.css.scss
.has_many {
h3 {
display: none;
}}
This will hide any h3 tag under a has_many class.
You can customise the label of the "Add..." button by using the new_record setting on has_many. For the heading label you can use heading:
f.has_many :documents,
heading: "Document Versions",
new_record: "Add new Document Version" do |d|
d.input :file, :as => :file
end
Sjors answer is actually a perfect start to solving the question. I monkeypatched Active Admin in config/initializers/active_admin.rb with the following:
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
def titled_has_many(association, options = {}, &block)
options = { :for => association }.merge(options)
options[:class] ||= ""
options[:class] << "inputs has_many_fields"
# Set the Header
header = options[:header] || association.to_s
# Add Delete Links
form_block = proc do |has_many_form|
block.call(has_many_form) + if has_many_form.object.new_record?
template.content_tag :li do
template.link_to I18n.t('active_admin.has_many_delete'), "#", :onclick => "$(this).closest('.has_many_fields').remove(); return false;", :class => "button"
end
else
end
end
content = with_new_form_buffer do
template.content_tag :div, :class => "has_many #{association}" do
form_buffers.last << template.content_tag(:h3, header.titlecase) #using header
inputs options, &form_block
# Capture the ADD JS
js = with_new_form_buffer do
inputs_for_nested_attributes :for => [association, object.class.reflect_on_association(association).klass.new],
:class => "inputs has_many_fields",
:for_options => {
:child_index => "NEW_RECORD"
}, &form_block
end
js = template.escape_javascript(js)
js = template.link_to I18n.t('active_admin.has_many_new', :model => association.to_s.singularize.titlecase), "#", :onclick => "$(this).before('#{js}'.replace(/NEW_RECORD/g, new Date().getTime())); return false;", :class => "button"
form_buffers.last << js.html_safe
end
end
form_buffers.last << content.html_safe
end
end
end
Now in my admin file I call titled_has_many just like has_many but I pass in :header to override the use of the Association as the h3 tag.
f.titled_has_many :association, header: "Display this as the H3" do |app_f|
#stuff here
end
Does not deserve a prize but you could put this in config/initializers/active_admin.rb . It will allow you to tweak the headers you want using a config/locales/your_file.yml (you should create the custom_translations entry yourself). Dont forget to restart the server. And use the f.hacked_has_many in your form builder.
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
def hacked_has_many(association, options = {}, &block)
options = { :for => association }.merge(options)
options[:class] ||= ""
options[:class] << "inputs has_many_fields"
# Add Delete Links
form_block = proc do |has_many_form|
block.call(has_many_form) + if has_many_form.object.new_record?
template.content_tag :li do
template.link_to I18n.t('active_admin.has_many_delete'), "#", :onclick => "$(this).closest('.has_many_fields').remove(); return false;", :class => "button"
end
else
end
end
content = with_new_form_buffer do
template.content_tag :div, :class => "has_many #{association}" do
# form_buffers.last << template.content_tag(:h3, association.to_s.titlecase)
# CHANGED INTO
form_buffers.last << template.content_tag(:h3, I18n.t('custom_translations.'+association.to_s))
inputs options, &form_block
# Capture the ADD JS
js = with_new_form_buffer do
inputs_for_nested_attributes :for => [association, object.class.reflect_on_association(association).klass.new],
:class => "inputs has_many_fields",
:for_options => {
:child_index => "NEW_RECORD"
}, &form_block
end
js = template.escape_javascript(js)
_model = 'activerecord.models.' + association.to_s.singularize
_translated_model = I18n.t(_model)
js = template.link_to I18n.t('active_admin.has_many_new', :model => _translated_model), "#", :onclick => "$(this).before('#{js}'.replace(/NEW_RECORD/g, new Date().getTime())); return false;", :class => "button"
form_buffers.last << js.html_safe
end
end
form_buffers.last << content.html_safe
end
end
end
If you have issues with locale files not being loaded good in staging/production mode, adding this to your application.rb might help (substitute :nl for the right locale)
config.before_configuration do
I18n.load_path += Dir[Rails.root.join('config','locales','*.{rb,yml}').to_s]
I18n.locale = :nl
I18n.default_locale = :nl
config.i18n.load_path += Dir[Rails.root.join('config','locales','*.{rb,yml}').to_s]
config.i18n.locale = :nl
config.i18n.default_locale = :nl
I18n.reload!
config.i18n.reload!
end
config.i18n.locale = :nl
config.i18n.default_locale = :nl
How could I optimize (refactor) this non activerecord based model in rails3 that i have created.?
application.rb contains this:
CS = CloudServers::Connection.new(:username => '<hidden>', :api_key => '<hidden>')
cloudserver.rb (model) contains this:
class Cloudserver
# extend ActiveModel::Naming
attr_reader :id
attr_reader :name
attr_reader :image_id
attr_reader :flavor_id
attr_reader :status
attr_reader :progress
attr_reader :host_id
def initialize(id,name,image_id,flavor_id,status,progress,host_id)
#id = id
#name = name
#image_id = image_id
#flavor_id = flavor_id
#status = status
#progres = progress
#host_id = host_id
end
def self.all
server = CS.servers.map { |i|
new(i[:id],i[:name],i[:imageId],i[:flavorId],i[:status],i[:progress],i[:hostId])
# new(i)
}
end
def self.find(param)
all.detect { |l| l.id == param.to_i } || raise(ActiveRecord::RecordNotFound)
end
# def self.new
# server = CS.create_server(:name => "BOOYA", :imageId => 49, :flavorId => 2, :metadata => {'Luke' => 'Awesome'})
# end
end
FYI i am trying to build rails models for this api:
https://github.com/rackspace/ruby-cloudservers
should I bother or just have the controllers access the CS Object directly
Just for the sake of refactoring, here's a shorter way of writing the same class
class Cloudserver < Struct.new(:id, :name, :image_id, :flavor_id, :status, :progress, :host_id)
class << self
def all
server = CS.servers.map { |i|
new(i[:id],i[:name],i[:imageId],i[:flavorId],i[:status],i[:progress],i[:hostId])
}
end
def find
all.detect { |l| l.id == param.to_i } || raise(ActiveRecord::RecordNotFound)
end
end
end