In my application I have a polymorphic Idea model. I want to use it with different entities like project, opportunity etc.
In the idea table it is differentiated using ideable_id and ideable_type.
So as an example, for a Project ideable_id is the project_id and ideable_type is Project.
For Opportunity ideable_id is the opportunity_id and ideable_type is Opportunity
I want to create an idea for a model called ideabank. ideabank is a virtual model, it's not an entity like project or opportunity. How should I create such a model without a database representation which will give me ideable_id and ideable_type?
Or should I leave the fields for ideable_id and ideable_type free while adding idea to ideabank?
If you are using rails 3, see this
http://railscasts.com/episodes/219-active-model
models/message.rb
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
messages_controller.rb
def create
#message = Message.new(params[:message])
if #message.valid?
# TODO send message here
flash[:notice] = "Message sent! Thank you for contacting us."
redirect_to root_url
else
render :action => 'new'
end
end
Related
So I followed this wonderfully flawed tutorial:
http://matharvard.ca/posts/2011/aug/22/contact-form-in-rails-3/
...on making contact forms. It works great. The only problem is that it ONLY sends the subject. I think maybe the problem is in the notifications mailer:
notifications_mailer.rb
class NotificationsMailer < ActionMailer::Base
default :from => "noreply#youdomain.dev"
default :to => "you#youremail.dev"
def new_message(message)
#message = message
mail(:subject => "[YourWebsite.tld] #{message.subject}")
end
end
I would, of course, like it to send ALL the info the user submitted... (name, email address, subject, and body.
Also I was wondering how I could do a simple version of this with just the body where the subject is set to a default. (I want to have a small comment box that would send an email to me with the comment.) Would I have to make a whole new controller and model for that, or could this handle both?
UPDATE
Notifications Mailer View / new.html.erb
Name: <%= #message.name %>
Email: <%= #message.email %>
Subject: <%= #message.subject %>
Body: <%= #message.body %>
contact controller
class ContactController < ApplicationController
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
NotificationsMailer.new_message(#message).deliver
flash[:success] = "Message was successfully sent."
redirect_to(root_path)
else
flash[:error] = "Please fill all fields."
render :new
end
end
end
message.rb
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :subject, :body
validates :name, :email, :subject, :body, :presence => true
validates :email, :format => { :with => %r{.+#.+\..+} }, :allow_blank => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
Basically it works... but it only sends the subject. I also got it to send a complete mail once with everything BUT the subject... but I can't remember how I did it.
Should I just smash this computer into a million pieces and go on a rampage?
Sigh...
UPDATE AGAIN
This is what the emails say with the above settings:
Subject: [liquid.radio] Whatever The Subject is. Body: Completely
blank
This is what they said after whatever the hell I did two weeks ago.
Subject: Message from liquid.radio
Body:
A contact enquiry was made by Richard Pryor at 2013-06-17 23:36.
Reply-To: richard#pryor.com
Subject: Scared for no reason Body: Oh
no... Oh God no! What is that?!
All I did was mess around with the notifications controller. Although I don't remember... for the life of me... what I did. But, as you can see... it send the complete message as it should... but a completely different subject.
Really kinda need help here.
First, this project places production settings in config/application.rb. Move the GMail ActionMailer settings to config/environments/production.rb. Add the letter_opener gem to your Gemfile's development group.
# Gemfile
group :development do
gem 'letter_opener'
end
Add the letter_opener settings to your development environment.
# config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
Add active_attr gem
# Gemfile
gem 'active_attr'
Make sure you run bundle install
Replace your messages model with robust ActiveAttr implementation.
# app/models/message.rb
class Message
include ActiveAttr::Model
attribute :name
attribute :email
attribute :subject
attribute :body
validates_presence_of :name, :email, :subject, :body
validates :email, :format => { :with => %r{.+#.+\..+} }, :allow_blank => true
end
Improve the routes.
# config/routes.rb
get 'contact' => 'contact#new', :as => 'contact'
post 'contact' => 'contact#create', :as => 'contact'
Make sure your email template is correct
# app/views/notifications_mailer/new_message.text.erb
Name: <%= #message.name %>
Email: <%= #message.email %>
Subject: <%= #message.subject %>
Body: <%= #message.body %>
Update: December 12, 2013
I've created a Rails 3.2 project on GitHub to assist anyone searching for a modern solution to creating contact forms in Rails. Merry Christmas!
https://github.com/scarver2/contact_form_app
Ultimately here's what I did... I realize this isn't a true solution. But I still don't have a different one.
notifications_mailer.rb
class NotificationsMailer < ActionMailer::Base
default :from => "example#gmail.com"
default :to => "example#gmail.com"
def new_message(message)
#message = message
mail(:subject => "[liquid.radio] #{message.subject}", :body => "
From: #{message.name}
Reply to: #{message.email}
Subject: #{message.subject}
Message: #{message.body}")
end
end
Not how the mailer is supposed to work at all... but at least it sends a complete message.
If you're in a time crunch... like I was... this will get you there. I will accept a real answer (probably scarver2's) once I stop getting blank emails any other way.
I have made the following addition to my active admin interface:
action_item :only => :show do
link_to('Approve this article', approve_admin_article_path(article)) if article.approved.nil?
end
member_action :approve, :method => :get do
# do approval
redirect_to :action => :show, :notice => "Approved!"
end
This throws the following error:
undefined method `approved' for
:Arbre::HTML::Article
What I think is happening is Active Admin thinks I'm passing an article tag in, not an article class?
Does anyone know of a work around for this? perhaps aliasing?
Thanks!
class Article < ActiveRecord::Base
attr_accessible :body
# Relations:
belongs_to :articleable, polymorphic: true, :counter_cache => true
has_many :comments, as: :commentable, order: 'created_at DESC', dependent: :destroy
# Validations
validates_presence_of :body
validates_length_of :body, maximum: 15000
end
Found a workaround
There is something fishy when you name your class as 'Article', ActiveAdmin relate to it when rendering as <article> HTML tag - The problem is somewhere in the controller of course because this is where the article object is being generated
So, I override the controller
ActiveAdmin.register Article do
controller do
def show
# grabbing my desired Article and not the <article> tag into some global variable
##myarticle = Article.find(params[:id])
end
end
sidebar :article_details , :only => :show do
ul do
# using the ##myarticle which I know should be initialized
# (you can put .nil? checking here if you want)
li link_to 'Article Images (' + ##myarticle.images.count.to_s + ')' , admin_article_article_images_path(##myarticle)
li link_to 'Article Clips ('+##myarticle.clips.count.to_s + ')' , admin_article_article_clips_path(##myarticle)
end
end
end
Enjoy
Assuming you're having the issue in the 'show' block, you could change the show block to the following:
show do |object|
end
Then you can call object.some_method without the clash. This way you don't need to override the controller.
I have a simple static website written in rails 3.
The site has one controller called pages and each static page is served as view. Such as pages/home, pages/about, pages/prices, etc. This all works great.
I've now run into a problem where I need to add a simple contactus feature but I'm struggling to get my head round the model/controller/views for this.
I already have a pages controller with a contactus view, that view has details addresses etc. Now I somehow need to get a message model into the contactus view so I can populate the model attirbutes and send the email.
Can I / Should I just create a new message model from within the Pages Controller as in ,
class PagesController < ApplicationController
def contact
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
# TO DO send message here using OS mail program.
redirect_to root_url, notice: "Message sent! Thank you for contacting us."
else
render "new"
end
end
end
def about
end
def products
end
def portfolio
end
def services
end
end
Or should I take out the contactus view from the pages controller and make new controller called messages ?
Thanks.
I would have a separate controller called contact for example with new and create actions
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
NotificationsMailer.new_message(#message).deliver
redirect_to(root_path, :notice => "Message was successfully sent.")
else
flash.now.alert = "Please fill all fields."
render :new
end
end
end
Then a separate model to handle your messages
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :subject, :body, :file
validates :name, :email, :subject, :body, :presence => true
validates :email, :format => { :with => %r{.+#.+\..+} }, :allow_blank => true
end
your attributes can be anything you like, obviously this is just an example of what you can do
I have two models, Post and Comment that have a polymorphic association with another model called Vote.
post.rb and comment.rb have has_many :votes, :as => :votable, :dependent => :destroy
vote.rb has belongs_to :votable, :polymorphic => true
This controller has two actions one to add up votes for Post and the other for Comment:
controllers/votes_controller.rb:
class VotesController < ApplicationController
def vote_up
#post = Post.find(params[:id])
if #post.votes.exists?(:user_id => current_user.id)
#notice = 'You already voted'
else
#vote = #post.votes.create(:user_id => current_user.id, :polarity => 1)
end
respond_to do |format|
format.js
end
end
def vote_up2
#comment = Comment.find(params[:id])
if #comment.votes.exists?(:user_id => current_user.id)
#notice2 = 'You already voted'
else
#vote2 = #comment.votes.create(:user_id => current_user.id, :polarity => 1)
end
respond_to do |format|
format.js
end
end
end
I think that's unnecesary. Is there any way of using a single name to refer to the current votable element or either #post and #comment?
Edit
routes.rb:
get 'votes/:id/vote_up' => 'votes#vote_up', as: 'vote_up'
get 'votes/:id/vote_down' => 'votes#vote_down', as: 'vote_down'
The vote_up action should be implemented in your posts and comments controller respectively. Users are voting on posts or comments, they're not voting on a vote.
I would extract the voting logic and place it in a module that your models will include, then call it on a votable object from the controller.
in your lib directory, create votable.rb
module Votable
def up_vote_from(usr)
place_vote(1, usr.id)
end
def down_vote_from(usr)
place_vote(-1, usr.id)
end
private
def place_vote(direction, usr_id)
v = self.votes.find_or_create_by_user_id(usr_id)
v.update_attribute(:polarity, direction)
end
end
(This revised code will alter a user's original vote if they vote again. Vote methods will return true if the vote saves, false otherwise.)
In each votable model, such as post.rb and comment.rb, add this line to mix in your voting methods:
include Votable
Now, this can be done in a controller:
#post.up_vote_from current_user # => true
As far as implementation is concerned, you will end up with some repetition in your controllers/routes.
In each votable controller, set something up like:
def cast_vote
#post = Post.find params[:id]
if #post.call("#{params[:updown]}_vote_from", current_user)
respond_to do |format|
format.js
end
else
head :not_found
end
end
(this expects .../posts/123/vote/up for an upvote, .../posts/123/vote/down for a downvote.)
then append each resource to include your vote method:
resources :posts do
member do
post 'vote/:updown', :to => "posts#cast_vote", :as => :vote_on
end
end
which can be called in your views with:
<%= button_to "Up", :url => vote_on_post_path(#post, "up"), :remote => true %>
<%= button_to "Down", :url => vote_on_post_path(#post, "down"), :remote => true %>
This is a lot less work than it looks. It'll make sense once you put it in place. It'll make even more sense if you code it in by hand vs. cut and paste. :)
I am trying to create a Active Record tableless Model. My user.rb looks like this
class User < ActiveRecord::Base
class_inheritable_accessor :columns
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
default,
sql_type.to_s,
null
)
end
column :name, :text
column :exception, :text
serialize :exception
end
When creating the new object in controller
#user = User.new
I am getting the error
Mysql2::Error: Table 'Sampledb.users' doesn't exist: SHOW FIELDS FROM users
class Tableless
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
def self.attr_accessor(*vars)
#attributes ||= []
#attributes.concat( vars )
super
end
def self.attributes
#attributes
end
def initialize(attributes={})
attributes && attributes.each do |name, value|
send("#{name}=", value) if respond_to? name.to_sym
end
end
def persisted?
false
end
def self.inspect
"#<#{ self.to_s} #{ self.attributes.collect{ |e| ":#{ e }" }.join(', ') }>"
end
end
Few things:
Firstly you are using the Rails2 approach outlined in Railscast 193 when really you should be using the Rails 3 approach, outlined in Railscast 219
You probably don't want to inherit from ActiveRecord::Base when doing this sort of thing.
Read Yehuda Katz's blog post on this.
As mentioned by stephenmurdoch in rails 3.0+ you can use the method outlined in railscasts 219
I had to make a slight modification to get this to work:
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
unless attributes.nil?
attributes.each do |name, value|
send("#{name}=", value)
end
end
end
def persisted?
false
end
end
Don't inherit your class from ActiveRecord::Base.
If a model inherits from ActiveRecord::Base as you would expect a model class to,it wants to have a database back-end.
Just remove:
class_inheritable_accessor :columns
And it should work, even with associations just like a model with a table.
Just for anyone still struggling with this. For rails 2.x.x
class TestImp < ActiveRecord::Base
def self.columns
#columns ||= []
end
end
For rails 3.1.x you can either include ActiveModel (as explained by #ducktyped) without inheriting from ActiveRecord or If you do need to inherit from ActiveRecord::Base due to some reason then the above with one other addition:
class TestImp < ActiveRecord::Base
def attributes_from_column_definition
[]
end
def self.columns
#columns ||= []
end
end
For Rails >= 3.2 there is the activerecord-tableless gem. Its a gem to create tableless ActiveRecord models, so it has support for validations, associations, types.
When you are using the recommended way to do it in Rails 3.x there is no support for association nor types.