I have a web service that serves Ads to several different clients. The structure of the Ad varies between clients, and therefore, I am using namespaces for my models and controllers by the client name to differentiate between Ads. From the high level, it looks like this:
'app/models/client1/ad.rb'
class Client1::Ad < ActiveRecord::Base
attr_accessible :title, :description
end
'app/models/client2/ad.rb'
class Client2::Ad < ActiveRecord::Base
attr_accessible :title, :description, :source
end
In reality, these models are more complex and have associations, but that is not the point.
I am writing some unit tests using rspec-rails 2.4.0 and factory_girl_rails 1.0.1, and all of my factories work great. However, I am not able to define factories for the namespaced models. I've tried something like:
Factory.define :client1_ad, :class => Client1::Ad do |ad|
ad.title "software tester"
ad.description "Immediate opening"
end
and
Factory.define :client2_ad, :class => Client2::Ad do |ad|
ad.title "software tester"
ad.description "Immediate opening"
ad.source "feed"
end
It didn't do the job. I looked around, but every single example that I saw was using non-namespaced models. Anyone have any ideas? Any input is greatly appreciated.
I have a minimal working example here, maybe you could use it to pinpoint where your problem is. The comment you left on dmarkow's answer suggests to me that you have an error someplace else.
app/models/bar/foo.rb
class Bar::Foo < ActiveRecord::Base
end
*db/migrate/20110614204536_foo.rb*
class Foo < ActiveRecord::Migration
def self.up
create_table :foos do |t|
t.string :name
end
end
def self.down
drop_table :foos
end
end
spec/factories.rb
Factory.define :foo, :class => Bar::Foo do |f|
f.name 'Foooo'
end
*spec/models/foo_spec.rb*
require 'spec_helper'
describe Bar::Foo do
it 'does foo' do
foo = Factory(:foo)
foo.name.should == 'Foooo'
end
end
Running the test:
$ rake db:migrate
$ rake db:test:prepare
$ rspec spec/models/foo_spec.rb
.
Finished in 0.00977 seconds
1 example, 0 failures
Hope it helps.
I think maybe FactoryGirl changes since this answer was posted. I did to make it work
Factory.define do
factory :foo, :class => Bar::Foo do |f|
f.name 'Foooo'
end
end
With the current latest version of FactoryGirl (4.5.0), this is the syntax:
FactoryGirl.define do
factory :client1_ad, class: Client1::Ad do |f|
f.title "software tester"
f.description "Immediate opening"
end
end
Notice that client1_ad can be whatever name you want coz we already force identifying its class name.
I found this question looking into a related issue with FactoryGirl and after a little reading of the source I figured out I could solve my problem by renaming my factories.
When I had model classes that were namespaced inside modules, eg: Admin::User I should have been defining my factories like this:
factory :'admin/user', class: Admin::User do
#...
end
Rather than:
factory :admin_user, class: Admin::User do
#...
end
Maybe this little tidbit of info might help someone someday. The specifics of my issue was that I was trying to use build(described_class) to build instances from factories in my RSpec specs and this works just fine with un-namespaced classes but not with classes inside modules. The reason is that internally when looking up factories FactoryGirl will use ActiveSupport's underscore helper to normalise the factory name.
Have you tried passing the actual class, rather than a string with the class name:
Factory.define :client1_ad, :class => Client1::Ad do |ad|
Related
I was trying to install gem FriendlyId. I am following railscast: 314-pretty-urls-with-friendlyid. It seems pretty easy to install. But not working for me.
Here are the steps I did:
added gem "friendly_id", "~> 4.0.9" in Gemfile
I then ran bundle install command
Then modified my product model to this:
class Product < ActiveRecord::Base
extend FriendlyId
friendly_id :name
attr_accessible :name, :product_code, :recipe, :selling_price, :servings, letsrate_rateable "quality", "packaging", "hygiene" , "service", "price"
validates :name, :presence => true
class << self
def search(params)
if params[:search]
products = Product.published.includes(:product_sub_categories)
end
end
end
end
Still it is showing 'id':
http://localhost:3000/products/9
As per railscasts it should show product name. But it is not. I individually install gem but no effect.
Can anybody tell what I am missing?
Not sure why this is not mentioned in Guide, but try:
alias_attribute :to_param, :slug
This works because to_param method allows to customise the id part of URL. And here you are assigning that part to friendly_id generated URL.
Another solution
I am not sure if this is implemented inside friendly_id, but I did this:
class Product < ActiveRecord::Base
def self.find(id_or_slug)
case id_or_slug
when String
find_by_slug id_or_slug
else
super id_or_slug
end
end
end
How can I convert this migration to raw sql? or Can I convert?
class AddUnsubscribeTokenToUsers < ActiveRecord::Migration
def self.up
add_column :users, :unsubscribe_token, :string, :unique => true
User.all.each do |user|
user.unsubscribe_token = ActiveSupport::SecureRandom.hex(18)
end
end
def self.down
remove_column :users, :unsubscribe_token
end
end
AFAIK you can't convert a single migration into SQL, but you can have ActiveRecord output your schema in SQL instead of Ruby.
# in config/application.rb
config.active_record.schema_format = :sql
This will give you SQL output in your db/schema instead of the Ruby DSL. But neither format will include the snippet of code setting the user token.
Also, it's considered a Bad Idea to include DB modifying code like that in your migration. For example what happens if you get rid of or rename the model, when that migration runs it will fail. At least wrap it in a check for the model. or a begin/rescue/end
if defined? User
User.all.each do |user|
user.unsubscribe_token = ActiveSupport::SecureRandom.hex(18)
end
end
or
begin
User.all.each do |user|
user.unsubscribe_token = ActiveSupport::SecureRandom.hex(18)
end
rescue
end
And lastly, that snippet is not going to do what you intended since your not saving the model after setting the token, either use update_attributes or call user.save
I stumbled upon a very nice article, describing how this can be achieved via a custom rake task.
I am trying to get the attributes of the objects after calling a .where query. The query is the following:
#matchers = TutoringSession.where(:begin_time_hour => 21).limit(5)
I get an array of tutoring sessions as a result. But I would like to be able to return only specific attributes of each of the matching tutoring sessions. So I have the following code in my view:
#matchers.each do |matcher|
matcher.begin_time_hour
end
Instead of listing each of matcher's begin_time_hour attributes, it all of the attributes for each matcher object. I have experimented with this block trying "puts matchers.begin_time_hour," and have also tried using partials to solve this problem, however I keep running into issues. If I ask #matcher.class, it says, it is ActiveRecord::Relation object. I thought it would be a TutoringSession object.
Here are my models, in case this helps.
require 'date'
class TutoringSession < ActiveRecord::Base
belongs_to :refugee
belongs_to :student
before_save :set_day_and_time_available, :set_time_available_hour_and_day
attr_accessor :begin_time, :book_level, :time_open
attr_accessible :time_open, :day_open, :tutoring_sessions_attributes, :page_begin, :begin_time
end
and my other class is the following
require 'date'
require 'digest'
class Refugee < ActiveRecord::Base
has_many :tutoring_sessions
has_many :students, :through => :tutoring_sessions
accepts_nested_attributes_for :tutoring_sessions, :allow_destroy => true
attr_accessible :name, :email, :cell_number, :password, :password_confirmation, :day_open, :time_open, :tutoring_sessions_attributes
end
Please let me know if you need more info. Thanks for the help!
It looks like you're not outputting anything to the view. By calling
#matchers.each do |matcher|
matcher.begin_time_hour
end
you get the result from running the loop, which is the relation, instead of the data. You are accessing begin_time_hour, but you aren't doing anything with it. You'd need something more like this to display the begin_time_hour fields.
<% #matcher.each do |matcher| %>
<%= matcher.begin_time_hour %>
<% end %>
By the way, #matchers should be an ActiveRecord::Relation object, a representation of the sql query that will be generated from the where and limit clauses. Calling all on the relation with make it an array of TutoringSession objects
#matchers = TutoringSession.where(:begin_time_hour => 21).limit(5).all
Calling each implicitly runs the query and iterates over the TutoringSession objects, so you shouldn't need to worry about that though.
What is the best way to add a check for accepting terms of use in a rails app?
I can't seem to get validates_acceptance_of working quite right. I added a bool to my user model (was that necessary?). And then have a checkbox that returns either true/false.
I feel like I'm just making a silly little mistake. Any ideas?
In your model,
validates_acceptance_of :terms
If you're using attr_accessible in your model then make sure you also add,
attr_accessible :terms
In your view,
<%= form_for #user do |f| %>
...
<%= f.check_box :terms %>
...
<% end %>
There is no need for an extra column in the users table unless you plan on denying access to users who have not accepted the terms of service, which won't exist since they can't complete registration in the first place.
This is a working Rails 4 solution:
Terms of service doesn't need to be a column in the database
Form
= f.check_box :terms_of_service
models/user.rb
validates :terms_of_service, acceptance: true
And most important, devise will sanitize your parameters and terms of service will be removed from the submitted params. So:
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:full_name,
:email, :password, :password_confirmation, :terms_of_service)
end
end
end
This is a working solution for Rails-6.1 (I18n) + Devise-4.8.0 + SimpleForm. No need to add a column in the "users" table in DB.
View
<% label_str = t('read_html', mylink: link_to(t('terms'), '/a/b/c')) %>
<%= f.input :terms_of_service, label: label_str, as: :boolean, checked: false %>
Or, if you use Indo-European languages only, you can make it a little more simple, like:
label_str = (t('agree_html')+link_to(t('terms'), '/a/b/c')+'.').html_safe
/app/models/user.rb
attr_accessor :terms_of_service
validates_acceptance_of :terms_of_service, on: :create
validates_presence_of :terms_of_service, on: :create
/app/controllers/application_controller.rb
Devise::ParameterSanitizer::DEFAULT_PERMITTED_ATTRIBUTES[:sign_up] << :terms_of_service
# see /vendor/bundle/ruby/*/gems/devise-*/lib/devise/parameter_sanitizer.rb
Explanation
In the User model, on: create guarantees it is read only in creation. If you need to reevaluate the condition in updating, too, specify it accordingly, like on: %i(create update).
In the User model, I add validates_presence_of to play safe. The reason is, validates_acceptance_of will not be executed when the parameter terms_of_service is nil, in which case validates_presence_of will catch it and set an error. Admittedly, if the data are always submitted via the web-interface you have built AND your implementation is working perfectly, the value should be always either true or false and never be nil. So, validates_presence_of should not be necessary in this sense. It does no harm, though (except you'd need to be a little careful in manual user creation, bypassing the web-interface, such as from the Console).
The last one is neccesary for use with Devise for the same reason as in the answer by #vladCovaliov; that is, to prevent Devise from sanitizing your custom parameter, which is not a column in the database table. The one-liner in the example above can be stated in any files as long as you are sure it is read at the run-time and after Devise Ruby code. application_controller.rb is one of the sure places (though I guess there is a better-fitting place). Make sure the sentence is put out of the class ApplicationController block.
In config/routes.rb:
resources :posts do
resources :comments
end
resources :pictures do
resources :comments
end
I would like to allow for more things to be commented on as well.
I'm currently using mongoid (mongomapper isn't as compatible with Rails 3 yet as I would like), and comments are an embedded resource (mongoid can't yet handle polymorphic relational resources), which means that I do need the parent resource in order to find the comment.
Are there any elegant ways to handle some of the following problems:
In my controller, I need to find the parent before finding the comment:
if params[:post_id]
parent = Post.find(params[:post_id]
else if params[:picture_id]
parent = Picture.find(params[:picture_id]
end
which is going to get messy if I start adding more things to be commentable.
Also url_for([comment.parent, comment]) doesn't work, so I'm going to have to define something in my Comment model, but I think I'm also going to need to define an index route in the Comment model as well as potentially an edit and new route definition.
There might be more issues that I have to deal with as I get further.
I can't imagine I'm the first person to try and solve this problem, are there any solutions out there to make this more manageable?
I had to do something similar in an app of mine. I took what I came up with and changed it around a bit, but I haven't tested it, so use with care. It's not pretty, but it's better than anything else I was able to think of.
In routes.rb:
resources :posts, :pictures
controller :comments do
get '*path/edit' => :edit, :as => :edit_comment
get '*path' => :show, :as => :comment
# etc. The order of these is important. If #show came first, it would direct /edit to #show and simply tack on '/edit' to the path param.
end
In comment.rb:
embedded_in :commentable, :inverse_of => :comments
def to_param
[commentable.class.to_s.downcase.pluralize, commentable.id, 'comments', id].join '/'
end
In a before filter in comments_controller.rb:
parent_type, parent_id, scrap, id = params[:path].split '/'
# Security: Make sure people can't just pass in whatever models they feel like
raise "Uh-oh!" unless %w(posts pictures).include? parent_type
#parent = parent_type.singularize.capitalize.constantize.find(parent_id)
#comment = #parent.comments.find(id)
Ok, ugliness over. Now you can add comments to whatever models you want, and simply do:
edit_comment_path #comment
url_for #comment
redirect_to #comment
And so on.
Edit: I didn't implement any other paths in my own app, because all I needed was edit and update, but I'd imagine they'd look something like:
controller :comments do
get '*path/edit' => :edit, :as => :edit_comment
get '*path' => :show, :as => :comment
put '*path' => :update
delete '*path' => :destroy
end
The other actions will be trickier. You'll probably need to do something like:
get ':parent_type/:parent_id/comments' => :index, :as => :comments
post ':parent_type/:parent_id/comments' => :create
get ':parent_type/:parent_id/comments/new' => :new, :as => :new_comment
You'd then access the parent model in the controller using params[:parent_type] and params[:parent_id]. You'd also need to pass the proper parameters to the url helpers:
comments_path('pictures', 7)
Ryan Bates covered polymorphic associations in Railscasts #154, but the example was for Rails 2 and Active Record. I managed to get his example working using Rails 3 and Mongoid by making a few changes.
In the Post and Picture models, add the following line:
embeds_many :comments, :as => :commentable
According to the Mongoid associations documentation, all embedded_in associations are polymorphic. You don't need the commentable_id and commentable_type columns mentioned in the Railscast when using Mongoid, because the comment is a child of the commentable. In the Comment model, add the following line:
embedded_in :commentable, :inverse_of => :comment
Setup the routes in config/routes.rb like this:
resources posts do
resources comments
end
resources pictures do
resources comments
end
Add the following method to your comments controller as a private method. This is identical to Ryan's method:
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
In each of your comments controller actions where you need to find the comment, call the find_commentable method first to get the parent. Once the parent has been found, you can find the comment by ID, by searching through the commentable's comments. For example, in the edit action the code to find the comment would look like this:
#commentable = find_commentable
#comment = #commentable.comments.find(params[:id])
To reduce the repetition of calling find_commentable at the start of every action, you could put a before filter at the top of the controller like this:
class CommentsController < ApplicationController
before_filter :find_commentable
...
And then change the return call in the find_commentable method to:
return #commentable = $1.classify.constantize.find(value)
I haven't encountered any problems using this method, but if you come across any issues please point them out.
Drawing on Uriptical's answer, I found the relationships to work but named routes still did not.
I'm still pretty new at rails 3 but I found a simple solution using eval.
For instance, in my project, the polymorphic parents (represented in my app as the mongoid objects Product and Category) are defined as #imagable using a modification of find_comentable and the child being edited is referred to as #image.
a url such as product_image_path(#imagable, #image) which does
GET => products/:product_id/images/ can be replaced with:
send("#{#imagable.class.name.downcase}_image_url", #imagable, image )
This works for all named paths. For instance:
link_to 'Edit', send("edit_#{#imagable.class.name.downcase}_image_path", #imagable, image )
link_to 'Destroy', send("#{#imagable.class.name.downcase}_image_url", #imagable, image), :confirm => 'Are you sure?', :method => :delete
The downside to this is it leaves sends all over your views and in controllers wherever you have redirects.
Is there a more elegant solution to do this via routes?
*replaced eval with send