Last part of my project, Hopefully.
Need to check if user.email attribute changes. If it does, then need to tell mailchimp to change or add the email.
Looks like Dirty will do this, but have never used it before. How can I catch the change in a block, or pass it to a block, and then update the attribute?
Using the ActiveRecord::Dirty module is pretty straightforward:
bob = User.find_by_email('bob#example.org')
bob.changed? # => false
bob.email = 'robert#example.org')
bob.changed? # => true
bob.email_changed? # => true
bob.email_was # => 'bob#example.org'
bob.email_change # => ['bob#example.org', 'robert#example.org']
bob.changed # => ['email']
bob.changes # => { 'email' => ['bob#example.org', 'robert#example.org'] }
I recommend using Rails Dirty methods:
if #user.email.changed?
# ...
end
But you can also do:
if #user.email != params[:user][:email]
# ...
end
Related
I got an events helper module that somebody coded in a rails application. I am working on a form that can allow someone to create a new event.
here is a part of the form
=form.input :sponsorship_type, collection: get_event_labels(:event_types), as: :select_other
=form.input :society_name
it used to be
=form.input :event_type, collection: get_event_labels(:sponsorship_types), as: :select_other
=form.input :society_name
per the client request I had to drop the event_type column from the events table and added this instead
t.string "sponsorship_type"
the old schema has this
t.string "event_type"
this is the module
module EventsHelper
LABEL_MAP = {
institutions: [::INSTITUTIONS, 'activerecord.values.institutions.name'],
event_types: [::EVENT_TYPES, 'activerecord.values.event_types'],
industries: [::INDUSTRIES, 'activerecord.values.industries'],
referrers: [::REFERRERS, 'activerecord.values.referrers'],
regions: [::REGIONS, 'activerecord.values.regions'],
cities: [::CITIES, 'activerecord.values.cities']
}.freeze
def get_event_labels(type)
if Geokit::Geocoders::IpGeocoder.geocode(remote_ip).country_code == 'TW' and type == :event_types
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
end
Hash[
LABEL_MAP[type][0].map do |constant|
[I18n.t("#{LABEL_MAP[type][1]}.#{constant}"),
constant]
end
]
end
def remote_ip
request.remote_ip
end
end
what is this? [::EVENT_TYPES, 'activerecord.values.event_types']
i tried just changing all the event_types to sponsorship_type. and then I am getting a
': uninitialized constant SPONSORSHIP_TYPES (NameError)
Its probably because activerecord.values.sponsorship_types have no values. How do I access it and put in values?
what is this?
::EVENT_TYPES
my end goal is to return the hash
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
as selection option for the user on the form.
EVENT_TYPES is a constant. It must be defined somewhere in that application, perhaps in the controller or somewhere in the config folder. Find it and define your SPONSORSHIP_TYPES in the same way.
activerecord.values.event_types looks like a localization key. Look into your localization files in config/locales/... for some yaml hash with this structure. Add a new node sponsorship_types in the same way.
I usually have to check things like:
if ['Bob','Mary','John'].include? #user.name
Is there a way to write something like:
if #user.name.in? ['Bob','Mary','John']
Thank you.
Rails 3.1 has got this Object.in? method
characters = ["Konata", "Kagami", "Tsukasa"]
"Konata".in?(characters) # => true
character = "Konata"
character.in?("Konata", "Kagami", "Tsukasa") # => true
If #user.name is a String, you can add in? to String.
class String
def in? a
a.include? self
end
end
This has the following effect:
irb(main):011:0> 'Bob'.in? ['Bob','Mary','John']
=> true
irb(main):012:0> 'Jane'.in? ['Bob','Mary','John']
=> false
I have an ActiveRecord object and I would like to prevent it from being saved, without having permanent validations on the model. You used to be able to do something like this using errors.add but it doesn't look like it works anymore.
user = User.last
user.errors.add :name, "name doesn't rhyme with orange"
user.valid? # => true
user.save # => true
or
user = User.last
user.errors.add :base, "my unique error"
user.valid? # => true
user.save # => true
How can I prevent the user object from getting saved in Rails 3.2 without modifying it's model?
You can set errors, but do it within a validate method, e.g.:
validate :must_rhyme_with_orange
def must_rhyme_with_orange
unless rhymes_with_orange?
errors.add(:name, "doesn't rhyme with orange")
end
end
If you want to conditionally run the validation, one trick is to use attr_accessor and a guard condition:
attr_accessor :needs_rhyming
validate :must_rhyme_with_orange, :if => Proc.new {|o| o.needs_rhyming}
> u = User.last
> u.needs_rhyming = true
> u.valid? # false
Your issue is running valid? will rerun the validations.. resetting your errors.
pry(main)> u.errors[:base] << "This is some custom error message"
=> ["This is some custom error message"]
pry(main)> u.errors
=> {:base=>["This is some custom error message"]}
pry(main)> u.valid?
=> true
pry(main)> u.errors
=> {}
pry(main)>
Instead, just check if u.errors.blank?
This is a slight deviation from the original question, but I found this post after trying a few things. Rails has built in functionality to reject an object from saving if it has the _destroy attribute assigned as true. Quite helpful if you're handling model creation on the controller level.
I have a Rails 3 app which JSON encodes objects in order to store them in a Redis key/value store.
When I retrieve the objects, I'm trying to decode the JSON and instantiate them from the data like so:
def decode(json)
self.new(ActiveSupport::JSON.decode(json)["#{self.name.downcase}"])
end
The problem is that doing this involves mass assignment which is disallowed (for good reason I'm told!) for attributes I haven't given attr_writer ability to.
Is there a way I can bypass the mass assignment protection just for this operation only?
assign_attributes with without_protection: true seems less intrusive:
user = User.new
user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
user.name # => "Josh"
user.is_admin? # => true
#tovodeverett mentioned in the comment you can also use it with new, like this in 1 line
user = User.new({ :name => 'Josh', :is_admin => true }, :without_protection => true)
EDIT: kizzx2's Answer is a much better solution.
Kind of a hack, but...
self.new do |n|
n.send "attributes=", JSON.decode( json )["#{self.name.downcase}"], false
end
This invokes attributes= passing false for the guard_protected_attributes parameter which will skip any mass assignment checks.
You can create a user also in this way which is not doing the mass assignment.
User.create do |user|
user.name = "Josh"
end
You may want to put this into a method.
new_user(name)
User.create do |user|
user.name = name
end
end
How can I prevent users from adding new tags which don't already exist in the tags db?
I want them to be able to add any tags that already exist to another model which they can fully edit, but not be able to create new tags if they don't yet exist?
I'm using declarative_auth so some users with permissions should be create to add whatever tags they want.
user.rb
acts_as_tagger
post.rb
acts_as_taggable_on :features
https://github.com/mbleigh/acts-as-taggable-on
UPDATE:
This seems to do it except I can't get the error message variable to work:
validates :feature_list, :inclusion => {
:in => SomeModel.tag_counts_on(:features).map(&:name),
:message => "does not include {s}" }
I havn't used acts_as_taggable, but can you pass normal rails validations?
# LIKE is used for cross-database case-insensitivity
validates_inclusion_of :name => lambda { find(:all, :conditions => ["name LIKE ?", name]) }
Could probably be more robust and rails validation like but this works:
validate :valid_feature_tag
def valid_feature_tag
invalid_tags = false
feature_list.each do |tag|
list = SomeModel.tag_counts_on(:features).map(&:name)
unless list.include?(tag)
invalid_tags = true
end
end
unless invalid_tags == false
errors.add(:feature_list, 'cannot contain new tags, please suggest new tags to us')
return false
else
return true
end
end
Here's an efficient and clean way to enforce allowed tags:
validate :must_have_valid_tags
def must_have_valid_tags
valid_tags = ActsAsTaggableOn::Tag.select('LOWER(name) name').where(name: tag_list).map(&:name)
invalid_tags = tag_list - valid_tags
if invalid_tags.any?
errors.add(:tag_list, "contains unknown tags: [#{invalid_tags.join(', ')}]")
end
end