In my rails app I have 2 models: post and post_translations.
class PostTranslation < ActiveRecord::Base
belongs_to :post
LANGUAGES = %w( en fr es de it )
validates_inclusion_of :language, :in => LANGUAGES
end
class Post < ActiveRecord::Base
has_many :post_translations
end
I want to prevent the same language translation from being submitted twice, so I want to limit the enums to the values not listed in the language column of a particular post_id.
I don't know if I should do this in model, controller or helper.
Which is the best practice?
Thanks in advance.
I'd use an attribute on the class instead of defining it on an instance.
class PostTranslation < ActiveRecord::Base
##languages = %w( en fr es de it )
cattr_reader :languages
belongs_to :post
validates :language, :inclusion => { :in => ##languages },
:uniqueness => { :scope => :post_id }
end
Now to fulfill your requirement of showing only the languages without translations, define a method on Post:
class Post < ActiveRecord::Base
has_many :post_translations
def untranslated
PostTranslation.languages - post_translations.map(&:language)
end
end
Then you can build a select menu by getting a post (#post = Post.find(params[:id]) and populating the collection from #post.untranslated.
It's perfectly valid to keep this in the model. Models should hold primary responsibility for ensuring that the data entered into them is correct.
For your particular case, you can use the :uniqueness validator with a scope passed to it. Basically your validation will ensure that languages are unique in the context of a particular post
The following should work:
validates :language, :inclusion => { :in => LANGUAGES },
:uniqueness => { :scope => :post_id }
If you prefer the Rails 2 style syntax, you can use:
validates_uniqueness_of :language, :scope => :post_id
Related
I am trying to use auto-complete/type-ahead feature provided by following Rails gem https://github.com/maxivak/bootstrap3_autocomplete_input , together with https://github.com/plataformatec/simple_form
Everything works fine in case I choose "new" action for new record in form. I am able to choose value in input field by auto-complete feature. Problem is in case i choose "edit" action to edit already existing record. In that case field does not show correct value (pre-filled by form), but it shows something like: #<Airport:0x007f98b478b7a8>
Even in "show" action, I can see correct value displayed.
I tried to change f.input with f.association as I had it before I started implementing auto-complete, but this did not helped.
Records in Cargo model have correct airports_id reference stored, I checked that manually in rails console.
Question is how can I get correct Airport value pre-filled by form in case I choose "edit" action, instead some kind of reference, I got.
Rails 4.1.7
My code is:
Cargo model:
class Cargo < ActiveRecord::Base
belongs_to :airport
...
Cargo view:
...
<%= f.input :airport, :as => :autocomplete, :source_query => autocomplete_airport_city_airports_url %>
...
Airport model:
class Airport < ActiveRecord::Base
has_many :cargos, :dependent => :destroy
attr_accessible :iata_code, :name, :city
validates :iata_code, :name, :city, presence: true
validates :iata_code, :uniqueness => { :scope => :name }
validates :iata_code, length: { is: 3 }, format: { with: /\A[a-zA-Z\d\s]*\z/ }
validates :name, :city, length: { minimum: 2, maximum: 128 }
def full_airport_name
"#{city} / #{iata_code}"
end
end
Airports controller
class AirportsController < ApplicationController
autocomplete :airport, :city, { :display_value => 'full_airport_name', :full_model=>true }
...
Routes:
resources :airports do
get :autocomplete_airport_city, :on => :collection
end
Actually I found the problem. First of all I refactored Airports model, removed all columns but name, and reseed name column with data concatenated from separate strings IATA code / City. After this, there is need to specify in model, what to show as value. Simply this solved this issue:
class Airport < ActiveRecord::Base
has_many :cargos, :dependent => :destroy
attr_accessible :name
validates :name, presence: true
validates :name, :uniqueness => true
def to_s
name
end
end
This is described, I didnot understand it on first sight previously, in original documentation here https://github.com/maxivak/bootstrap3_autocomplete_input section Model.
User f.association and because rails will automatically look for :nameand you do not have that, you'll have to define it like so:
f.association :airport, label_method: :full_airport_name, value_method: :id........etc
I am working on a self-learning Rails application (the source code can be found here. I want to validate the presence of the content before posting a text or an image:
.
Those are my models or look below:
class Post < ActiveRecord::Base
belongs_to :user
default_scope { order ("created_at DESC")}
belongs_to :content, polymorphic: true
has_reputation :votes, source: :user, aggregated_by: :sum
end
class PhotoPost < ActiveRecord::Base
has_attached_file :image, styles: {
post: "200x200>"
}
end
class TextPost < ActiveRecord::Base
attr_accessible :body
end
Here are my controllers in case they have a relation with this. Any other files can be found in my Github account. I am sure it will be messy to copy the whole project (that is why I am giving links for the controllers and for my project).
So what I have tried so far. (I tried those on the Posts Model)
=> Using validates_associated
validates_associated :content, :text_post
and getting an error "undefined method `text_post' for #Post:0x517c848>"
=> Used validates
validates :content, :presence => true
and getting no error however a post is created with no text.
validates :body, :presence => true
and getting an error "undefined method `body' for #Post:0x513e4a8>"
If you need any other information please let me know and I will provide it asap.
Thank you.
It would seem you have quite a confusing model setup with some key missing relation rules. E.g. Polymorphic rule which is not being utilised and a has_many relation between User and Post with no sign a of a user_id value in the Post model. Here is how I would set it up:
User.rb
def User << ActiveRecord::Base
has_many :text_posts
has_many :photo_posts
end
TextPost.rb
def TextPost << ActiveRecord::Base
attr_accessible :body, :user_id
belongs_to :user
validates :body, :presence => true
end
PhotoPost.rb
def PhotoPost << ActiveRecord::Base
attr_accessible :image, :user_id
belongs_to :user
validates :file, :presence => true, :format => {
:with => %r{\.(gif|png|jpg)$}i,
:message => "must be a URL for GIF, JPG or PNG image."
}
end
Then in your view you would need to do:
<%= form_for #text_post do |f| %>
# ...
<% end %>
And in your controller you can modify the create method to include the current_user from devise and assign it to the new text post record (user_id attribute):
text_posts_controller.rb
def create
#text_post = current_user.text_posts.new(params[:text_post])
end
This adheres more to the DRY principle which Ruby on Rails excels at - you shouldn't be writing alot of code to just create a new record.
I would advise on reading up on some Ruby on Rails standard and best practises. You shouldn't need to create a method in the Dashboard Model in order to create a new TextPost or PhotoPost record. This is a very confusing way of going about it; instead you should be utilising the power of ActiveRecord relation.
I would advise checking out Railscasts. They have alot of fulfilling content.
I have many select options in my forms with a collection of possible options.
E.g.:
title_options = %w[Mr Mrs Miss Ms Dr]
In my view, I will render the select (using formtastic):
<%= f.input :title, :as => :select, :collection => title_options %>
Currently, though, I store the title options in a helper file with many methods for each select:
module SelectHelper
def days_options
...
end
def title_options
...
end
..
end
Then, in a model for validation I can either extend this helper:
class user < ActiveRecord::Base
extend SelectHelper
validates :title, :inclusion => {:in => title_options}
end
or duplicate the options:
class user < ActiveRecord::Base
validates :title, :inclusion => {:in = %w[Mr Mrs Miss Ms Dr]}
end
Is there a better way to store the collection, for example, as a method in the model directly? I don't see it necessary to store these options in a database as they shouldn't ever change.
Well, to simplify things, you can store this collections in a constant inside your class.
class User < ActiveRecord::Base
TITLES = %w[Mr Mrs Miss Ms Dr]
validates :title, :inclusion => {:in => TITLES}
You can try to use enumerated_attribute gem or some of same functionality
I have the following Models:
Language
Itemtype
Item
belongs_to :itemtype
LocalisedItem
belongs_to :item
belongs_to :language
The LocalisedItem model has an attribute called "title".
I want to validate the uniqueness of said "title" attribute. My problem is the scope: It´s supposed to be unique per language (easy) and itemtype, which I could not figure out how to do until now.
My best try...
validates :title, :uniqueness => { :scope => [:language_id, 'item.itemtype_id'] }
...fails with "NoMethodError: undefined method `item.itemtype_id'".
Is there any way to check for uniqueness in the way described?
You can use this format for validate uniqueness with a scope:
validates_uniqueness_of :title, :scope => :language_id
Is it posible to validate the uniqueness of a child model's attribute scoped against a polymorphic relationship?
For example I have a model called field that belongs to fieldable:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => :fieldable_id
end
I have several other models (Pages, Items) which have many Fields. So what I want is to validate the uniqueness of the field name against the parent model, but the problem is that occasionally a Page and an Item share the same ID number, causing the validations to fail when they shouldn't.
Am I just doing this wrong or is there a better way to do this?
Just widen the scope to include the fieldable type:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => [:fieldable_id, :fieldable_type]
end
You can also add a message to override the default message, or use scope to add the validation:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :fieldable_id, :scope => [:fieldable_id, :fieldable_type], :message => 'cannot be duplicated'
end
As a bonus if you go to your en.yml, and enter:
activerecord:
attributes:
field:
fieldable_id: 'Field'
You are going to replace the default 'subject' that rails add to the errors with the one you specify here. So instead of saying: Fieldable Id has been already taken or so, it would say:
Field cannot be duplicated